Skip to main content
Join us for React Conf on Oct 7-8. Learn more.
This is unreleased documentation for React Native Next version.
For up-to-date documentation, see the latest version (0.81).
Version: Next

Android Native UI Components

info

Native Module and Native Components are our stable technologies used by the legacy architecture. They will be deprecated in the future when the New Architecture will be stable. The New Architecture uses Turbo Native Module and Fabric Native Components to achieve similar results.

There are tons of native UI widgets out there ready to be used in the latest apps - some of them are part of the platform, others are available as third-party libraries, and still more might be in use in your very own portfolio. React Native has several of the most critical platform components already wrapped, like ScrollView and TextInput, but not all of them, and certainly not ones you might have written yourself for a previous app. Fortunately, we can wrap up these existing components for seamless integration with your React Native application.

Like the native module guide, this too is a more advanced guide that assumes you are somewhat familiar with Android SDK programming. This guide will show you how to build a native UI component, walking you through the implementation of a subset of the existing ImageView component available in the core React Native library.

info

You can also setup local library containing native component with one command. Read the guide to Local libraries setup for more details.

ImageView example

For this example we are going to walk through the implementation requirements to allow the use of ImageViews in JavaScript.

Native views are created and manipulated by extending ViewManager or more commonly SimpleViewManager . A SimpleViewManager is convenient in this case because it applies common properties such as background color, opacity, and Flexbox layout.

These subclasses are essentially singletons - only one instance of each is created by the bridge. They send native views to the NativeViewHierarchyManager, which delegates back to them to set and update the properties of the views as necessary. The ViewManagers are also typically the delegates for the views, sending events back to JavaScript via the bridge.

To send a view:

  1. Create the ViewManager subclass.
  2. Implement the createViewInstance method
  3. Expose view property setters using @ReactProp (or @ReactPropGroup) annotation
  4. Register the manager in createViewManagers of the applications package.
  5. Implement the JavaScript module

1. Create the ViewManager subclass

In this example we create view manager class ReactImageManager that extends SimpleViewManager of type ReactImageView. ReactImageView is the type of object managed by the manager, this will be the custom native view. Name returned by getName is used to reference the native view type from JavaScript.

  • Java
  • Kotlin
java
publicclassReactImageManagerextendsSimpleViewManager<ReactImageView>{

publicstaticfinalStringREACT_CLASS="RCTImageView";
ReactApplicationContext mCallerContext;

publicReactImageManager(ReactApplicationContext reactContext){
mCallerContext = reactContext;
}

@Override
publicStringgetName(){
returnREACT_CLASS;
}
}

2. Implement method createViewInstance

Views are created in the createViewInstance method, the view should initialize itself in its default state, any properties will be set via a follow up call to updateView.

  • Java
  • Kotlin
java
@Override
publicReactImageViewcreateViewInstance(ThemedReactContext context){
returnnewReactImageView(context,Fresco.newDraweeControllerBuilder(),null, mCallerContext);
}

3. Expose view property setters using @ReactProp (or @ReactPropGroup) annotation

Properties that are to be reflected in JavaScript needs to be exposed as setter method annotated with @ReactProp (or @ReactPropGroup). Setter method should take view to be updated (of the current view type) as a first argument and property value as a second argument. Setter should be public and not return a value (i.e. return type should be void in Java or Unit in Kotlin). Property type sent to JS is determined automatically based on the type of value argument of the setter. The following type of values are currently supported (in Java): boolean, int, float, double, String, Boolean, Integer, ReadableArray, ReadableMap. The corresponding types in Kotlin are Boolean, Int, Float, Double, String, ReadableArray, ReadableMap.

Annotation @ReactProp has one obligatory argument name of type String. Name assigned to the @ReactProp annotation linked to the setter method is used to reference the property on JS side.

Except from name, @ReactProp annotation may take following optional arguments: defaultBoolean, defaultInt, defaultFloat. Those arguments should be of the corresponding type (accordingly boolean, int, float in Java and Boolean, Int, Float in Kotlin) and the value provided will be passed to the setter method in case when the property that the setter is referencing has been removed from the component. Note that "default" values are only provided for primitive types, in case when setter is of some complex type, null will be provided as a default value in case when corresponding property gets removed.

