How to bridge a Swift Promise to React Native - swift

I'm integrating an iOS native SDK into React-Native. There's a function called SDK.getCardData which I want to use from RN. My first attempt was to call resolve and reject inside the closure:
import Foundation
import SDK
#objc(SwiftComponentManager)
class SwiftComponentManager: NSObject {
#objc
func getCardData(_ resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) -> Void {
let cardId: String = "test"
let secret: String = "test"
SDK.getCardData(cardId, secret: secret) { (cardData, error) in
if (error != nil) {
reject(String(format: "Card data request failed: %#", error!.localizedDescription))
} else {
let pan = cardData!.pan
let cvv = cardData!.cvv
resolve(String(format: "Card data fetched successfully, pan: %#, cvv: %#", pan, cvv))
}
}
}
#objc func testMethod() -> Void {
print("This Does appear")
}
}
Unfortunately this throws the following error: escaping closure captures non-escaping parameter 'resolve'.
Second attempt:
import Foundation
import SDK
import Promises
#objc(SwiftComponentManager)
class SwiftComponentManager: NSObject {
#objc
func getCardData(_ resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) -> Promise<CardData?> {
let cardId: String = "test"
let secret: String = "test"
let promise = Promise<CardData?>()
SDK.getCardData(cardId, secret: secret) { (cardData, error) in
if (error != nil) {
promise.reject(error)
} else {
let pan = cardData!.pan
let cvv = cardData!.cvv
promise.resolve(cardData)
}
}
return promise
}
#objc func testMethod() -> Void {
print("This Does appear")
}
}
How to call the resolve & reject properly? It is important for the function to return Void, more here.

Found the answer, Just had to add #escaping to the arguments:
#objc func fling(_ options: NSDictionary, resolver resolve: #escaping RCTPromiseResolveBlock, rejecter reject: #escaping RCTPromiseRejectBlock) -> Void {
...

Related

Invalid conversion from throwing function of type XXXX to non-throwing function type XXXX

I am stuck with this situation where I have a custom JSONDecoder struct which contains a private function to decode data, and another function which is exposed, and should return a specific, Decodable type. I would like these functions to throw successively so I only have to write my do/catch block inside the calling component, but I'm stuck with this error on the exposedFunc() function:
Invalid conversion from throwing function of type '(Completion) throws -> ()' (aka '(Result<Data, any Error>) throws -> ()') to non-throwing function type '(Completion) -> ()' (aka '(Result<Data, any Error>) -> ()')
Here is the code:
import Foundation
import UIKit
typealias Completion = Result<Data, Error>
let apiProvider = ApiProvider()
struct DecodableTest: Decodable {
}
struct CustomJSONDecoder {
private static func decodingFunc<T: Decodable>(
_ response: Completion,
_ completion: #escaping (T) -> Void
) throws {
switch response {
case .success(let success):
try completion(
JSONDecoder().decode(
T.self,
from: success
)
)
case .failure(let error):
throw error
}
}
static func exposedFunc(
value: String,
_ completion: #escaping (DecodableTest) -> Void
) throws {
apiProvider.request {
try decodingFunc($0, completion)
}
}
}
class CustomViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
do {
try CustomJSONDecoder.exposedFunc(value: "test_value") { result in
// Do something with result
}
} catch {
print(error)
}
}
}
class ApiProvider: NSObject {
func request(_ completion: #escaping (Completion) -> ()) {
}
}
Thank you for your help
This defines method that takes a non-throwing function:
class ApiProvider: NSObject {
func request(_ completion: #escaping (Completion) -> ()) {
}
}
So in all cases, this function must take a Completion and return Void without throwing. However, you pass the following:
apiProvider.request {
try decodingFunc($0, completion)
}
This method does throw (note the uncaught try), so that's not allowed. You need to do something if this fails:
apiProvider.request {
do {
try decodingFunc($0, completion)
} catch {
// Here you must deal with the error without throwing.
}
}
}
Not using concurrency features is making your code hard to understand. Switch!
final class CustomViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Task {
let result = DecodableTest()
// Do something with result
}
}
}
extension DecodableTest {
init() async throws {
self = try JSONDecoder().decode(Self.self, from: await APIProvider.data)
}
}
enum APIProvider {
static var data: Data {
get async throws { .init() }
}
}

Getting error while mocking services in test target

