When I click a button in native UI (probably from a fragment container) , I wanna invoke a method from JavaScript, let's call it just like 'jsMethod' or other else. However, I have never handled this situation before.
I am looking up some relative codes from Facebook's document on website and can not find out the key to the problem so far. Anyone give me some suggestions?
To call a JavaScript method from Java, do something like this:
ReactInstanceManager reactInstanceManager = reactNativeHost.getReactInstanceManager();
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
CatalystInstance catalystInstance = reactContext.getCatalystInstance();
WritableNativeArray params = new WritableNativeArray();
params.pushString("Message to show using nameOfJsMethod");
catalystInstance.callFunction("JavaScriptVisibleToJava", "nameOfJsMethod", params);
The JavaScript method you want to call must be defined and made visible to Java:
import BatchedBridge from "react-native/Libraries/BatchedBridge/BatchedBridge";
export class ExposedToJava {
nameOfJsMethod(message) {
alert(message);
}
}
const exposedToJava = new ExposedToJava();
BatchedBridge.registerCallableModule("JavaScriptVisibleToJava", exposedToJava);
This sample on Github includes a working demo.
EDIT I'm told this approach is not officially documented. If your scenario allows, consider taking the advice of #ufxmeng (in the comment to the OP) instead.
Related
I want to navigate between pages be starting and finishing new activity for any new page and show this page in own activity. How to achieve this behavior?
The answer is Yes,you could look at Native Forms.
For ios:
in ios project AppDelegate
public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{
Forms.Init();
...
UIViewController mainPage = new NotesPage().CreateViewController();
mainPage.Title = "Notes";
_navigation = new AppNavigationController(mainPage);
_window.RootViewController = _navigation;
_window.MakeKeyAndVisible();
return true;
}
For Android:
in Android project your activity ,it convert your page to a fragment,and then you could fill it in your activity:
Android.Support.V4.App.Fragment mainPage = new NotesPage().CreateSupportFragment(this);
SupportFragmentManager
.BeginTransaction()
.Replace(Resource.Id.fragment_frame_layout, mainPage)
.Commit();
On Android Xamarin.Forms navigate among pages by using Fragments.
If you don't like it you may fork Xamarin.Forms and change the code to work the way you want it to work: https://github.com/xamarin/Xamarin.Forms
This will make some packages incompatible with your fork of Xamarin.Forms, which you may need to take under consideration if you intend to use them.
Also if you consider the required time and knowledge this is non-trivial. It is highly unlikely that someone will do that job instead of you and provide you with the plug-in solution.
For tests I use Espresso and Barista
I have a test in which I need to open another screen by pressing a button. How can I check if this screen opens? Did the screen I need open?
Can I somehow check the chain of screens? To understand that the screens open in the order I need?
If someone throws links to good tutorials on UI tests in Android, I will be very grateful.
An easy solution would be to just check for an element of the new screen to be shown like this:
onView(withId(R.id.id_of_element_in_your_new_screen)).check(matches(isDisplayed()))
If you really want to check out for the current activity that is shown, you could try something like this:
Gather the current activity via InstrumentationRegistry and check for the activity in stage RESUMED.
fun getTopActivity(): Activity? {
InstrumentationRegistry.getInstrumentation().runOnMainSync {
val resumedActivities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED)
if (resumedActivities.iterator().hasNext()) {
resumedActivities.iterator().next()?.let {
activity = it
}
}
}
return activity
}
You could then check this in a test like this:
#Test
fun checkForActivity() {
val currentActivity = getTopActivity()
assertTrue(currentActivity?.javaClass == YourActivityToCheckAgainst::class.java)
}
I personally use intended(hasComponent(YourActivityToCheckAgainst::class.java.name)), which checks if the last intent was done with a desired activity, set as its component.
I also wrote an extensive Android UI testing tutorial using Espresso + Barista libraries.
I create UI. It works well. But i don't know how to send data from Java to JS? In react native moduls I can use callback and activete this onClick events. But in UI i don't know.
More about what I need.
I have android component. Send it to JS this way createViewInstance(ThemedReactContext reactContext)
and users something change inside component. And these changes I see in java class. I need to send these changes to the JS when JS ask for them.
You know how to send data from UI component to JS? Please give me some example.
Thank you.
Building on gre's answer that put me on the right track, but still left me with a lot of work to do, I'll try to explain some of the missing details.
There are 2 basic ways to do this:
use an existing event type
create & use a custom event type
Existing Events
As gre mentioned, the React Native docs explain this in the Native UI Components section of Events.
They show how to send the event using the following code:
WritableMap event = Arguments.createMap();
event.putString("message", "MyMessage");
ReactContext reactContext = (ReactContext)getContext();
reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(
getId(), "topChange", event);
With this explanation:
The event name topChange maps to the onChange callback prop in
JavaScript (mappings are in UIManagerModuleConstants.java).
The actual definition from UIManagerModuleConstants.java looks like this:
"topChange",
MapBuilder.of(
"phasedRegistrationNames",
MapBuilder.of(
"bubbled", "onChange",
"captured", "onChangeCapture")))
i.e. by using the event topChange in the Android code, you can intercept it in JS with either onChange or onChangeCapture due to this mapping.
You can find many other existing events declared in there to piggy-back on.
There are also "direct" events declared in that may be more useful:
"topLayout",
MapBuilder.of("registrationName", "onLayout")
i.e. Android event topLayout maps to JS event callback onLayout
(I do not understand the difference between the "bubbled" vs "captured" vs "direct" event types)
To receive the event in JS, take note of the 3 places _onChange() is referenced in the docs:
create the callback method: _onChange(event: Event) {}
bind it in the constructor: this._onChange = this._onChange.bind(this);
pass it when creating your custom view: return <RCTMyCustomView {...this.props} onChange={this._onChange} />;
Custom Events
Custom events need to be declared to the system before they can be used, mapping Android events to JS events in a similar way to how they are done by React Native above.
This is done by overriding one of the following methods in your ViewManager:
getExportedCustomBubblingEventTypeConstants()
getExportedCustomDirectEventTypeConstants()
The javadoc is very helpful in showing how the mapping should work, but I found it useful to reference the React Native code in UIManagerModuleConstants mentioned above.
Once it is declared there, you use it as you would any other "existing" event.
Example Custom Event implementation
I wanted to send the click event from Android up to JS, and to call it onClick. I chose to use a "direct" event for this. I also chose to use the same name in Android and in JS - this is not necessary.
3 files need to be modified:
ViewManager class
This code maps the Android event name "onClick" to the JS function "onClick".
/**
* This method maps the sending of the "onClick" event to the JS "onClick" function.
*/
#Nullable #Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return MapBuilder.<String, Object>builder()
.put("onClick",
MapBuilder.of("registrationName", "onClick"))
.build();
}
View class
This code sends an event to the JS when the view is clicked. The name used here is the Android event name, which will map to whatever you set above in the ViewManager class.
// trigger the onPress JS callback
super.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
final Context context = getContext();
if (context instanceof ReactContext) {
((ReactContext) context).getJSModule(RCTEventEmitter.class)
.receiveEvent(getId(),
"onClick", null);
}
}
});
The instanceof check is there because this view is sometimes referenced from native code, outside the React context.
React Native JS component
Bind in constructor:
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
Declare actual callback function:
onClick(event: Event) {
// do something
}
Make sure to set the callback when rendering your view:
render() {
return <NativeView onClick={this.onClick} />;
}
All very simple when laid out like this, but the documentation of the finer details is scattered around the web.
http://facebook.github.io/react-native/docs/native-components-android.html#events
^this shows how you can trigger events from Java side to JS on an UI component.
but for "custom events" (events that are not pre-defined likes onLoad, onScroll, etc..) you will also need to override getExportedCustomDirectEventTypeConstants.
Here is an example, triggering onGLProgress for gl-react-native:
(1) define the custom event mapping: https://github.com/ProjectSeptemberInc/gl-react-native/blob/7d6e83de5a8280d06d47234fe756aa3050e9b9a1/android/src/main/java/com/projectseptember/RNGL/GLCanvasManager.java#L115-L116
(2) dispatch the event from Java to JS: https://github.com/ProjectSeptemberInc/gl-react-native/blob/0f64a63fec2281e9d6d3641b9061b771a44fcac8/android/src/main/java/com/projectseptember/RNGL/GLCanvas.java#L839-L849
(3) and on the JS side, you can give a onGLProgress prop callback.
Specifically, I'm seeing this issue on an Android tablet, but I'm told it's with ALL mobile devices -- iPhones, Nexus tablets, etc.
But I have the common problem of change events not firing. Here's the function code that has the click events assigned:
function do_this(with_this_data)
{
var that = this;
this.with_this_data = with_this_data;
this.period = 900;
this.updateHours();
$('#date').change(function() {
that.updateHours();
});
$('#time_hour').change(function() {
that.updateMinutes();
});
// extra irrelevant data trimmed out
}
Now...one fix that should work is to move those .change() statements into a $(document).ready block -- but the problem is, if I do, then i get all sorts of undefined variable issues and stuff....all of the "update" functions are within said $(document).ready block and defined by names like "FutureStuff.prototype.updateMinutes."
What are my options???
Mifeet, again, I appreciate your feedback; I know you weren't able to fully get me up and running, but I'm still thankful.
But anyway, I solved the issue...it meant that basically I had to rewrite a new version of the JS code and stick it in an "if this chap is using a mobile browser" block. So yeah, one huge block of code for desktop users, another for mobile...but it works. :) And it was a pain in the hiney.
I have the following code which gets call in my main activity's onCreate method
public static ErrorReporter getInstance(){
if (instance == null){
instance = new ErrorReporter();
}
return instance;
}
Only on android 1.5 calling the above method causes java.lang.VerifyError. I am not able to figure out why this is happening. Any hints on how to solve this problem
Simply do a build on 1.5 and you'll see where is the culprit...
I got exactly the same problem when i try to set the listadatper for a listview :)
check this
private void setResultListListAdapter() {
mListAdapter_ = new ListAdapter(mContext_,
R.layout.dsg_detailed_list_row, mLstStops_);
setListAdapter(mListAdapter_);
}
gets a VerifyError before mListAdapter_ gets initialized.. so something with this...
new ListAdapter(mContext_,
R.layout.dsg_detailed_list_row, mLstStops_);
but there's nothing which is just available in 1.5 :=//
strange thing...
also in 2 other classes this code works just fine... :=)
hope someone knowes more, thanks a lot!
(everything initialized, everything checked...setListAdapter never gets called)
SOLUTION (for me)
it really was a method which wasn't supported in Android 1.5
mConvertView_.setTag(uniqueIntID, ViewHolder);
ViewHolder is a static class, instead of using normal View.gettag(),
because of different layouts i was using the above method.. so :=)
the second is supported, View.getTag()
I was using a function in ErrorReporter class which was not available in 1.5. Used reflection to take care of the unavailable function and the error is gone.