Setter declaration requirements for methods annotated with @ReactPropGroup are different than for @ReactProp, please refer to the @ReactPropGroup annotation class docs for more information about it. IMPORTANT! in ReactJS updating the property value will result in setter method call. Note that one of the ways we can update component is by removing properties that have been set before. In that case setter method will be called as well to notify view manager that property has changed. In that case "default" value will be provided (for primitive types "default" value can be specified using defaultBoolean, defaultFloat, etc. arguments of @ReactProp annotation, for complex types setter will be called with value set to null).

  • Java
  • Kotlin
java
@ReactProp(name ="src")
publicvoidsetSrc(ReactImageView view,@NullableReadableArray sources){
view.setSource(sources);
}

@ReactProp(name ="borderRadius", defaultFloat =0f)
publicvoidsetBorderRadius(ReactImageView view,float borderRadius){
view.setBorderRadius(borderRadius);
}

@ReactProp(name =ViewProps.RESIZE_MODE)
publicvoidsetResizeMode(ReactImageView view,@NullableString resizeMode){
view.setScaleType(ImageResizeMode.toScaleType(resizeMode));
}

4. Register the ViewManager

The final step is to register the ViewManager to the application, this happens in a similar way to Native Modules, via the applications package member function createViewManagers.

  • Java
  • Kotlin
java
@Override
publicList<ViewManager>createViewManagers(
ReactApplicationContext reactContext){
returnArrays.<ViewManager>asList(
newReactImageManager(reactContext)
);
}

5. Implement the JavaScript module

The very final step is to create the JavaScript module that defines the interface layer between Java/Kotlin and JavaScript for the users of your new view. It is recommended for you to document the component interface in this module (e.g. using TypeScript, Flow, or plain old comments).

ImageView.tsx
import{requireNativeComponent}from'react-native';

/**
* Composes `View`.
*
* - src: Array<{url: string}>
* - borderRadius: number
* - resizeMode: 'cover' | 'contain' | 'stretch'
*/
exportdefaultrequireNativeComponent('RCTImageView');

The requireNativeComponent function takes the name of the native view. Note that if your component needs to do anything more sophisticated (e.g. custom event handling), you should wrap the native component in another React component. This is illustrated in the MyCustomView example below.

Events

So now we know how to expose native view components that we can control freely from JS, but how do we deal with events from the user, like pinch-zooms or panning? When a native event occurs the native code should issue an event to the JavaScript representation of the View, and the two views are linked with the value returned from the getId() method.

  • Java
  • Kotlin
java
classMyCustomViewextendsView{
...
publicvoidonReceiveNativeEvent(){
WritableMap event =Arguments.createMap();
event.putString("message","MyMessage");
ReactContext reactContext =(ReactContext)getContext();
reactContext
.getJSModule(RCTEventEmitter.class)
.receiveEvent(getId(),"topChange", event);
}
}

To map the topChange event name to the onChange callback prop in JavaScript, register it by overriding the getExportedCustomBubblingEventTypeConstants method in your ViewManager:

  • Java
  • Kotlin
java
publicclassReactImageManagerextendsSimpleViewManager<MyCustomView>{
...
publicMapgetExportedCustomBubblingEventTypeConstants(){
returnMapBuilder.builder().put(
"topChange",
MapBuilder.of(
"phasedRegistrationNames",
MapBuilder.of("bubbled","onChange")
)
).build();
}
}

This callback is invoked with the raw event, which we typically process in the wrapper component to make a simpler API:

MyCustomView.tsx
import{useCallback}from'react';
import{requireNativeComponent}from'react-native';

constRCTMyCustomView=requireNativeComponent('RCTMyCustomView');

exportdefaultfunctionMyCustomView(props:{
// ...
/**
* Callback that is called continuously when the user is dragging the map.
*/
onChangeMessage:(message:string)=>unknown;
}){
const onChange =useCallback(
event =>{
props.onChangeMessage?.(event.nativeEvent.message);
},
[props.onChangeMessage],
);

return<RCTMyCustomView{...props}onChange={props.onChange}/>;
}