I am trying to write test cases for the test target. The app is running perfectly, but the test case failing with the mocking.
Here is my code, please let me know if there any other solutions. How do I mock services?
Is there any other way to write a test case for the ViewCoontroller initializer?
class NavigationCodeTests: XCTestCase {
var subject: ViewController?
override func setUp() {
self.subject = ViewController(service: MockUserService())
_ = self.subject?.view
}
func test_user_service_not_nil() {
XCTAssertNotNil(self.subject?.service, "User service can't be nil after initialization of ViewController")
}
func test_user_service_should_have_user() {
self.subject?.userViewModel?.user.observe(on: self, observerBlock: { (user) in
XCTAssertNotNil(user?.name, "User service can't be nil after initialization of ViewController")
})
}
}
class MockUserService: UserService {
func fetch(_ completion: #escaping(_ user: User) -> Void) {
completion(User(name: "abc", contact: "124"))
}
}
class UserService: UserServiceDelegate {
func fetch(_ completion: #escaping(_ user: User) -> Void) {
completion(User(name: "Damu", contact: "12"))
}
}
protocol UserServiceDelegate {
func fetch(_ completion: #escaping(_ user: User) -> Void)
}

Implement custom func with completion handler

I have just created a custom class for my purpose (export something to destination).
My extend class as below
import Foundation
import UIKit
class PBExtensionExport: NSObject {
static let instance = PBExtensionExport()
fileprivate var progress: Int = 0
func exportPhotos(images: [UIImage], completion: (Bool) -> ()) {
for image in images {
UIImageWriteToSavedPhotosAlbum(image, self, #selector(updateStatus), nil)
// Progress monitoring
completion(images.count == progress)
}
}
#objc private func updateStatus() {
// Updating progress status
progress += 1
}
}
And this is how I called it in main proc:
PBExtensionExport.instance.exportPhotos(images: selectedImages, completion: { (success) -> Void in
if success {
// Do something
} else {
}
})
The problem is no compile error. But when I run my code, the command display runtime exception:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '-[NSInvocation setArgument:atIndex:]: index (2) out of bounds [-1, 1]'
Can anyone explain this problem for me please?
Your updateStatus takes no arguments while UIImageWriteToSavedPhotosAlbum's completionSelector parameter expects this method signature, according to the docs.
- (void)image:(UIImage *)image
didFinishSavingWithError:(NSError *)error
contextInfo:(void *)contextInfo;
In Swift, this would be something like
func updateStatus(image: UIImage, error: Error?, context: UnsafeRawPointer) {
// Your code here
}
You should go with Photos framework to save images. See below code with proper use of completion handler.
fileprivate var currentImageIndex = 0
typealias imagecompletionBlock = (_ isSuccess: Bool) -> Void
var arrImages:[UIImage]?
var imageSuccessBlock : imagecompletionBlock?
func saveImageToLibrary(images:[UIImage],completion:#escaping ((Bool) -> Void))
{
self.imageSuccessBlock = completion
self.currentImageIndex = 0
}
func saveImage()
{
if self.currentImageIndex == self.arrImages?.count
{
self.imageSuccessBlock!(true)
}else{
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAsset(from: self.arrImages![self.currentImageIndex])
}, completionHandler: { success, error in
DispatchQueue.main.async {
if success {
// Saved successfully!
self.saveImage()
}
else if let error = error {
// Save photo failed with error
}
else {
// Save photo failed with no error
}
}
})
}
}

Mock third party classes (Firebase) in Swift

