Pass Java Object to React Native Component and Vice versa - android

I am including react native to my existing android application. I have one activity(say A) that is starting activity B. A activity sends some data in Bundle(consider some Custom Objects POJO) that B needs to render its view.
Now I want my Activity B to contain React Native View. How will the objects(POJO) come from Activity A be sent to React Native code**(JavaScipt Code)**?

I know some time has passed, but maybe someone is still looking for an answer.
If you want to send object from JavaScript to ReactNative (let's say as an method argument), which is unfortunately not mentioned in documentation:
let map = {
name: 'message1',
surname: 'message2',
}
NativeModules.BluetoothModule.sendObject(map);
And get it in android:
#ReactMethod
public void sendObject(ReadableMap readableMap){
Log.i(TAG, readableMap.toString());
//you can decode it here like:
String name = readableMap.getString("name");
//or just parse it as json
}
Now for the other way (from Java to Javascript) you can use either Callbacs, Promises or Events. This part is described in documentation here

From Java (Native Thread) to JavaScript (UI Thread):
#ReactMethod
public void sendObjectFromNative(Promise promise){
WritableMap map = Arguments.createMap();
map.putString("name", name);
map.putDouble("rotationDegrees", rotationDegrees);
map.putBoolean("isLandscape", isLandscape);
promise.resolve(map);
}
In JavaScript(UI Thread):
import { NativeModules } from 'react-native';
NativeModules.AwesomeModule.sendObjectFromNative().then(result=>console.log(result));
Above described how to do it vice versa

Related

Creating a queue for the evaluateJavascript function on a WebView

I have a hybrid app; some of my Activities use a WebView to display web content. The web app that I show in the WebView has a JS interface that lets me send commands to the web app to navigate different places or do other things.
For example, if I need my web app to navigate to the "user profile" page, I execute a command like:
class SomeActivity: AppCompatActivity {
...
webView.evaluateJavascript("navigateTo(\"userprofile\")")
...
}
Then, I get a response via the JS interface, and the app reacts accordingly.
I introduced a JS queue to improve performance, so the JS commands are executed sequentially. Instead of calling the evaluateJavascript() function directly on the WebView, I've created a custom WebView component with this JS queue set as a property.
class SomeActivity: AppCompatActivity {
...
webView.jsQueue.queueEvaluateJavascript("navigateTo(\"userprofile\")")
...
}
Now I would like to add a new behaviour on top of that, which is being able to pre-process the commands within the queue. What I mean by pre-processing is that if I ever queue commands of the same "type", like:
class SomeActivity: AppCompatActivity {
...
webView.jsQueue.queueEvaluateJavascript("navigateTo(\"userprofile\")")
webView.jsQueue.queueEvaluateJavascript("navigateTo(\"about-me\")")
webView.jsQueue.queueEvaluateJavascript("navigateTo(\"user-list\")")
...
}
What I would like to happen is that the queue is smart enough to ditch those two first "navigate" commands - "navigateTo(\"userprofile\")" and "navigateTo(\"about-me\")" - because I don't want my WebView to navigate to those two places just to finally navigate to "navigateTo(\"user-list\")".
The implementation of this JS queue looks like this:
class JsQueue(
private val webView: WebView,
private val scope: CoroutineScope
) {
init {
scope.launch {
for (jsScript in jsChannel) {
runJs(jsScript)
}
}
}
private val jsChannel = Channel<String>(BUFFERED)
fun queueEvaluateJavascript(script: String) {
runBlocking {
jsChannel.send(script)
}
}
suspend fun runJs(script: String) = suspendCoroutine<String> { cont ->
webView.evaluateJavascript(script) { result ->
cont.resume(result)
}
}
}
How can I pre-process the js commands in the Channel<String> so I
ditch duplicated js commands?
Also, sometimes my WebView will become invisible, and I want to pause the queue when that happens. I wonder if there's any way to programmatically pause a Channel?
Edit #1
Also, sometimes my WebView will become invisible, and I want to
pause the queue when that happens. I wonder if there's any way to programmatically pause a Channel?
I've tried using this PausableDispatcher implementation, and it seems to be doing the trick.
All of the command examples you gave follow a specific pattern: they're all functions. We can use this to our advantage!
First, let's create some terminology.
navigateTo() is a function (of course!).
And lets call the navigateTo part of the function a type.
An example of some types are:
console.log() => `console.log`,
gotoUrl(url) => `gotoUrl`.
I just made this terminology up. But it will help you understand the logic.
Now, what we need to do is look at the array of commands, understand it's type, and check if any other commands have the same type. If they do, they need to be excluded from the queue before the queue is executed.
Easy!
I've written a sample code that you can integrate with your script:
// Example array of commands for demonstration.
let commands = [
'navigateTo("a")',
'navigateTo("b")',
'navigateTo("c")',
];
/** A list of non-duplicate types*/
let types = [];
/** A list of non-duplicate commands */
let newCommands = [];
// Reverse the array because the most important commands start from the end of array.
for(let command of commands.reverse()){
let type = command.slice(0, command.indexOf('('));
// Determine if type already exists
let alreadyExists = false;
for(let commandType of types){
if(type == commandType){
alreadyExists = true;
break;
}
}
if(alreadyExists)
// Type already exists. Do not add to command list.
continue;
// This type & command does not exist.
// Update command & type arrays
types.push(type);
newCommands.push(command)
}
// New Commands
console.log("Commands: ", newCommands);
// If you want to keep same queue order without duplicates:
console.log("Commands: ", newCommands.reverse())
Let me know if I missed the mark answering this question. Otherwise, cheers to a great queue system!

How to trigger in-app navigation based on API response

This question relates to both ios (swift) and android.
I'm working on building the backend implementation for a banner system that our mobile apps need to integrate with. Some of the banners need to redirect a user to a section of the app when pressed.
What is the best practice for triggering route navigation based on data returned in an API call?
For example, a list of banner objects in JSON where a key references the page to navigate to - would deep linking apply here, does it make sense for the apps to create a mapping of routes that I can reference by passing a string?
I feel like there has to be a simple solution here but the mobile team I'm working with seems pretty adamant.
In my current project (iOS), we are using deep links to map navigation to a certain tab of our app. So you could send it through JSON and then post a Notification, which your main controller is listening to (TabBarController for example), and then select the wanted index to go to, or any other navigation logic you need.
// Registering the deeplink with DeepLinkKit pod (https://github.com/button/DeepLinkKit)
self.router?.register("a/created/path") { link in
if let link = link {
debugPrint("Router: \(String(describing: link.url))")
NotificationCenter.default.post(
name: Notification.Name(Configuration.App.kGoToSomewhere),
object: nil
)
}
}
Or just extracted from your JSON and then setup the notification
call { URL in
NotificationCenter.default.post(
name: Notification.Name("ANotification"),
object: nil
)
}
And then in your main controller
class MainTabBarController: UITabBarController {
func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(
self,
selector: #selector(selectTab),
name: Notification.Name("ANotificaton"),
object: nil
)
}
}
func selectTab() {
// Do your navigation logic here
}
I hope it helps!

