Send closure from Objective-C to JavaScript in iOS React-Native - ios

Is it possible to construct a closure in objective-c and pass it to javascript where it can be invoked? The specific problem I am trying to solve is adding support for changing shipping methods and contacts in Apple Pay as part of the tipsi-stripe react-native module (something it doesn't do yet). This is basically what I have so far, but the callback in javascript gets 'null'.
- (void) paymentAuthorizationViewController:(PKPaymentAuthorizationViewController *)controller
didSelectShippingMethod: (PKShippingMethod *) shippingMethod
completion:(nonnull void (^)(PKPaymentAuthorizationStatus, NSArray<PKPaymentSummaryItem *> * _Nonnull))completion {
id callback = (void (^)(NSArray* summaryItems)) {
completion(PKPaymentAuthorizationStatusSuccess, nil, summaryItems);
}
[self sendEventWithName: "#ShippingMethodChanged" body:#{#"selectedMethod": #"someMethodDetails", #"callback": callback}];
}
In javascript, I have something like this:
import { NativeEventEmitter, NativeModules } from 'react-native'
const { TPSStripeManager } = NativeModules;
const stripeEventEmitter = new NativeEventEmitter(TPSStripeManager);
componentWillMount() {
this.stripeOnShippingMethodChanged = stripeEventEmitter.addListener(
'ShippingMethodChanged',
(method, callback) => {
// async compute some value then
let summaryItems = await computeItemsWithMethod(method);
callback(summaryItems);
}
);
}
componentWillUnmount() {
this.stripeOnShippingMethodChanged.remove();
}
I'm assuming I have to somehow wrap the Objective-C closure so javascript knows how to invoke it but I can't find anything. Any help appreciated!

It is not possible to do what I was attempting to do here, the react-native bindings only support simple types that can be encoded as a string. The solution that I ended up with based on patterns I found elsewhere is to store the completion as a property on the object, trigger an event that can be seen in js-land and provide another exported method that can be called to trigger the completion. Code is here:
https://github.com/tipsi/tipsi-stripe/pull/244/files

Related

How to add something like isClickable() in my appium native app tests

How to add something like isClickable() in my appium native app tests. I have written my tests, however they are very flaky and fail sometimes because it cannot find the element. I am thinking about making custom click and set value functions with the implicit wait times.
I thought about using isClickable() but the appium documentation says - Please note that isClickable works only in web and webviews, it doesn't work in mobile app native context.
Is there any other alternative i can use? can i use smartwait? if yes how can i implement that
Here is how i am defining home.screen.js
import AppScreen from './app.screen';
const SELECTORS = {
HOME_SCREEN: '~homeBarButton',
PRODUCTSEARCH_SCREEN: '~productSearchBarButton',
CUSTOMERSEARCH_SCREEN: '~customersBarButton',
STOREHUB_SCREEN: '~storeHubBarButton',
SETTING_ICON: '~SettingsIcon',
LOGOUT_BUTTON: '~settingsMainLogoutButton'
};
class HomeScreen extends AppScreen {
constructor () {
super(SELECTORS.HOME_SCREEN);
}
get homescreenButton () {
return $(SELECTORS.HOME_SCREEN);
}
get productsearchField () {
return $(SELECTORS.PRODUCTSEARCH_SCREEN);
}
get customersearchButon () {
return $(SELECTORS.CUSTOMERSEARCH_SCREEN);
}
get storehubButon () {
return $(SELECTORS.STOREHUB_SCREEN);
}
get settingIcon () {
return $(SELECTORS.SETTING_ICON);
}
get logoutButton () {
return $(SELECTORS.LOGOUT_BUTTON);
}
}
export default new HomeScreen();
And i am writing my test like this test.js:
import HomeScreen from '../screenobjects/home.screen';
import FormScreen from '../screenobjects/forms.screen';
import CommonPage from '../pageobjects/common.page';
describe('Sending item successfullt,', () => {
beforeEach(() => {
CommonPage.login()
});
afterEach(() => {
CommonPage.logout()
});
it('should be able to send the item to the mirror', () => {
driver.pause(3000)
HomeScreen.productsearchField.click();
driver.pause(3000)
HomeScreen.customersearchButon.click();
});
});
As you can see above, I have to add driver.pause otherwise my tests would fail because of button not clickable or typeable.
My suggestion is that you can get your elements attribute clickable and if its true keep doing your things
public boolean isClickable(String element) {
return androidDriver.findElementByAccessibilityId(element).getAttribute("clickable").equals("true");
}
You can use any method to find your element.
Best approach is to stop using implicit waits and do an explicit wait before each driver UI interaction.
You should do some reading on waitUntil / WebDriverWait (not sure if you have that in node.js implementation).
Then create functions for interacting with all types of elements in your app that perform an explicit wait before execution.
Pseudo code:
get clickButton (Selector element) {
waitUntil(clickable(element),...);
return $(driver.click(element));
}
Write generic methods for all type of elements in your app (button, textfield, dropdown...) and remove implicit waits from driver. You will see a big difference in your test stability.

what is alternative of for in in darjs?

I am looking for the alternative of javascript for in in dart:js?
for example:
if('addEventListener' in event) {
event.addEventListener(change);
}
I used is operator, but it's throwing an error in Safari becouse addEventListener does not exist in event.
if(event.addEventListener is Function) {
event.addEventListener(change);
}
Checking whether an object supports a specific method is not something you do in Dart. You should check that the object implements an interface which has that method.
In this example, you probably need:
if (event is EventTarget) {
event.addEventListener("change", change);
}
If you think that the object might support the function, but you don't actually know which interface it gets the function from, then you can do what you try here, using a dynamic lookup, but you need to catch the error you get if the function isn't there.
dynamic e = event; // if it isn't dynamic already.
Object addEventListener;
try {
addEventListener = e.addEventListener;
} on Error {
// ignore.
}
if (addEventListener is Function) {
addEventListener(...);
}

Is this loginRequired(f)() the way to handle login required functions in dart?

I am new to Dart programming. I am trying to figure out what is the proper way (what everyone will do) to handle/guard those functions which are login required. The following is my first trial:
$ vim login_sample.dart:
var isLoggedIn;
class LoginRequiredException implements Exception {
String cause;
LoginRequiredException(this.cause);
}
Function loginRequired(Function f) {
if (!isLoggedIn) {
throw new LoginRequiredException("Login is reuiqred.");
}
return f;
}
void secretPrint() {
print("This is a secret");
}
void main(List<String> args) {
if (args.length != 1) return null;
isLoggedIn = (args[0] == '1') ? true : false;
try {
loginRequired(secretPrint)();
} on LoginRequiredException {
print("Login is required!");
}
}
then, run it with $ dart login_sample.dart 1 and $ dart login_sample.dart 2.
I am wondering if this is the recommended way to guard login required functions or not.
Thank you very much for your help.
Edited:
My question is more about general programming skills in Dart than how to use a plugin. In python, I just need to add #login_required decorator in the front of the function to protect it. I am wondering if this decorator function way is recommended in dart or not.
PS: All firebase/google/twitter/facebook etc... are blocked in my country.
I like the functional approach. I'd only avoid using globals, you can wrap it in a Context so you can mock then for tests and use Futures as Monads: https://dartpad.dartlang.org/ac24a5659b893e8614f3c29a8006a6cc
Passing the function is not buying much value. In a typical larger Dart project using a framework there will be some way to guard at a higher level than a function - such as an entire page or component/widget.
If you do want to guard at a per-function level you first need to decide with it should be the function or the call site that decides what needs to be guarded. In your example it is the call site making the decision. After that decision you can implement a throwIfNotAuthenticated and add a call at either the definition or call site.
void throwIfNotAuthenticated() {
if (!userIsAuthenticated) {
throw new LoginRequiredException();
}
}
// Function decides authentication is required:
void secretPrint() {
throwIfNotAuthenticated();
print('This is a secret');
}
// Call site decides authentication is required:
void main() {
// do stuff...
throwIfNotAuthenticated();
anotherSecreteMethod();
}

Send javascript function to objective-C using JavascriptCore

I'm trying to send a Javascript function object to Objective-C via JavascriptCore, leveraging the JSExport protocol. I have a function declared in Objective-C, conforming to JSExport as follows:
(class View)
+ (void) newWithFunc:(id)func
{
NSLog(#" %# ", func);
}
After declaring this class, I try to call the function above with a Javascript function object as a parameter
JSValue *val;
val = [context evaluateScript:#"var mufunc = function() { self.value = 10; };"];
val = [context evaluateScript:#"mufunc;"];
NSLog(#" %#", val); //Prints as 'function() { self.value = 10; }', seems correct.
val = [context evaluateScript:#"var view = View.newWithFunc(mufunc);"];
When the last call is made, the parameter sent to my Objective-C method is of type 'NSDictionary', which doesn't seem very valuable if what I would like to do is call that function from Objective-C at a later point in time. Is this possible with JavascriptCore?
Please mark Tayschrenn's answer as correct. I don't know how he knew this or where it's documented, but this is what I figured out by trial and error:
- (void)newWithFunc: (JSValue*)func
{
[func callWithArguments:#[]]; // will invoke js func with no params
}
Declaring the parameter (id)func apparently causes the javascript-cocoa bridge to convert it to an NSDictionary (as you noticed), rendering it unusable as a callable JSValue.
This is definitely possible, but you need to change newWithFunc: to accept a JSValue* rather than plain id. The reason is that JSValue* is a special type for JavaScriptCore - it won't try to convert the JS value to its native equivalent but rather wrap it in a JSValue and pass it on.

How to pass objective-c function as a callback to C function?

I want to call a c function from objective-c and pass objective-c function as a callback
the problem is this function has a callback as parameter, so I have to pass objective-c function as a call back to c function
here is the header of the c function
struct mg_context *mg_start(const struct mg_callbacks *callbacks,
void *user_data,
const char **configuration_options);
here is where I try to call it
- (void)serverstarted
{
NSLog(#"server started");
}
- (IBAction)startserver:(id)sender {
NSLog(#"server should start");
const char *options[] =
{
"document_root", "www",
"listening_ports", "8080",
NULL
};
mg_start(serverstarted(), NULL, options);
}
I have tried several ways to do it and searched the web to just get a clue how to do it but with not luck
here is the library I am incuding in my code
https://github.com/valenok/mongoose
Your chief problem is the first parameter to mg_start(), which is described in the declaration as const struct mg_callbacks *callbacks. You are trying pass a pointer to a function. (Actually you are trying to pass the result of a call to that function, which is even further from the mark.) That isn't what it says: it says a pointer to a struct (in particular, an mg_callbacks struct).
The example code at https://github.com/valenok/mongoose/blob/master/examples/hello.c shows you how to configure this struct. You have to create the struct and put the pointer to the callback function inside it. Then you pass the address of that struct.
Other problems with your code: your callback function itself is all wrong:
- (void)serverstarted
{
NSLog(#"server started");
}
What's wanted here is a C function declared like this: int begin_request_handler(struct mg_connection *conn), that is, it takes as parameter a pointer to an mg_connection struct. Your serverstarted not only doesn't take that parameter, it isn't even a C function! It's an Objective-C method, a totally different animal. Your use of the term "Objective-C function" in your title and your question is misleading; C has functions, Objective-C has methods. No Objective-C is going to be used in the code you'll be writing here.
What I suggest you do here is to copy the hello.c example slavishly at first. Then modify the content / names of things slowly and bit by bit to evolve it to your own code. Of course learning C would also help, but you can probably get by just by copying carefully.
As matt already said, you cannot pass an Objective-C method as callback where a C function
is expected. Objective-C methods are special functions, in particular the receiver ("self")
is implicitly passed as first argument to the function.
Therefore, to use an Objective-C method as request handler, you need an (intermediate) C function as handler and you have to pass self to that function, using the user_data argument. The C function can then call the Objective-C method:
// This is the Objective-C request handler method:
- (int)beginRequest:(struct mg_connection *)conn
{
// Your request handler ...
return 1;
}
// This is the intermediate C function:
static int begin_request_handler(struct mg_connection *conn) {
const struct mg_request_info *request_info = mg_get_request_info(conn);
// Cast the "user_data" back to an instance pointer of your class:
YourClass *mySelf = (__bridge YourClass *)request_info->user_data;
// Call instance method:
return [mySelf beginRequest:conn];
}
- (IBAction)startserver:(id)sender
{
struct mg_callbacks callbacks;
memset(&callbacks, 0, sizeof(callbacks));
callbacks.begin_request = begin_request_handler;
const char *options[] =
{
"document_root", "www",
"listening_ports", "8080",
NULL
};
// Pass "self" as "user_data" argument:
mg_start(&callbacks, (__bridge void *)self, options);
}
Remarks:
If you don't use ARC (automatic reference counting) then you can omit the (__bridge ...)
casts.
You must ensure that the instance of your class ("self")
is not deallocated while the server is running. Otherwise the YourClass *mySelf
would be invalid when the request handler is called.

Resources