Android Native UI Components
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.
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:
- Create the ViewManager subclass.
- Implement the
createViewInstance
method - Expose view property setters using
@ReactProp
(or@ReactPropGroup
) annotation - Register the manager in
createViewManagers
of the applications package. - 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
class ReactImageManager(
private val callerContext: ReactApplicationContext
) : SimpleViewManager<ReactImageView>() {
override fun getName() = REACT_CLASS
companion object {
const val REACT_CLASS = "RCTImageView"
}
}
public class ReactImageManager extends SimpleViewManager<ReactImageView> {
public static final String REACT_CLASS = "RCTImageView";
ReactApplicationContext mCallerContext;
public ReactImageManager(ReactApplicationContext reactContext) {
mCallerContext = reactContext;
}
@Override
public String getName() {
return REACT_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
override fun createViewInstance(context: ThemedReactContext) =
ReactImageView(context, Fresco.newDraweeControllerBuilder(), null, callerContext)
@Override
public ReactImageView createViewInstance(ThemedReactContext context) {
return new ReactImageView(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
@ReactProp(name = "src")
fun setSrc(view: ReactImageView, sources: ReadableArray?) {
view.setSource(sources)
}
@ReactProp(name = "borderRadius", defaultFloat = 0f)
override fun setBorderRadius(view: ReactImageView, borderRadius: Float) {
view.setBorderRadius(borderRadius)
}
@ReactProp(name = ViewProps.RESIZE_MODE)
fun setResizeMode(view: ReactImageView, resizeMode: String?) {
view.setScaleType(ImageResizeMode.toScaleType(resizeMode))
}
@ReactProp(name = "src")
public void setSrc(ReactImageView view, @Nullable ReadableArray sources) {
view.setSource(sources);
}
@ReactProp(name = "borderRadius", defaultFloat = 0f)
public void setBorderRadius(ReactImageView view, float borderRadius) {
view.setBorderRadius(borderRadius);
}
@ReactProp(name = ViewProps.RESIZE_MODE)
public void setResizeMode(ReactImageView view, @Nullable String 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
override fun createViewManagers(
reactContext: ReactApplicationContext
) = listOf(ReactImageManager(reactContext))
@Override
public List<ViewManager> createViewManagers(
ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
new ReactImageManager(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).
import {requireNativeComponent} from 'react-native';
/**
* Composes `View`.
*
* - src: Array<{url: string}>
* - borderRadius: number
* - resizeMode: 'cover' | 'contain' | 'stretch'
*/
module.exports = requireNativeComponent('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.