Passing data from Native android to React Native

I am trying to establish a communication between the native part (android) with javascript part(react-native).Here is the following code from Java side:-
WritableMap params = Arguments.createMap();
params.putString("arg1","valueToJS");
getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("showResult",params);
And the Respective Javascript file to receive the event :-
DeviceEventEmitter.addListener('showResult', function (e: Event) {
// hadle the event triggered from java side
});
Now in addListner where do we add the params(Argument) in order to get the "valueToJS",Since i am not able find proper documentation about DeviceEventEmitter i am finding difficult to get the value of argument arg1.Any help is highly appreciated.

How can we call angular functions by native javascript in angular 4

In Angular 1.x , we can use angular.element(appElement).scope() to get $scope, and then use the $apply(), so that the native javascript can directly call angular functions or two-way binding. While with Angular 4, how could we call angular functions or two-way binding by native javascript or the android native.
For example :
The web is developed by angular 4, and it will be used in the android webview, so it needs an interaction with android, how can i handle the interaction?
I can think of many ways, but have never read anything in the manuals which clarifies as to the most Angular way.
Zones
You have to keep in mind that Angular uses zones and a change detection tree at the core of it's engine. So any outside access needs to happen in that context.
You have to run external code in the Angular zone:
zone.run(() => {
// do work here
});
If you make changes to any data that will directly or indirectly effect a template expression you run the risk of a change detection error. So a component needs to inject ChangeDetectorRef and call markForCheck.
So if you code runs inside a component but from outside of Angular. You need to do this:
zone.run(() => {
// do work here.
this.ChangeDetectorRef.markForCheck();
});
Still, that raises the question. How do I get to the component?
Accessing Angular
You have to bootstrap your Angular application so that it can be accessed.
When you bootstrap your Angular application the browser service returns a promise to the main module. That main module contains the injector and from there you can access any exported services.
platformBrowserDynamic()
.bootstrapModule(AppModule)
.then((modRef: NgModuleRef<AppModule>) => {
window.myGlobalService = modRef.injector.get(MyServiceClass);
});
That would place a service class as a global variable. You would have to create an API that forwards to Angular's zones.
#Injectable()
export class MyServiceClass {
public dataEmitter: Subject<any> = new Subject();
public constructor(private zone: NgZone) {}
public fooBar(data: any): any {
return this.zone.run(()=>{
// do work here
this.dataEmitter.next(data);
return "My response";
});
}
}
You can return a result from zone.run out of the service. The key is that Angular code is run in the correct zone.
Easy Component One-Way Binding
The easiest solution for one-way data binding is to just use the Event module for the DOM.
#Component({....})
export class MyComponent {
#HostListener('example',['$event'])
public onExample(event: Event) {
console.log(event.fooBar);
}
}
// else where in external JavaScript
var elem; // the DOM element with the component
var event = new Event('example', {fooBar: 'Hello from JS!'});
elem.dispatchEvent(elem);
I prefer this approach since Angular handles the event listener as it would any other kind of event (i.e. click events)
You can also do this the other way around. Have the component emit DOM events on it's ElementRef for external JavaScript to listen for. This makes the whole two-way communications more DOM standard.