I'm trying to unit test a class of my own which is calling a method on a third party class:
FIRAuth.auth()?.signInAnonymously() { (user, error) in
//
}
I'm using protocol based dependency injection to achieve this:
protocol FIRAuthProtocol {
func signInAnonymously(completion: FIRAuthResultCallback?)
}
extension FIRAuth: FIRAuthProtocol {}
class MyClass {
private var firAuth: FIRAuthProtocol
init(firAuth: FIRAuthProtocol) {
self.firAuth = firAuth
}
func signIn() {
firAuth.signInAnonymously() { (user, error) in
//
}
}
}
class MockFIRAuth: FIRAuthProtocol {
var signInAnonymouslyCalled = false
func signInAnonymously(completion: FIRAuthResultCallback? = nil) {
signInAnonymouslyCalled = true
}
}
class MyClassSpec: QuickSpec {
override func spec() {
describe("MyClass") {
describe(".signIn()") {
it("should call signInAnonymously() on firAuth") {
let mockFIRAuth = MockFIRAuth()
let myClass = MyClass(firAuth: mockFIRAuth)
expect(mockFIRAuth.signInAnonymouslyCalled).to(beFalse())
myClass.signIn()
expect(mockFIRAuth.signInAnonymouslyCalled).to(beTrue())
}
}
}
}
}
So far so good!
Now, I'd like my mockFIRAuth to return an instance of FIRUser.
Here's my issue: I can't create an instance of FIRUser myself.
FYI: public typealias FIRAuthResultCallback = (FIRUser?, Error?) -> Swift.Void
If found this great article which explains how to make a method on a third party class return a protocol instead of a type. http://masilotti.com/testing-nsurlsession-input/
Maybe my situation is different than the article's, but here's my shot at this:
I've defined a FIRUserProtocol:
protocol FIRUserProtocol {
var uid: String { get }
}
extension FIRUser: FIRUserProtocol {}
I've updated my FIRAuthProtocol to call the completion handler with FIRUserProtocol instead of FIRUser:
protocol FIRAuthProtocol {
func signInAnonymously(completion: ((FIRUserProtocol?, Error?) -> Void)?)
}
I've updated my FIRAuth extension to support the modified protocol. My newly defined method calls the default implementation of signInAnonymously:
extension FIRAuth: FIRAuthProtocol {
func signInAnonymously(completion: ((FIRUserProtocol?, Error?) -> Void)? = nil) {
signInAnonymously(completion: completion)
}
}
Finally, I've updated MockFIRAuth to support the modified protocol:
class MockFIRAuth: FIRAuthProtocol {
var signInAnonymouslyCalled = false
func signInAnonymously(completion: ((FIRUserProtocol?, Error?) -> Void)? = nil) {
signInAnonymouslyCalled = true
}
}
Now, when I run my test everything comes to a crashing halt:
Thread 1: EXC_BAD_ACCESS (code=2, address=0x7fff586a2ff8)
Please advice!
Update
After renaming the completion argument label in my FIRAuthProtocol's method everything seems to work as expected:
protocol FIRAuthProtocol {
func signInAnonymously(completionWithProtocol: ((FIRUserProtocol?, Error?) -> Void)?)
}
extension FIRAuth: FIRAuthProtocol {
func signInAnonymously(completionWithProtocol: ((FIRUserProtocol?, Error?) -> Void)? = nil) {
signInAnonymously(completion: completionWithProtocol)
}
}
This solves my issue for now, but I'd still like to know why my first attempt was unsuccessful. Does this mean that the two methods with different parameter types in their closures can't be told apart, which was causing my app to crash?
I've finally found an elegant way to solve this.
protocol FIRAuthProtocol {
func signInAnonymously(completion: ((FIRUserProtocol?, Error?) -> Void)?)
}
extension FIRAuth: FIRAuthProtocol {
func signInAnonymously(completion: ((FIRUserProtocol?, Error?) -> Void)? = nil) {
let completion = completion as FIRAuthResultCallback?
signInAnonymously(completion: completion)
}
}
This way, there's no need to alter function names or argument labels.

Swift/Stripe error communicating with the Parse Cloud Code