Integration with an Android Fragment example

In order to integrate existing Native UI elements to your React Native app, you might need to use Android Fragments to give you a more granular control over your native component than returning a View from your ViewManager. You will need this if you want to add custom logic that is tied to your view with the help of lifecycle methods, such as onViewCreated, onPause, onResume. The following steps will show you how to do it:

1. Create an example custom view

First, let's create a CustomView class which extends FrameLayout (the content of this view can be any view that you'd like to render)

  • Java
  • Kotlin
CustomView.java
// replace with your package
packagecom.mypackage;

importandroid.content.Context;
importandroid.graphics.Color;
importandroid.widget.FrameLayout;
importandroid.widget.ImageView;
importandroid.widget.TextView;

importandroidx.annotation.NonNull;

publicclassCustomViewextendsFrameLayout{
publicCustomView(@NonNullContext context){
super(context);
// set padding and background color
this.setPadding(16,16,16,16);
this.setBackgroundColor(Color.parseColor("#5FD3F3"));

// add default text view
TextView text =newTextView(context);
text.setText("Welcome to Android Fragments with React Native.");
this.addView(text);
}
}

2. Create a Fragment

  • Java
  • Kotlin
MyFragment.java
// replace with your package
packagecom.mypackage;

importandroid.os.Bundle;
importandroid.view.LayoutInflater;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroidx.fragment.app.Fragment;

// replace with your view's import
importcom.mypackage.CustomView;

publicclassMyFragmentextendsFragment{
CustomView customView;

@Override
publicViewonCreateView(LayoutInflater inflater,ViewGroup parent,Bundle savedInstanceState){
super.onCreateView(inflater, parent, savedInstanceState);
customView =newCustomView(this.getContext());
return customView;// this CustomView could be any view that you want to render
}

@Override
publicvoidonViewCreated(View view,Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
// do any logic that should happen in an `onCreate` method, e.g:
// customView.onCreate(savedInstanceState);
}

@Override
publicvoidonPause(){
super.onPause();
// do any logic that should happen in an `onPause` method
// e.g.: customView.onPause();
}

@Override
publicvoidonResume(){
super.onResume();
// do any logic that should happen in an `onResume` method
// e.g.: customView.onResume();
}

@Override
publicvoidonDestroy(){
super.onDestroy();
// do any logic that should happen in an `onDestroy` method
// e.g.: customView.onDestroy();
}
}

3. Create the ViewManager subclass

  • Java
  • Kotlin
MyViewManager.java
// replace with your package
packagecom.mypackage;

importandroid.view.Choreographer;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.widget.FrameLayout;

importandroidx.annotation.NonNull;
importandroidx.annotation.Nullable;
importandroidx.fragment.app.FragmentActivity;

importcom.facebook.react.bridge.ReactApplicationContext;
importcom.facebook.react.bridge.ReadableArray;
importcom.facebook.react.common.MapBuilder;
importcom.facebook.react.uimanager.annotations.ReactProp;
importcom.facebook.react.uimanager.annotations.ReactPropGroup;
importcom.facebook.react.uimanager.ViewGroupManager;
importcom.facebook.react.uimanager.ThemedReactContext;

importjava.util.Map;