How can I access the values passed into my app via a custom url scheme from within my C# code?

I'm working on a Xamarin Forms application. It has an Entry field for the visit code on the initial page. I need to extend its functionality so that the application will open when a custom url scheme myscheme://visitcode is encountered, and the Entry will have its Text value prepopulated with the value of visitcode.
I've had success with getting the application to launch.
I added my scheme to the info.plist file in my iOS project, and it properly launches the app when I click on my custom url scheme in Safari on an iPhone.
I added the following line above my MainActivity in my Droid project:
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable }, DataScheme = "myscheme")]
It properly launches the app when I click on my custom url scheme in Chrome on an Android phone.
The only remaining obstacle is to retrieve the value and populate the Entry field with it.
Can someone help me?
Note: I haven't tested this yet, so make sure your app lifecycle and the place where you handle the events are matching the Xamarin.Forms app lifecycle. That is, make sure Xamarin.Forms.Application.Current is not null. If it is, reshuffle your code to work around that.
For iOS, you have to override OpenUrl in your AppDelegate.cs:
public override bool OpenUrl (UIApplication application, NSUrl url, string sourceApplication, NSObject annotation);
and in Android, you handle that in your MainActivity.cs, in your OnCreate or any other method used as entry point:
var intent = Intent;
var uri = intent.Data;
That should allow you to retrieve the parameters of the url.
You then can retrieve the current Xamarin.Forms Application object, by doing:
var myapp = Xamarin.Forms.Application.Current as MyApplication;
Now, it's up to you to retrieve the right entry, or it's view model, or a service or whatever to connect the dots.
There is a component within the Xamarin store that handles this for you:
http://components.xamarin.com/view/rivets
This will remove the lengthy code requirements in building native implementations.

Categories

Resources