React Native – How to create Native UI components (Android) (1)?

Basic steps

Create native UI and register this into ReactPackage

1. Create a view

  • rn_view_example.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
  <TextView
      android:layout_width="300dp"
      android:layout_height="100dp"
      android:text="Test"/>
</LinearLayout>
class RNViewExample: LinearLayout {
    var myContext: ThemedReactContext = context as ThemedReactContext;

    constructor(context: ThemedReactContext): super(context) {
        init();
    }
    private fun init() {
        inflate(context, R.layout.rn_view_example, this)
    }
}

2. Create a view manager to use this view in React Native

class RNViewManagerExample : SimpleViewManager<RNViewExample>() {
    override fun getName() = "RNViewExample"

    override fun createViewInstance(reactContext: ThemedReactContext): RNViewExample {
        return RNViewExample(reactContext)
    }
}

3. Register View Manager into ReactPackage

class RNAircastPackage : ReactPackage {
    override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
        return emptyList()
    }

    override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
        return mutableListOf(
            RNViewManagerExample()
        )
    }
}

Create a view component in JS/TS

1. Create a view component

import React from 'react';
import { requireNativeComponent, ViewProps } from 'react-native';

export const NativeViewExampleRaw = requireNativeComponent<{}>(
  'RNViewExample'
);

type Props = ViewProps;

const NativeViewExample: React.FC<Props> = (props) => {
  return <NativeViewExampleRaw {...props} />;
};

export default NativeViewExample;

2. Load this React component

...
  <NativeViewExample style={styles.player} />
...

const styles = StyleSheet.create({
  player: {
    width: '100%',
    height: '50%',
    backgroundColor: '#ddd',
    marginTop: 15,
  },
});

Reference bridging-native-ui-components

How to expose a prop in view manager?

1. Expose a prop name url in native code

// ViewManager

class RNViewManagerExample : SimpleViewManager<RNViewExample>() {
    ...
    @ReactProp(name = "url")
    fun setUrl(view: RNViewExample, url: String) {
      view.url = url
    }
}
// ExampleView

class RNViewExample: LinearLayout {
  ...

  var url: String? = null
  set(value) {
    field = value
  }
  ...
}

2. Pass this prop in JS

const NativeViewExample: React.FC<Props> = () => {
  ...
  return <NativeViewExampleRaw
    style={style}
    url={url}
  />;
};

export default NativeViewExample;

How to expose methods in native view?

Expose these 2 methods

class RNViewExample: LinearLayout {
  ...
  fun play() {
    player.play()
  }

  fun stop() {
    player.stop()
  }
};

1. Create a command map in view manager

class RNViewManagerExample : SimpleViewManager<RNViewExample>() {
  ...
  override fun getCommandsMap(): Map<String, Int> {
    return ViewProps.Commands.toCommandsMap()
  }

  override fun receiveCommand(
    root: RNViewExample,
    commandId: String?,
    args: ReadableArray?
  ) {
    super.receiveCommand(root, commandId, args)
    // you can also get argument like this: args!!.getString(0)
    when (commandId) {
      ViewProps.Commands.PLAY.ordinal.toString() -> root.play()
      ViewProps.Commands.STOP.ordinal.toString() -> root.stop()
    }
  }
  ...
}
// ViewProps

import com.facebook.react.common.MapBuilder

class ViewProps {

  enum class Commands(val action: String) {
    PLAY("play"),
    STOP("stop");

    companion object {
      fun toCommandsMap(): Map<String, Int> {
        return values().associate { it.action to it.ordinal }
      }
    }
  }
}

2. Expose these methods in JS view component

type NativeProps = {
  style: ViewStyle;
  url: string;
};

export type NativeMethods = {
  play: () => void;
  stop: () => void;
};

///////////////////////////////////
///////////////////////////////////

export const NativeViewManager =
  requireNativeComponent<NativeProps>('RNViewExample');

