How to provide additional variables to webview onPageFinished method? - webview

I have "val" values from api response call which I need to provide to onPageFinished.
How can I make that?
override fun onResponse(call: Call, response: Response) {
//getting info from response to json
val app json.app ?: ""
view.loadUrl(json.url)
}
view.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
//and now I need to get this app variable
}
}

Related

How apply await in request a web?

I have this code, i want do a request a url
if(!m.sendRequest()){
appDelegate.setValues()
}
this line is executed first
appDelegate.setValues()
i want wait this condition
!m.sendRequest()
structure sendRequest with a completion
func sendRequest(com:#escaping((Bool) -> ())) {
Api.sendReq {
com(true)
}
}
sendRequest { value in
if value {
appDelegate.setValues()
}
}

Moya rxswift : Refresh token and restart request

I'm using Moya Rx swift and i want to catch the response if the status code is 401 or 403 then call refresh token request then recall/retry the original request again and to do so i followed this Link but i tweaked it a bit to suit my needs
public extension ObservableType where E == Response {
/// Tries to refresh auth token on 401 errors and retry the request.
/// If the refresh fails, the signal errors.
public func retryWithAuthIfNeeded(sessionServiceDelegate : SessionProtocol) -> Observable<E> {
return self.retryWhen { (e: Observable<Error>) in
return Observable
.zip(e, Observable.range(start: 1, count: 3),resultSelector: { $1 })
.flatMap { i in
return sessionServiceDelegate
.getTokenObservable()?
.filterSuccessfulStatusAndRedirectCodes()
.mapString()
.catchError {
error in
log.debug("ReAuth error: \(error)")
if case Error.StatusCode(let response) = error {
if response.statusCode == 401 || response.statusCode == 403 {
// Force logout after failed attempt
sessionServiceDelegate.doLogOut()
}
}
return Observable.error(error)
}
.flatMapLatest({ responseString in
sessionServiceDelegate.refreshToken(responseString: responseString)
return Observable.just(responseString)
})
}}
}
}
And my Protocol :
import RxSwift
public protocol SessionProtocol {
func doLogOut()
func refreshToken(responseString : String)
func getTokenObservable() -> Observable<Response>?
}
But it is not working and the code is not compiling, i get the following :
'Observable' is not convertible to 'Observable<_>'
I'm just talking my first steps to RX-swift so it may be simple but i can not figure out what is wrong except that i have to return a type other than the one I'm returning but i do not know how and where to do so.
Your help is much appreciated and if you have a better idea to achieve what I'm trying to do, you are welcome to suggest it.
Thanks in advance for your help.
You can enumerate on error and return the String type from your flatMap. If the request succeeded then it will return string else will return error observable
public func retryWithAuthIfNeeded(sessionServiceDelegate: SessionProtocol) -> Observable<E> {
return self.retryWhen { (error: Observable<Error>) -> Observable<String> in
return error.enumerated().flatMap { (index, error) -> Observable<String> in
guard let moyaError = error as? MoyaError, let response = moyaError.response, index <= 3 else {
throw error
}
if response.statusCode == 401 || response.statusCode == 403 {
// Force logout after failed attempt
sessionServiceDelegate.doLogOut()
return Observable.error(error)
} else {
return sessionServiceDelegate
.getTokenObservable()!
.filterSuccessfulStatusAndRedirectCodes()
.mapString()
.flatMapLatest { (responseString: String) -> Observable<String> in
sessionServiceDelegate.refreshToken(responseString: responseString)
return Observable.just(responseString)
}
}
}
}
Finally i was able to solve this by doing the following :
First create a protocol like so ( Those functions are mandatory and not optional ).
import RxSwift
public protocol SessionProtocol {
func getTokenRefreshService() -> Single<Response>
func didFailedToRefreshToken()
func tokenDidRefresh (response : String)
}
It is very very important to conform to the protocol SessionProtocol in the class that you write your network request(s) in like so :
import RxSwift
class API_Connector : SessionProtocol {
//
private final var apiProvider : APIsProvider<APIs>!
required override init() {
super.init()
apiProvider = APIsProvider<APIs>()
}
// Very very important
func getTokenRefreshService() -> Single<Response> {
return apiProvider.rx.request(.doRefreshToken())
}
// Parse and save your token locally or do any thing with the new token here
func tokenDidRefresh(response: String) {}
// Log the user out or do anything related here
public func didFailedToRefreshToken() {}
func getUsers (page : Int, completion: #escaping completionHandler<Page>) {
let _ = apiProvider.rx
.request(.getUsers(page: String(page)))
.filterSuccessfulStatusAndRedirectCodes()
.refreshAuthenticationTokenIfNeeded(sessionServiceDelegate: self)
.map(Page.self)
.subscribe { event in
switch event {
case .success(let page) :
completion(.success(page))
case .error(let error):
completion(.failure(error.localizedDescription))
}
}
}
}
Then, I created a function that returns a Single<Response>.
import RxSwift
extension PrimitiveSequence where TraitType == SingleTrait, ElementType == Response {
// Tries to refresh auth token on 401 error and retry the request.
// If the refresh fails it returns an error .
public func refreshAuthenticationTokenIfNeeded(sessionServiceDelegate : SessionProtocol) -> Single<Response> {
return
// Retry and process the request if any error occurred
self.retryWhen { responseFromFirstRequest in
responseFromFirstRequest.flatMap { originalRequestResponseError -> PrimitiveSequence<SingleTrait, ElementType> in
if let lucidErrorOfOriginalRequest : LucidMoyaError = originalRequestResponseError as? LucidMoyaError {
let statusCode = lucidErrorOfOriginalRequest.statusCode!
if statusCode == 401 {
// Token expired >> Call refresh token request
return sessionServiceDelegate
.getTokenRefreshService()
.filterSuccessfulStatusCodesAndProcessErrors()
.catchError { tokeRefreshRequestError -> Single<Response> in
// Failed to refresh token
if let lucidErrorOfTokenRefreshRequest : LucidMoyaError = tokeRefreshRequestError as? LucidMoyaError {
//
// Logout or do any thing related
sessionServiceDelegate.didFailedToRefreshToken()
//
return Single.error(lucidErrorOfTokenRefreshRequest)
}
return Single.error(tokeRefreshRequestError)
}
.flatMap { tokenRefreshResponseString -> Single<Response> in
// Refresh token response string
// Save new token locally to use with any request from now on
sessionServiceDelegate.tokenDidRefresh(response: try! tokenRefreshResponseString.mapString())
// Retry the original request one more time
return self.retry(1)
}
}
else {
// Retuen errors other than 401 & 403 of the original request
return Single.error(lucidErrorOfOriginalRequest)
}
}
// Return any other error
return Single.error(originalRequestResponseError)
}
}
}
}
What this function do is that it catches the error from the response then check for the status code, If it is any thing other than 401 then it will return that error to the original request's onError block but if it is 401 (You can change it to fulfill your needs but this is the standard) then it is going to do the refresh token request.
After doing the refresh token request, it checks for the response.
=> If the status code is in bigger than or equal 400 then this means that the refresh token request failed too so return the result of that request to the original request OnError block.
=> If the status code in the 200..300 range then this means that refresh token request succeeded hence it will retry the original request one more time, if the original request fails again then the failure will go to OnError block as normal.
Notes:
=> It is very important to parse & save the new token after the refresh token request is successful and a new token is returned, so when repeating the original request it will do it with the new token & not with the old one.
The token response is returned at this callback right before repeating the original request.
func tokenDidRefresh (response : String)
=> In case the refresh token request fails then it may that the token is expired so in addition that the failure is redirected to the original request's onError, you also get this failure callback
func didFailedToRefreshToken(), you can use it to notify the user that his session is lost or log him out or anything.
=> It is very important to return the function that do the token request because it is the only way the refreshAuthenticationTokenIfNeeded function knows which request to call in order to do the refresh token.
func getTokenRefreshService() -> Single<Response> {
return apiProvider.rx.request(.doRefreshToken())
}
Instead of writing an extension on Observable there's another solution. It's written on pure RxSwift and returns a classic error in case of fail.
The easy way to refresh session token of Auth0 with RxSwift and Moya
The main advantage of the solution is that it can be easily applicable for different services similar to Auth0 allowing to authenticate users in mobile apps.

Getting an AuthToken using Siesta via Password Grant

new to both Swift and Siesta... Trying to make a "password" grant type request. I used the code located here (the block at the very bottom). My code is:
var authToken: String??
var tokenCreationResource: Resource { return resource("oauth/v2/token") }
func refreshTokenOnAuthFailure(request: Request) -> Request {
return request.chained {
guard case .failure(let error) = $0.response, // Did request fail…
error.httpStatusCode == 401 else { // …because of expired token?
return .useThisResponse // If not, use the response we got.
}
return .passTo(
self.createAuthToken().chained { // If so, first request a new token, then:
if case .failure = $0.response { // If token request failed…
return .useThisResponse // …report that error.
} else {
//print($0.response)
return .passTo(request.repeated()) // We have a new token! Repeat the original request.
}
}
)
}
}
func userAuthData() -> [String: String] {
return [
"username": "username",
"password": "password",
"grant_type": "password",
"client_id": "abc1234567",
"client_secret": "1234567abc"
]
}
func createAuthToken() -> Request {
print("requestingToken")
return tokenCreationResource
.request(.post, urlEncoded: userAuthData())
.onSuccess {
self.authToken = $0.jsonDict["access_token"] as? String // Store the new token, then…
print($0.jsonDict) //*****SEE MY NOTE BELOW ABOUT THIS LINE
self.invalidateConfiguration() // …make future requests use it
}
}
The problem is that it doesn't seem to set the authToken variable... In troubleshooting the $0.jsonDict variable in the createAuthToken() function seems to be empty. The line noted prints [:]
If I change the print($0.jsonDict) to print($0) I see the full response including the "content" section which displays the results I would have expected to be in the jsonDict
If it matters, my server implementation is Symfony using FOSOauthServerBundle. All this works fine if I just manually do a request in the browser and like I said the "content" of the response shows my token, I just can't seem to access it via the .jsonDict["access_token"]
Had this exact same issue with the example code - you need to remove
standardTransformers: [.text, .image]
from the Service constructor (or include .json).

Unable to access variable within a function in Swift

In the function I'm building a socket method but I can't access pCount after the socket method in the function
Use of unresolved identifier 'pCount'
Why?
psCount is async function so you can't return in it. In case, socket.on have't finished but psCount is still return
You should code:
func psCount(handleFinish:((pCount:Int)->())){
socket.on("reply") { data, act in
let json = Json(data)
let pCount:Int = json[0].count
handleFinish(pCount: pCount:Int)
}
}
and call it:
self.psCount { (pCount) -> () in
print(pCount)
}

Using a Swift closure to capture a variable

I have this bit of code and it obviously errors out because when I use FOO in the return statement it's outside of the scope of the function. I know (I think I know) I need to use a closure to capture the variable but I can't figure out how to do that. Using Alamofire & SwiftyJSON. Any help would be great! Thanks!
func getPlayerID(named: String) -> String {
Alamofire.request(.GET, "URL", headers: headers)
.responseJSON { response in
let json = JSON.self(response.result.value!)
for var index = 0; index < json.count; index++ {
if json[index]["Name"].stringValue == named {
var FOO = json[index]["FOO"].stringValue
} // If Statement End
} // For Loop End
} // Alamofire Request End
// Return Statement for getPLayerID Function
return FOO
} // getPlayerID Function End
} // Player Struct End
The basic idea is that getPlayerID should not return anything, but rather should just have a parameter which is a closure, and once you retrieve the value you want to "return", you call the closure using that value as a parameter.
But, I'd suggest all sorts of other refinements here:
Build an array of the strings and return that
Check to see if result is a .Failure because you have no control over what various server/network issues may arise
Change the closure to detect and report errors
But hopefully this illustrates the basic idea:
Personally, I'd (a) make the String parameter to the completionHandler optional; (b) add another optional error parameter; and (c) add error handling to the getPlayerID:
func getPlayerID(completionHandler: ([String]?, ErrorType?) -> Void) {
Alamofire.request(.GET, "URL", headers: headers)
.responseJSON { request, response, result in
switch (result) {
case .Success(let value):
let json = JSON.self(value)
// variable to hold all of the results
var strings = [String]()
// populate the array of strings
for var index = 0; index < json.count; index++ {
if json[index]["Name"].stringValue == named {
strings.append(json[index]["FOO"].stringValue)
}
}
// call the completion handler with the strings
completionHandler(strings, nil)
case .Failure(_, let error):
completionHandler(nil, error)
}
}
}
And then, when you want to call it:
getPlayerID() { strings, error in
// use `strings` here
}
// but not here
If you make an asynchronous request you can not return a value received in response in the same function cause it needs time for request to be sent over network to the server and back. The best way to solve this out is to add callback parameter to your function instead of return value.
func getPlayerID(named: String, callback:(foo:String)->()) {
Alamofire.request(.GET, "URL", headers: headers)
.responseJSON { response in
let json = JSON.self(response.result.value!)
for var index = 0; index < json.count; index++ {
if json[index]["Name"].stringValue == named {
var FOO = json[index]["FOO"].stringValue
callback(foo: FOO) // you fire callback here..
} // If Statement End
} // For Loop End
} // Alamofire Request End
} // getPlayerID Function End
Callback is a block object that will be fired when your response will be received. So if response is not coming (for example, internet connection went down) callback will never fired.
Example how to use this:
self.getPlayerID("ototo") { (foo) -> () in
print("foo received = \(foo)")
}
Also there is a time span between sending the request and receiving the response. So it is a good practice to add UIActivityIndicatorView in UI of your app until response is arrived (and handle timeout if internet connection suddenly went down).

Resources