Unable to return value via Platform Channel when using Swift Callback Handlers - swift

I am working with Flutter and Swift. I'm trying to authorize a credit card using the AuthrizeNet SDK, which unfortunately, does not natively support Flutter. To get around this, I created a Platform Channel in my iOS AppDelegate, which I successfully managed receive data in from Flutter. My question now is how can I return that data from inside the callback handler of the authorizing function (from the AuthorizeNet SDK)? When trying to call the Flutter result function, the Swift compiler throws this error: Escaping closure captures non-escaping parameter 'result'. Here's my code:
handler!.getTokenWithRequest(request, successHandler: { (inResponse:AcceptSDKTokenResponse) -> () in
let paymentResponseModel: PaymentResponseModel = PaymentResponseModel.init(
token: inResponse.getOpaqueData().getDataValue(),
resultCode: inResponse.getMessages().getResultCode(),
tokenResultDescription: inResponse.getOpaqueData().getDataDescriptor(),
messageCode: inResponse.getMessages().getMessages()[0].getCode(),
messageText: inResponse.getMessages().getMessages()[0].getText()
)
result(String(data: try! JSONEncoder().encode(paymentResponseModel), encoding: String.Encoding.utf8))
}) { (inError:AcceptSDKErrorResponse) in
let paymentResponseModel: PaymentModelErrorResponse = PaymentModelErrorResponse.init(
code: inError.getMessages().getResultCode(),
message: inError.getMessages().getMessages()[0].getCode(),
details: inError.getMessages().getMessages()[0].getText()
)
result(String(data: try! JSONEncoder().encode(paymentResponseModel), encoding: String.Encoding.utf8))
}
I've tried different way of calling the result function outside of the getTokenWithRequest function, but I couldn't get it to work. I'm fairly new to Swift programming, so I apologize if this question is vague or has a simple solution. Thank you!

The problem is that the successHandler will be called in the future (as a callback), but result must be called immediately and you must return from the method of your code snippet immediately. So, how do you get the success/error response back? You have to make a call from native to Dart when the the token response becomes available. Then, at the Dart end, you'll have to hang about until that response call is made when you can update your app state, and the UI.
It becomes a bi-directional method channel like this:
---token request--->
<---result----------
<---token response--
----result--------->
Neither of the results contains much information, but could be used to convey errors. For example, if the first result comes back false, something went wrong and the Dart end shouldn't expect the token response - it won't happen.

Related

Check type of generic Combine Publisher Output Swift

I have a function which access data from my server. The server can either return a JSON object of type Player:
{"id":"6B38EF76-6BBC-4423-BCB6-FD9F9B5E7A6F","ign":"Shard","region":"eu","rating":1000}
or of type Standard Response:
{"statusCode":4,"description":"Invalid Arguments"}
I have a function with the following pattern:
fetch<T: Decodable>(from endpoint: Endpoint) -> AnyPublisher<T, DatabaseError>
This function access the server via a data task publisher and decodes the result into a generic T using combine's built in decode operator:
.decode(type: T.self, decoder: JSONDecoder())
I have the two types of response I showed above modelled as structs with the relevant coding keys etc and this part of the code works fine.
I use the fetch function inside a different function which passes it the relevant endpoint
func getPlayerDetails(of id: UUID) -> AnyPublisher<Player, DatabaseError> {
return fetch(from: .getDetails(of: id, isTeam: false))
}
As you can see this function returns the publisher with Player as the output. My question is how can I check the generic output of fetch(from:) and if it is a player object, pass that through, and if not return my custom error DatabaseError?
Thanks
Okay so I figured out how I to solve my problem, this doesn't really answer the original question however this link does to the most part solve it.
The solution I used in the end was to check the status code of my data task before anything else. My server side code was configured such that the status code would be 200 for when the first JSON was sent, and 500 if the second JSON was sent. This meant I could simply decode the second JSON before if the status code showed that it would be sent.
Thanks for your help

How to call custom method right before sessionTask resume in Alamofire

I need to record some data/info when Alamofire called resume to start the request.
(I used Swift in my project)
Is there anyway without do method_swizzling?
the timeline will be like this:
Call a request (put in request queue or execute right away) -> [custom method] -> SessionTask.resume()
I know Moya did something similar called WillSend. But I would like to know how to do it without using Moya.
Thank you.
If all you need to do is inspect various request elements you can use Alamofire 5's EventMonitor protocol, which is called during various lifetime events as Alamofire makes requests. There's a built in ClosureEventMonitor which allows you to set closures for those events. For example:
let monitor = ClosureEventMonitor()
monitor.requestDidCompleteTaskWithError = { (request, task, error) in
debugPrint(request)
}
let session = Session(eventMonitors: [monitor])
Please see our documentation for ClosureEventMonitor and the EventMonitor protocol itself.

Reactive asynchronous feedback system with RxSwift

