Plama
Native components of android in React Native

There are many ready-made native user interface components that can be used in React Native applications. Some of them are parts of the native platform itself, while others are third-party libraries.

In many cases the use of React Native components will not be sufficient when developing a mobile application. Fortunately, we can easily integrate existing native components into our application.

In this article, I present a brief concept of integrating a ready-made native component for the Android platform with the React Native application and communication between parties using the so-called bridge. To fully understand the content, basic knowledge related to the development of mobile applications in React Native and native Android applications is required.

First steps

At the very beginning, we will create a new React Native application using the command:

react-native init bridge_android_test_app

Our goal will be to integrate the native material-wheel-view component with the React Native application. The component is used to select the element in the same way as the one known from the iOS platform.

Library repository: https://github.com/BlackBoxVision/material-wheel-view

The library we have chosen should be installed first – we will do it using Gradle. To do this very quickly, let’s add an android/app/build.gradle in the dependencies section, line:

implementation 'com.github.BlackBoxVision:material-wheel-view:v0.0.1'


Use of a native component

The integration between the technologies is in two stages. First, we have to deal with the native part and then the Javascript part.

Let’s create appropriate classes in Java, which will use our library. For ease of use, I recommend using Android Studio and not another editor that we use to work with React Native apps.

So we add a new file named RCTWheelPickerManager.java in android/app/src/main/java/com/bridge_android_test_app directory.

In order to render a component, we use the ViewManager class extension, namely SimpleViewManager, which adds simple and necessary properties of the components, such as size or background color.

public class RCTWheelPickerManager extends SimpleViewManager<WheelView> {

}

We have to add a name in the class, which we will use to communicate with React.

public static final String REACT_CLASS = "RCTWheelPicker";

And the public method that will return it:

@Override
public String getName() {
	return REACT_CLASS;
}

The next step will be to add the createViewInstance method, in which we declare the initial state of the component and return its instance, which we want to render.

In this method, we have to create a new instance of the component we want to display. To test it later, let’s also add a dataset and assign it to the picker. The last step is to return the object.

@Override
protected WheelView createViewInstance(ThemedReactContext reactContext) {
    final WheelView wheelView = new WheelView(reactContext);
    List<String> testList = new LinkedList<>();

    testList.add("Akanza 1");
    testList.add("Akanza 2");
    testList.add("Akanza 3");

    wheelView.setItems(testList);
    return wheelView;
}


Creating a package

The above prepared component must be registered as a native module. For this purpose we will create a new RCTWheelPickerPackage.java file in android/app/src/main/java/com/bridge_android_test_app directory with content:

package com.bridge_android_test_app;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class RCTWheelPickerPackage implements ReactPackage {

    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }

    public List<Class<? extends JavaScriptModule>> createJSModules() {
        return Collections.emptyList();
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Arrays.<ViewManager> asList(
            new RCTWheelPickerManager()
        );
    }
}

Then we have to add the module prepared in this way in the existing MainApplication.java file in the getPackages() method, adding a line:

packages.add(new RCTWheelPickerPackage());

After these steps, we can start using the component on the React Native side.

Use of the native module

In order to integrate on the Javascript side, we need to register the native component found. This process is very simple and does not require complicated operations.

Let’s create a WheelPickerAndroid.js file in the main application directory. In this file, we create a reference to the external, native module.

import {View, requireNativeComponent} from 'react-native';

const settings = {
     name: 'RCTWheelPicker',
     propTypes: {
          ...View.propTypes,
     },
};

export default requireNativeComponent('RCTWheelPicker', settings);

We can already use this component of JSX. Let’s replace the default one after creating App.js file with such content:

import React from 'react';
import {View, StyleSheet} from 'react-native';

const App = () => {
     return (
          <View style={styles.container}>
          </View>
     );
};

const styles = StyleSheet.create({
     flex: 1,
     width: '100%',
});

export default App;

We can import our previously created component:

import WheelPickerAndroid from './WheelPickerAndroid';

And render it:

<WheelPickerAndroid />

After turning on the application, nothing gets displayed. Why? Well, in the case of native components, React Native has some rigid rules that require us to specify the size of the component. That’s why we give the right style to our component by describing its size. After this change the App.js file looks like this:

import React from 'react';
import {View, StyleSheet} from 'react-native';
import WheelPickerAndroid from './WheelPickerAndroid';

const App = () => {
     return (
          <View style={styles.container}>
               <WheelPickerAndroid style={styles.wheelPicker} />
          </View>
     );
};

const styles = StyleSheet.create({
     container: {
          flex: 1,
          width: '100%',
     },
     wheelPicker: {
          width: '100%',
          height: 200,
     },
});

export default App;

Let’s check the results of our work now.

Display of the native component in React Native view.


React Native communication > Native component

The next step is to add the option to change the data presented in our component. In order to do so, we will provide appropriate information in the props of the JSX component:

<WheelPickerAndroid
     data={['test1', 'test2', 'test3', 'test4', 'test5']}
     style={styles.wheelPicker}
/>

Now in RCTWheelPickerManager.java file we have to add a method responsible for changing component properties.

@ReactProp(name = "data")
public void setData(WheelView wheelView, ReadableArray data) {
     List<String> dataList = new LinkedList<>();
     for (int i = 0; i < data.size() ; i++) {
          dataList.add(data.getString(i));
     }
     wheelView.setItems(dataList);
}

And remove unnecessary operations from the createWebInstance method

@Override
protected WheelView createViewInstance(ThemedReactContext reactContext) {
     final WheelView wheelView = new WheelView(reactContext);
     return wheelView;
}

Simple? Now we can test the component.

Testing communication React Native -> Native component.


Communication Native component > React native

Usually, in addition to transferring data to the native component, there is also a need for communication in the other direction. Let’s add support for such a process.

In RCTWheelPickerManager.java file in the createViewInstance method let’s add it:

wheelView.setLoopListener(new LoopScrollListener() {
     @Override
     public void onItemSelect(int item) {
          WritableMap event = Arguments.createMap();
          event.putInt("data", item);
          ((ReactContext) wheelView.getContext()).getJSModule(RCTEventEmitter.class).receiveEvent(
          wheelView.getId(),
          "topChange",
          event);
     }
});

And a new method that will give us an event to use on the Javascript site.

@Override
public Map getExportedCustomBubblingEventTypeConstants() {
     return MapBuilder.builder()
     .put(
     "topChange",
     MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "onChange"))
     )
     .build();
}

Now we add props with the function that will be performed during the picker movement, and the function itself:

const onItemSelected = event => {
     console.log(event.nativeEvent);
};

[...]

<WheelPickerAndroid
     onChange={onItemSelected}
     data={['test1', 'test2', 'test3', 'test4', 'test5']}
     style={styles.wheelPicker}
/>

Let’s test our work:

Communication testing Native component -> React Native.


Summary

In many cases, when working on React Native applications there is no need to go into native code. However, sometimes there are situations where it is desired or necessary. It is worth realizing, however, that this process is not difficult, and the effects that can be achieved in this way can be a positive change in the operation of applications.

Sources:

https://reactnative.dev/docs/native-modules-android
https://reactnative.dev/docs/communication-android
https://reactnative.dev/docs/native-components-android.html
https://github.com/BlackBoxVision/material-wheel-view

Categories
Other posts