const JSViewExample = forwardRef<NativeMethods, NativeProps>(
  ({ style, url }, ref) => {
    const nativeRef = useRef<typeof NativeViewManager | null>(null);

    useImperativeHandle(ref, () => ({
      play: () => {
        UIManager.dispatchViewManagerCommand(
          findNodeHandle(nativeRef.current),
          UIManager.getViewManagerConfig(
            'RNViewExample'
          ).Commands.play.toString(),
          [] // you can pass argument to method as well
        );
      },
      stop: () => {
        UIManager.dispatchViewManagerCommand(
          findNodeHandle(nativeRef.current),
          UIManager.getViewManagerConfig(
            'RNViewExample'
          ).Commands.stop.toString(),
          []
        );
      },
    }));

    return (
      <NativeViewManager
        style={style}
        url={url}
        ref={nativeRef as any}
      />
    );
  }
);

export default JSViewExample;

3. Render this JS View component and trigger native method

const MyComponent = () => {
  const playerRef = useRef<any>(null);
  const [play, setPlay] = useState(false);


  const playStop = () => {
    if (play) {
      playerRef.current.play();  // call native method
    } else {
      playerRef.current.stop();  // call native method
    }

    setPlay(!play);
  };

  return (
    <>
      <Button onPress={playStop}>{!play ? 'STOP' : 'PLAY'}</Button>
      <JSViewExample
        style={styles.player}
        url={'srt://xxxxxxxxxxx'}
        ref={playerRef}
      />
    </>
  );
};

const styles = StyleSheet.create({
  player: {
    width: '100%',
    height: '10%',
    backgroundColor: '#ddd',
    marginTop: 15,
  },
});

export default MyComponent;

Reference
pass-onpictureinpicturemodechanged-result-into-a-react-native-module
how-to-implement-a-react-native-ui-component-method-in-android

How to send event from native to JS?

Method 1

Native side

  • View
class RNViewExample(reactContext: ReactApplicationContext): LinearLayout {
  private val reactContext = reactContext

  fun play() {
    ...

    val event = Arguments.createMap()
    event.putString("data", "my name")

    reactContext.getJSModule(
      RCTEventEmitter::class.java
    ).receiveEvent(
      id,
      "play", // event name
      event
    )
  }
};
  • ViewManager
class RNViewManagerExample : SimpleViewManager<RNViewExample>() {
  ...
  override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> {
    return MapBuilder.of(
      "play",  // event name
      MapBuilder.of("registrationName", "onPlay")) // onPlay is the prop name
  }
}

JS Side

export const NativeViewManager =
  requireNativeComponent<NativeProps>('RNViewExample');

const JSViewExample = forwardRef<NativeMethods, NativeProps>(
  ({ style, url }, ref) => {

    ...

    return (
      <NativeViewManager
        ...
        onPlay={(event: { nativeEvent: { data: any } }) =>
          console.log(event.nativeEvent.data)
        }
      />
    );
  }
);

export default JSViewExample;

Q&A

  • How to pass reactContext into View?
    Pass “reactContext” into ViewManager at ReactPackage file
class RNAircastPackage : ReactPackage {
    ...
    
    override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
        return listOf(
          RNViewManagerExample(reactContext)
        ).toMutableList()
    }
}

Method 2

Native side

            val params = Arguments.createMap()
            params.putString("data", "Your data is here...")

            reactContext
                .getJSModule(RCTDeviceEventEmitter::class.java)
                .emit("onEventEmit", params)

JS Side

import { DeviceEventEmitter } from 'react-native';

...

  useEffect(() => {
    const onEventEmit = (event) => {
      console.log(event);
    };
    DeviceEventEmitter.addListener('onEventEmit', onEventEmit);
  }, []);

How to override Activity life cycle in this native UI

Extend the view with LifecycleEventListener

class RNViewExample(reactContext: ReactApplicationContext): LinearLayout, LifecycleEventListener {

    // ...

    override fun onHostResume() {

    }

    override fun onHostPause() {

    }

    override fun onHostDestroy() {
        stopPlaying()
    }
}

Be the first to comment

Leave a Reply

Your email address will not be published.


*