I have been working for days trying to figure out how to charge a card and save the card to a customer with Stripe using Swift, with Parse.com Cloud Code as the backend. I integrated the Stripe pod with my project successfully, and I have a token that is created, and prints out in the console to verify its existence. BUT I cannot do anything with it! I have searched everywhere for answers, and cannot figure out why I keep getting error. I think it has to do with the parameters that I am trying to feed to the Cloud Code, but I am unsure. I have read the docs for both Cloud Code and Stripe, and it was to no avail. This is my PaymentViewController.swift:
import UIKit
import Stripe
import PaymentKit
import Parse
import Bolts
class PaymentViewController: UIViewController, PTKViewDelegate {
#IBOutlet weak var saveBtn: UIButton!
var paymentView: PTKView = PTKView()
override func viewDidLoad() {
super.viewDidLoad()
var view : PTKView = PTKView(frame: CGRectMake(15,20,290,55))
paymentView = view
paymentView.delegate = self;
self.view.addSubview(self.paymentView)
saveBtn.enabled = false
}
func paymentView(view: PTKView!, withCard card: PTKCard!, isValid valid: Bool) {
if (valid) {
saveBtn.enabled = true
} else {
saveBtn.enabled = false
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func save(sender: AnyObject) {
var card: STPCard = STPCard()
card.number = self.paymentView.card.number
card.expMonth = self.paymentView.card.expMonth
card.expYear = self.paymentView.card.expYear
card.cvc = self.paymentView.card.cvc
STPAPIClient.sharedClient().createTokenWithCard(card, completion: { (tokenId: STPToken?, error: NSError?) -> Void in
if (error != nil) {
println(error)
println("what the..")
} else {
println(tokenId)
PFCloud.callFunctionInBackground("hello", withParameters: nil) {
(response: AnyObject?, error: NSError?) -> Void in
let responseString = response as? String
println(responseString)
}
PFCloud.callFunctionInBackground("createCharge", withParameters: nil, block: { (success: AnyObject?, error: NSError?) -> Void in
if error != nil {
println("error")
}
})
}
})
}
#IBAction func cancel(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
}
I have added the "Hello World" example to see if the Cloud Code was set up correctly, and that callFunction does work. My Cloud Code is:
var Stripe = require('stripe');
Stripe.initialize('My_Secret_Key');
Parse.Cloud.define("hello", function(request, response) {
response.success("Hello world!");
});
Parse.Cloud.define("createCharge", function(request, response) {
Stripe.Charges.create({
amount: 100 * 10, // $10 expressed in cents
currency: "usd",
card: "tok_3TnIVhEv9P24T0"
},{
success: function(httpResponse) {
response.success("Purchase made!");
},
error: function(httpResponse) {
response.error("Uh oh, something went wrong");
}
});
});
Any help would be truly appreciated!! I have been working tirelessly to figure this out! The console prints out
Uh oh, something went wrong (Code: 141, Version: 1.7.2)
The error you are seeing Uh oh, something went wrong (Code: 141, Version: 1.7.2) means that your createCharge Parse function returned an error. You may want to log the value of httpResponse to find the exact error from Stripe.
Looking at your code the error is most likely: Invalid Request Error: Cannot use token tok_3TnIVhEv9P24T0 more than once. You can confirm this in the Logs section of your Stripe Dashboard.
I see that you print the token, println(tokenId), you're also going to want to send that to your Parse function and set card equal to the value of the token you just created.
class PaymentViewController: UIViewController, PTKViewDelegate {
#IBOutlet weak var saveBtn: UIButton!
var paymentView: PTKView = PTKView()
override func viewDidLoad() {
super.viewDidLoad()
var view : PTKView = PTKView(frame: CGRectMake(15,20,290,55))
paymentView = view
paymentView.delegate = self;
self.view.addSubview(self.paymentView)
saveBtn.enabled = false
}
func paymentView(view: PTKView!, withCard card: PTKCard!, isValid valid: Bool) {
if (valid) {
saveBtn.enabled = true
} else {
saveBtn.enabled = false
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func save(sender: AnyObject) {
var card: STPCard = STPCard()
card.number = self.paymentView.card.number
card.expMonth = self.paymentView.card.expMonth
card.expYear = self.paymentView.card.expYear
card.cvc = self.paymentView.card.cvc
STPAPIClient.sharedClient().createTokenWithCard(card, completion: { (token: STPToken?, error: NSError?) -> Void in
if (error != nil) {
println(error)
println("not working")
} else {
//println(tokenId)
var coin = token!.tokenId
PFCloud.callFunctionInBackground("hello", withParameters: nil) {
(response: AnyObject?, error: NSError?) -> Void in
let responseString = response as? String
println(responseString)
}
var name = PFUser.currentUser()?.username as String!
var customer = PFUser.currentUser()?.objectId as String!
PFCloud.callFunctionInBackground("createCustomer", withParameters: ["coin" : coin, "name": name, "customer": customer], block: { (success: AnyObject?, error: NSError?) -> Void in
if error != nil {
println("create customer not working")
}
})
var customerId = customer!
PFCloud.callFunctionInBackground("createCharge", withParameters: ["customerId" : customerId], block: { (success: AnyObject?, error: NSError?) -> Void in
if error != nil {
println("not working")
}
})
}
})
}
#IBAction func cancel(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
}
And My updated cloud code is here:
var Stripe = require('stripe');
Stripe.initialize('sk_test_xxxxxxxxxxxxxx');
Parse.Cloud.define("hello", function(request, response) {
response.success("Hello world!");
});
Parse.Cloud.define("createCustomer", function(request, response) {
                   Stripe.Customers.create({
 card: request.params.coin,
account_balance: -10*100,
metadata: {
name: request.params.name,
customer: request.params.customer, // e.g PFUser object ID
}
 }, {
 success: function(customer) {
response.success(customer.id);
},
error: function(error) {
response.error("Error:" +error);
 }
})
                   });
 
Parse.Cloud.define("createCharge", function(request, response) {
Stripe.Charges.create({
amount: 100 * 10, // $10 expressed in cents
currency: "usd",
//card: request.params.coin
customer: request.params.customerId
},{
success: function(httpResponse) {
response.success("Purchase made!");
},
error: function(httpResponse) {
response.error(httpResponse)
response.error("Uh oh, something went wrong");
}
});
});
Ultimately I want to have the create customer in a different viewController.swift file and charge on another section of the app, but for right now I am testing it with them in the same PaymentViewController.swift file