publicclassMyViewManagerextendsViewGroupManager<FrameLayout>{

publicstaticfinalStringREACT_CLASS="MyViewManager";
publicfinalintCOMMAND_CREATE=1;
privateint propWidth;
privateint propHeight;

ReactApplicationContext reactContext;

publicMyViewManager(ReactApplicationContext reactContext){
this.reactContext = reactContext;
}

@Override
publicStringgetName(){
returnREACT_CLASS;
}

/**
* Return a FrameLayout which will later hold the Fragment
*/
@Override
publicFrameLayoutcreateViewInstance(ThemedReactContext reactContext){
returnnewFrameLayout(reactContext);
}

/**
* Map the "create" command to an integer
*/
@Nullable
@Override
publicMap<String,Integer>getCommandsMap(){
returnMapBuilder.of("create",COMMAND_CREATE);
}

/**
* Handle "create" command (called from JS) and call createFragment method
*/
@Override
publicvoidreceiveCommand(
@NonNullFrameLayout root,
String commandId,
@NullableReadableArray args
){
super.receiveCommand(root, commandId, args);
int reactNativeViewId = args.getInt(0);
int commandIdInt =Integer.parseInt(commandId);

switch(commandIdInt){
caseCOMMAND_CREATE:
createFragment(root, reactNativeViewId);
break;
default:{}
}
}

@ReactPropGroup(names ={"width","height"}, customType ="Style")
publicvoidsetStyle(FrameLayout view,int index,Integer value){
if(index ==0){
propWidth = value;
}

if(index ==1){
propHeight = value;
}
}

/**
* Replace your React Native view with a custom fragment
*/
publicvoidcreateFragment(FrameLayout root,int reactNativeViewId){
ViewGroup parentView =(ViewGroup) root.findViewById(reactNativeViewId);
setupLayout(parentView);

finalMyFragment myFragment =newMyFragment();
FragmentActivity activity =(FragmentActivity) reactContext.getCurrentActivity();
activity.getSupportFragmentManager()
.beginTransaction()
.replace(reactNativeViewId, myFragment,String.valueOf(reactNativeViewId))
.commit();
}

publicvoidsetupLayout(View view){
Choreographer.getInstance().postFrameCallback(newChoreographer.FrameCallback(){
@Override
publicvoiddoFrame(long frameTimeNanos){
manuallyLayoutChildren(view);
view.getViewTreeObserver().dispatchOnGlobalLayout();
Choreographer.getInstance().postFrameCallback(this);
}
});
}

/**
* Layout all children properly
*/
publicvoidmanuallyLayoutChildren(View view){
// propWidth and propHeight coming from react-native props
int width = propWidth;
int height = propHeight;

view.measure(
View.MeasureSpec.makeMeasureSpec(width,View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(height,View.MeasureSpec.EXACTLY));

view.layout(0,0, width, height);
}
}

4. Register the ViewManager

  • Java
  • Kotlin
MyPackage.java
// replace with your package
packagecom.mypackage;

importcom.facebook.react.ReactPackage;
importcom.facebook.react.bridge.ReactApplicationContext;
importcom.facebook.react.uimanager.ViewManager;

importjava.util.Arrays;
importjava.util.List;

publicclassMyPackageimplementsReactPackage{

@Override
publicList<ViewManager>createViewManagers(ReactApplicationContext reactContext){
returnArrays.<ViewManager>asList(
newMyViewManager(reactContext)
);
}

}

5. Register the Package

  • Java
  • Kotlin
MainApplication.java
@Override
protectedList<ReactPackage>getPackages(){
List<ReactPackage> packages =newPackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
packages.add(newMyAppPackage());
return packages;
}

6. Implement the JavaScript module

I. Start with custom View manager:

MyViewManager.tsx
import{requireNativeComponent}from'react-native';

exportconstMyViewManager=
requireNativeComponent('MyViewManager');

II. Then implement custom View calling the create method:

MyView.tsx
importReact,{useEffect, useRef}from'react';
import{
PixelRatio,
UIManager,
findNodeHandle,
}from'react-native';

import{MyViewManager}from'./my-view-manager';

constcreateFragment= viewId =>
UIManager.dispatchViewManagerCommand(
viewId,
// we are calling the 'create' command
UIManager.MyViewManager.Commands.create.toString(),
[viewId],
);

exportconstMyView=()=>{
const ref =useRef(null);

useEffect(()=>{
const viewId =findNodeHandle(ref.current);
createFragment(viewId);
},[]);

return(
<MyViewManager
style={{
// converts dpi to px, provide desired height
height:PixelRatio.getPixelSizeForLayoutSize(200),
// converts dpi to px, provide desired width
width:PixelRatio.getPixelSizeForLayoutSize(200),
}}
ref={ref}
/>
);
};

If you want to expose property setters using @ReactProp (or @ReactPropGroup) annotation see the ImageView example above.

AltStyle によって変換されたページ (->オリジナル) /