Using React Native as the View layer for Android apps
The two diverged roads 🌌
There is almost no modern app purely written in React Native (henceforth referred as RN). Also there are a lot of instances in the general RN development where you have to manipulate the native code to make things work.
Inversely, there are instances of apps developed natively, which have sprinkled React Native screens which are shared for both android/ios. For example, Zomato’s Android app had its “Chat” flow in RN while the app was natively developed, ref:
While native development is imperative for smoother performance, benefits of cross platform development ease can’t be denied. In this mess of native development (preferred for performance, hardware control etc) vs cross-platform development (preferred for developer ease, faster development), there exists a middle road.
The middle road (and the less travelled one) 🚶
This middle road is to replace the “View” layer of Android apps with React Native screens, for most screens. This does not necessarily mean that your android app needs to follow an architecture (see: MVP, MVVM etc) but it at least needs a separation of business and UI logic. The UI logic will just be replaced by RN views in this method.
Benefits of this solution
- Control lifecycle from android code, which is simpler and reliant
- Design UI with ease like a JS developer on RN
- Once the boilerplate setup is laid out its easy to write business logic in Java/Kotlin and UI in React Native
- Reuse UI screens code in case you have an iOS app as well
- Have the ability to make UI in native android for performance heavy screens (imagine a video call screen dealing with sound I/O hardware)
Communication is key 🔑
To make this work, efficient communication is essential on both sides. Firstly, setup the RN development environment for your native app. Use the official docs to do so: https://reactnative.dev/docs/environment-setup?platform=android&os=macos
And here’s how the communication happens
Android ➡️ React Native
- As “props”
While running the React application instance in the Android activity, update the startReactApplication to include a Bundle
Bundle initialProperties = new Bundle();
initialProperties.putString("module", "chat");
mReactRootView.startReactApplication(mReactInstanceManager, "BetafiApp", initialProperties);
const App = (props) => {
console.log(props.module)
return (
...
);
};
- Execute callback Android functions in RN
We use Native Modules for this (ChatModule in this case), which defines a function returnMessages. This can be executed in the JS code to get the messages. We’re using this to send a list of messages to RN UI.
@ReactMethod
public static void returnMessages(Callback successCallback) throws JSONException {
List<ChatMessage> chatMessages = getChatMessages();
WritableArray array = new WritableNativeArray();
for (ChatMessage cm : chatMessages) {
array.pushMap(getMapForChat(cm));
}
successCallback.invoke(array);
}
public static WritableMap getMapForChat (ChatMessage chatMessage){
WritableMap map = new WritableNativeMap();
map.putString("message", chatMessage.getMessage());
map.putString("user_name", chatMessage.getUserName());
return map;
}
NativeModules.ChatModule.returnMessages((array) => {
setLoading(false)
setMessages(array)
})
- Emit events from Android
This is similar to socket events and can be triggered from both codebases and received by the other one.
public static void pushMessageEvent(ChatMessage chatMessage){
if(mReactInstanceManager.getCurrentReactContext() != null) {
WritableMap chatMap = getMapForChat(chatMessage);
Log.d(TAG, "sendTextToRN: " + chatMap.toString());
mReactInstanceManager.getCurrentReactContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("message_event",chatMap);
}
}
DeviceEventEmitter.addListener('message_event', (e) => {
setMessages([...messages, e])
})
React Native ➡️ Android
- Execute Android functions with parameters on RN
We use Native Modules for this again, for the event of sending a message. The event is UI-triggered while the business logic has to run on Android
function send(text) {
if (text.trim() != '') {
//optimistic UI update
NativeModules.ChatModule.sendMessage(text)
setMessages([...messages, msg])
setText('')
}
}
@ReactMethod
public void sendMessage(String text) {
//HTTP call for sending text
pushChatMessage(text);
chatMessages.add(new ChatMessage(text));
}
Apart from these, the weirdest way to access data across platforms would be using some sort of mobile’s local storage (SharedPreferences, updating a local file etc) that is stored by say android and then fetched by RN interface
I’d like to know more examples of this setup that you’d have worked with or know of, kindly share the same in the comments :)