【React Native】Native UI Components で Android のネイティブのビューを表示する
こんにちは!アルダグラムでエンジニアをしている渡邊です!
React Native では Native UI Components で Android のネイティブのビューを表示させることができます。
今回この機能について掘り下げていきたいと思います!
今回のサンプルプロジェクトは こちら に公開しています。
※ Fabric については今回の記事では扱いません。
Native UI Components
Native UI Components でできることは大別して以下になります。
- Android のネイティブのビューを React Native で表示できる
- React Native からネイティブのビューにパラメータを渡すことができる
- ネイティブから React Native にイベントを伝えることができる
- React Native からネイティブのビューにイベントを伝えることができる
今回 RainbowView
という一定時間ごとに背景色が虹色に変化するビューを Android のネイティブのビューとして作成し、それを表示する例を元に順を追って見ていきます。
1. Android のネイティブのビューを React Native で表示
まずは Android 側のコードになります。
ネイティブのビューを表示するために ViewManager
を継承したクラスを用意する必要があります。
大抵の場合は以下の2つのいずれかを継承すれば問題ないはずです。
SimpleViewManager
ViewGroupManager
ViewGroupManager
は Android フレームワークの ViewGroup
を継承したビューを表示したい場合に使用します。 こちらを使うことで以下のコード例のように React Native において children
の要素を add して表示させることができます。
// NativeViewGroup は ViewGroupManager を使ったネイティブのビュー.
<NativeViewGroup
...
>
<View ... >
<View ... >
<View ... >
</NativeViewGroup>
上記のように children が必要ない場合は SimpleViewManager
を使います。
SimpleViewManager
も ViewGroupManager
も共通して、getName()
と createViewInstance()
メソッドをオーバーライドする必要があります。
今回 RainbowView
を表示するために SimpleViewManager
を使って実装します。
class RainbowViewManager : SimpleViewManager<RainbowView>() {
override fun getName(): String {
// React Native で参照する際の名前.
return "RainbowView"
}
override fun createViewInstance(reactContext: ThemedReactContext): RainbowView {
// 表示したいビューをインスタンス化する.
return RainbowView(reactContext)
}
}
ReactPackage
を継承したクラスの createViewManagers()
メソッドで RainbowViewManager
のインスタンスを登録します。
class MyAppPackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext) =
emptyList<NativeModule>()
override fun createViewManagers(reactContext: ReactApplicationContext) =
// RainbowViewManager を登録する.
mutableListOf(RainbowViewManager())
}
上記の MyAppPackage
は MainApplication
の ReactNativeHost
に登録します。
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
...
@Override
protected List<ReactPackage> getPackages() {
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new MyAppPackage()); // <- 追加
return packages;
}
...
};
...
これで表示する準備はできたので、React Native 側で表示させます。
まずは RainbowView
を使用するために requireNativeComponent
関数を使います。 引数には RainbowViewManager
の getName()
メソッドで返している文字列を指定します。
import { requireNativeComponent, ViewProps } from 'react-native'
type RainbowViewProps = Readonly<ViewProps>
export const RainbowView =
requireNativeComponent<RainbowViewProps>('RainbowView')
これで RainbowView
をコンポーネントとして使うことができるようになりました。
const App = () => <RainbowView style={styles.rainbow} />
const styles = StyleSheet.create({
rainbow: {
flex: 1
}
})
2. React Native からネイティブのビューにパラメータを渡す
先ほどの RainbowView
は1秒ごとに色が変わるビューとして実装しています。 色が変わる時間を React Native から設定させるようにしてみます。
Android 側で React Native からパラメータを受け取るためには、ViewManager
を継承したクラスで @ReactProp
アノテーションが付いたセッターメソッドを定義する必要があります。
今回の場合だと RainbowViewManager
に以下のようにセッターメソッドを用意します。
class RainbowViewManager : SimpleViewManager<RainbowView>() {
...
@ReactProp(name = "updateMillis")
fun setUpdateMillis(view: RainbowView, updateMillis: Int) {
view.updateMillis = updateMillis
}
}
@ReactProp
アノテーションには name
を必須で指定する必要があります。 ここで定義した名前は React Native からパラメータを渡す時の名前になります。
セッターメソッドにはルールがあり、第一引数には対象のビューのインスタンス(今回の例だと RainbowView
)、第二引数には受け取りたいパラメータを指定します。
第二引数に指定できるパラメータの型は以下のようになっています。
- Java の場合
-
boolean
、int
、float
、double
、String
、Boolean
、Integer
、ReadableArray
、ReadableMap
-
- Kotlin の場合
-
Boolean
、Int
、Float
、Double
、String
、ReadableArray
、ReadableMap
-
また @ReactProp
にはプリミティブ型に対して以下のようにデフォルト値を設定しておくことが可能です。
@ReactProp(name = "updateMillis", defaultInt = 1000)
続いて React Native 側を対応します。 先ほど定義した RainbowViewProps
に updateMillis
を追加します。
type RainbowViewProps = Readonly<{
updateMillis?: number
} & ViewProps>
export const RainbowView =
requireNativeComponent<RainbowViewProps>('RainbowView')
そして RainbowView
に対して updateMillis
の props を指定すればパラメータを渡すことができます。
const App = () => <RainbowView
updateMillis={2000}
style={styles.rainbow}
/>
3. ネイティブから React Native にイベントを伝える
今度はネイティブから React Native にイベントを伝えたい場合です。 例えば色が変わったタイミングで次の色を React Native に伝えるようにしたい場合を考えます。
Android 側では イベントの登録
と イベントの送信
の実装を行う必要があります。 イベントの登録
は ViewManager
の getExportedCustomDirectEventTypeConstants()
メソッドをオーバーライドします。
class RainbowViewManager : SimpleViewManager<RainbowView>() {
...
override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> {
return mutableMapOf(
"onColorChanged" // イベントを送信する際に指定する名前
to mutableMapOf(
"registrationName" to "onColorChanged") // React Native 側でイベントを参照する際に使う名前
)
}
}
上記の最初の方の onColorChanged
はイベントを送信する際に指定する名前になります。 registrationName
は固定で設定する必要があり、その後の onColorChanged
は React Native 側でイベントを参照する際の名前になります。
例では同じ名前にしていますが、もちろん異なる名前を設定しても問題はありません。
次に イベントの送信
ですが、これは RainbowView
で色が変わったタイミングで以下のメソッドを呼び出しています。
class RainbowView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
...
private fun sendColorChangedEventToJs(color: Int) {
val arguments = Arguments.createMap().apply {
// カラー値を16進数の文字列に変換
putString("color", "0x${color.toUInt().toString(16)}")
}
(context as ReactContext)
.getJSModule(RCTEventEmitter::class.java)
.receiveEvent(id, "onColorChanged", arguments)
}
}
上記の例のように WritableMap
に送りたいパラメータを設定してイベントを送信することが可能です。 ここではカラー値を16進数の文字列に変換して送っています。
React Native 側でイベントを受け取るために、RainbowViewProps
にコールバックを追加します。 コールバックの名前は前述の通り RainbowViewManager
の getExportedCustomDirectEventTypeConstants()
で設定した名前にする必要があります。
import { NativeSyntheticEvent, requireNativeComponent, ViewProps } from 'react-native'
type ColorChangedData = {
color: string
}
type RainbowViewProps = Readonly<{
updateMillis?: number
onColorChanged?: (e: NativeSyntheticEvent<ColorChangedData>) => void
} & ViewProps>
export const RainbowView =
requireNativeComponent<RainbowViewProps>('RainbowView')
追加したコールバックは以下のように使うことができます。
const App = () => <RainbowView
style={styles.rainbow}
onColorChanged={e => console.log('color changed: ', e.nativeEvent.color)}
/>
4. React Native からネイティブのビューにイベントを伝える
最後に React Native からネイティブのビューにイベントを伝えたい場合です。 RainbowView
では自動で一定時間ごとに色が変わりますが、色が変わるのを開始、終了できるように制御したい場合を考えます。
Android 側では ViewManager
を継承したクラスで getCommandsMap()
と receiveCommand()
メソッドをオーバーライドする必要があります。 RainbowViewManager
では以下のようになります。
class RainbowViewManager : SimpleViewManager<RainbowView>() {
...
override fun getCommandsMap(): Map<String, Int> {
// イベント名と数値をマッピングする
return mapOf("start" to COMMAND_START, "stop" to COMMAND_STOP)
}
override fun receiveCommand(view: RainbowView, commandId: String, args: ReadableArray?) {
super.receiveCommand(view, commandId, args)
// イベントを受け取った際の処理をここに書く.
when (commandId.toInt()) {
COMMAND_START -> {
view.startChangeColor()
}
COMMAND_STOP -> {
view.stopChangeColor()
}
}
}
companion object {
private const val COMMAND_START = 1
private const val COMMAND_STOP = 2
}
}
getCommandsMap()
ではイベント名と数値をマッピングして登録します。 ここでは start
と stop
というイベントを React Native から受け取れるように設定します。
次に receiveCommand()
ではイベントを受け取った際の処理を記述します。 React Native からパラメータも送ることができ、送ったパラメータは args
に格納されています。
続いて React Native 側です。 イベントを送るために以下のようにメソッドを定義します。
import { NativeSyntheticEvent, requireNativeComponent, UIManager, ViewProps } from 'react-native'
...
export const start = (viewId) =>
UIManager.dispatchViewManagerCommand(
viewId,
UIManager.RainbowView.Commands.start.toString(),
[] // ネイティブにパラメータを渡したい場合はこの配列に値を設定する
)
export const stop = (viewId) =>
UIManager.dispatchViewManagerCommand(
viewId,
UIManager.RainbowView.Commands.stop.toString(),
[]
)
上記の UIManager.dispatchViewManagerCommand()
の第二引数には、RainbowViewManager
の getCommandsMap()
で設定した start
と stop
のイベント名がそれぞれ使われています。
あとはこのメソッドを任意のタイミングで呼び出すようにします。
const App = () => {
const [started, setStarted] = useState(false)
const rainbowViewRef = useRef(null)
return (
<View>
<Button
...
title={started ? 'stop' : 'start'}
onPress={() => {
const viewId = findNodeHandle(rainbowViewRef.current)
if (started) {
stop(viewId)
setStarted(false)
} else {
start(viewId)
setStarted(true)
}
}}
/>
<RainbowView
ref={rainbowViewRef}
...
/>
</View>
)
}
viewId
を取得するために、ネイティブで作成したビュー(ここでは RainbowView
)に対して ref
を設定します。 そして findNodeHandle()
に ref.current
を指定することで viewId
を取得することができます。
そして先ほど定義した start
もしくは stop
関数に、viewId
を渡して呼び出すことでネイティブのビューに対してイベントを伝えることができるようになります。
その他 Tips
onDropViewInstance() メソッド
ViewManager
には onDropViewInstance()
メソッドがあり、このメソッドは View の後処理をしたい場合に便利です。
ネイティブで表示したビューが使われなくなるタイミングでこのメソッドが呼び出されるため、ビューの後処理が必要な場合にはこのメソッドをオーバーライドし、このメソッド内に処理を書くとよいです。
class RainbowViewManager : SimpleViewManager<RainbowView>() {
override fun onDropViewInstance(view: RainbowView) {
// ビューの後処理をここに記述する...
super.onDropViewInstance(view)
}
...
}
onAfterUpdateTransaction() メソッド
前述の @ReactProp
アノテーションを付与したセッターは ViewManager
内に複数定義することができます。 例えば複数のパラメータを受け取った後に何か処理したい場合には ViewManager
の onAfterUpdateTransaction()
メソッドが使えます。
@ReactProp
を使ってネイティブにパラメータが渡す場合、onAfterUpdateTransaction()
メソッドは全てのパラメータが渡された後に呼び出されます。
例えば以下のように全てのパラメータを受け取ってから初期化処理などを行いたい、というような場合に便利です。
class RainbowView ... {
var intValue: Int = 0
var stringValue: String = ""
private var isInitialized: Boolean = false
fun initialize() {
if (isInitialized) {
return
}
isInitialized = true
// intValue, stringValue を使った初期化処理をここに記述する...
}
}
class RainbowViewManager : SimpleViewManager<RainbowView>() {
@ReactProp(name = "intValue")
fun setIntValue(view: RainbowView, intValue: Int) {
view.intValue = intValue
}
@ReactProp(name = "stringValue")
fun setStringValue(view: RainbowView, stringValue: String) {
view.stringValue = stringValue
}
override fun onAfterUpdateTransaction(view: RainbowView) {
// 更新があった全てのパラメータの @ReactProp のメソッドが呼び出された後にこのメソッドが呼ばれる
super.onAfterUpdateTransaction(view)
view.initialize()
}
getExportedViewConstants()
ネイティブで定義した定数を React Native で使うために ViewManager
の getExportedViewConstants()
メソッドが使えます。 このメソッドでは React Native で参照する際の名前と定数値を Map として返すことで、React Native でこの定数値が参照できます。
class RainbowViewManager : SimpleViewManager<RainbowView>() {
...
override fun getExportedViewConstants(): Map<String, Any> {
return mapOf("DEFAULT_INT_VALUE" to 1, "DEFAULT_STRING_VALUE" to "hoge")
}
}
// ネイティブで定義した定数は UIManager.<ViewName>.Constants.<定数値名> で参照できる
export const DEFAULT_INT_VALUE =
UIManager.RainbowView.Constants.DEFAULT_INT_VALUE
export const DEFAULT_STRING_VALUE =
UIManager.RainbowView.Constants.DEFAULT_STRING_VALUE
参考
株式会社アルダグラムのTech Blogです。 世界中のノンデスクワーク業界における現場の生産性アップを実現する現場DXサービス「KANNA」を開発しています。 採用情報はこちら: herp.careers/v1/aldagram0508/
Discussion