I am designing a call manager with the help of RXSwift (ReactiveX) that continuously interacts with an API. The call manager comprises several objects that itself comprises an indicator (indicating status information loaded from the API) and control (requests to be sent to the API).
class CallManagerObjectA() {
var control = PublishSubject<String>()
var indicator = BehaviorSubject<String>(value: "string status")
}
Within the call manager, a scheduler regularly provides new values to the indicator observable:
<... API response ...>
indicator.onNext(newValue)
Somewhere else in a view controller, the indicator will be observed for a label:
indicator.subscribe(onNext: { label.stringValue = $0 })
Within the same view controller, the user can control the object status via GUI elements continuously:
control.onNext(commandValue)
Within the call manager, the control will be observed for an API call:
control.subscribe(onNext: { (command) in
// API request call
})
So far so good, this is working very well with reactive patterns.
Now, I am looking for a good solution to handle errors, if the call manager recognizes errors during the API interaction and show these errors to the user in the view controller. I was immediately thinking of something like this:
// Call manager recognizes the error
control.onError(error)
...
// Call manager ignores errors for the subscriber
control.retry().ignoreErrors().subscribe(onNext: { (command) in
// API request call
})
...
// View controller shows the errors
indicator.subscribe(onNext: { label.stringValue = $0 })
control.subscribe(onError: { print("error", $0) })
This however ends up in an infinite loop.
I fear that I have a fundamental understanding issue with reactive programming, or I miss something very important, but I am not able to understand how the handle errors in this reactive pattern environment.
Based on the code you have shown, you have a big misunderstanding, not just with how to handle Errors, but with how to program reactively in general. Try watching this video "Reactive Programming: Why It Matters"
To answer your specific question, there are two misunderstandings here:
When you call control.onError(_:) it will be the last call you will be able to make on control. Once it emits an error it will stop working.
The retry() operator asks its source to "try again on Error". If it's source is determinate, then it will just do the exact same thing it did before and emit the exact same output (i.e., the same error it emitted last time.) In the case of a PublishSubject, it doesn't know why onError was called. So the best it can do is just emit the error again.
Honestly, I consider this a bug in the API because subscribing to a publish subject that emitted an error at some point in the past should just do nothing. But then, you wouldn't be asking why you got an infinite loop. Instead you would be asking why your control stopped emitting events.

Empty response on long running query SailsJS

I'm currently running SailsJS on a Raspberry Pi and all is working well however when I execute a sails.models.nameofmodel.count() when I attempt to respond with the result I end up getting a empty response.
getListCount: function(req,res)
{
var mainsource = req.param("source");
if(mainsource)
{
sails.models.gatherer.find({source: mainsource}).exec(
function(error, found)
{
if(error)
{
return res.serverError("Error in call");
}
else
{
sails.log("Number found "+found.length);
return res.ok({count: found.length});
}
}
);
}
else
{
return res.ok("Error in parameter");
}
},
I am able to see in the logs the number that was found (73689). However when responding I still get an empty response. I am using the default stock ok.js file, however I did stick in additional logging to try to debug and make sure it is going through the correct paths. I was able to confirm that the ok.js was going through this path
if (req.wantsJSON) {
return res.jsonx(data);
}
I also tried adding .populate() to the call before the .exec(), res.status(200) before I sent out a res.send() instead of res.ok(). I've also updated Sails to 11.5 and still getting the same empty response. I've also used a sails.models.gatherer.count() call with the same result.
You can try to add some logging to the beginning of your method to capture the value of mainsource. I do not believe you need to use an explicit return for any response object calls.
If all looks normal there, try to eliminate the model's find method and just evaluate the request parameter and return a simple response:
getListCount: function(req, res) {
var mainsource = req.param("source");
sails.log("Value of mainsource:" + mainsource);
if (mainsource) {
res.send("Hello!");
} else {
res.badRequest("Sorry, missing source.");
}
}
If that does not work, then your model data may not actually be matching on the criteria that you are providing and the problem may lie there; in which case, your response would be null. You mentioned that you do see the resulting count of the query within the log statement. If the res.badRequest is also null, then you may have a problem with the version of express that is installed within sailsjs. You mention that you have 11.5 of sailsjs. I will assume you mean 0.11.5.
This is what is found in package.json of 0.11.5
"express": "^3.21.0",
Check for any possible bugs within the GitHub issues for sailsjs regarding express and response object handling and the above version of express.
It may be worthwhile to perform a clean install using the latest sailsjs version (0.12.0) and see if that fixes your issue.
Another issue may be in how you are handling the response. In this case .exec should execute the query immediately (i.e. a synchronous call) and return the response when complete. So there should be no asynchronous processing there.
If you can show the code that is consuming the response, that would be helpful. I am assuming that there is a view that is showing the response via AJAX or some kind of form POST that is being performed. If that is where you are seeing the null response, then perhaps the problem lies in the view layer rather than the controller/model.
If you are experiencing a true timeout error via HTTP even though your query returns with a result just in time, then you may need to consider using async processing with sailjs. Take a look at this post on using a Promise instead.

Retry an NSURLSessionDataTask request after re-authorizing (AKA NSInvocation equivalent in Swift)

TL;DR version: What is the nearest equivalent to NSInvocation - or similar way to package up a method call, including arguments - in Swift?
Background: I have a ServerController object which is used across my app to make requests to an API. The magic happens in this method:
private func makeRequestWithPath(
path: String,
method: String = "GET",
body: NSData? = nil,
successBlock: (AnyObject?) -> Void) {
// ...
self.urlSession.dataTaskWithRequest(request,
completionHandler: { (data, resp, error) -> Void in
if let httpResp = resp as? NSHTTPURLResponse {
switch httpResp.statusCode {
case 200:
successBlock(/* pass process response in here */)
case 401:
self.postNotificationOnMainThread(kAppTokenRefreshRequiredNotification)
// ...
}.resume()
Periodically API requests fail with a 401, which gets broadcast in the app as a notification, as many parties are interested in the fact the request is going to take a bit longer. Another API request is used to refresh an app token and then other API requests start working again.
This is the part I need help with: I then need to repeat the original API request following re-authorization.
Previously in Objective-C I might have used an NSInvocation object to package up the original method call, but this is not available in Swift and I'm therefore thinking there must be a more Swift-like approach. However I've not yet managed to get it figured out.
The best approach I've come up with so far is to:
put the arguments, along with a reference to the ServerController into a dictionary and pass them into the notification as the object: parameter
in the notification observer (after completing the re-authorization), pull the arguments out of the dictionary and manually call the same method again to repeat the request
This seems like a sub-optimal solution and there must be better way.