React NativeからAndroid呼び出し(Java)

やりたいこと

React NativeからAndroidの画面を呼び出したいです。

実現したいこと

React Native(JavaScript)の画面からAndroidJava)の画面に遷移したいです。

ネイティブモジュールを使えばできそうですが、Androidアプリの開発経験すらない初心者ですので、やってみます。 reactnative.dev

全体の流れ

経験者の方には「ネイティブモジュールを使って画面遷移なんて簡単じゃん」と思われそうですが、 当方React NativeはおろかAndroidアプリの開発経験すらないので、あまりイメージが湧きません。 そこで、 Ⅰ. ネイティブモジュールを呼び出してみて Ⅱ. ネイティブモジュールで画面遷移をする という流れで進めていきたいと思います。

以下はReact Nativeのプロジェクトが作成されている前提ですので、プロジェクトがない場合はこちら等をご参照ください。 teisyoku-tabetai.hatenablog.com

Ⅰ. ネイティブモジュール呼び出し

ネイティブモジュールとして呼び出すネイティブコードは、Androidの場合android/app/src/main/java/com/プロジェクト名/ 配下にあります。]

初期の時点で、MainActivity.javaファイルとMainApplication.javaがありますが、それぞれの意味はざっくり以下です。

  • MainActivity:React Nativeアプリケーションの起動を制御。アプリケーションのエントリーポイントとなるReactコンポーネントを指定する役割を果たす。
  • MainApplication:React NativeプロジェクトのAndroidアプリケーションレベルの設定と初期化を行う。

Androidプロジェクトでアプリケーションを作成する場合、MainActivityは起動する画面になりますが、React Nativeプロジェクトの場合はReact Nativeの画面に関係、つまりAndroidとして別の画面を作るなら別のActivityに書くよ、ということが伝わればここではOKです。

  1. App.tsxの作成 まず、以下のようなApp.tsxを作成
import React from 'react';
import { Text, Button, NativeModules, View, StyleSheet } from 'react-native';

const { AndroidScreen } = NativeModules; //ネイティブモジュール読み込み

export default function App() {

  const onPressExampleButton = () => {
    AndroidScreen.getAnimalName((animal) => console.log(animal));
  };

  return (
    <View style={styles.container}>
      <Text style={styles.text}>React Nativeの画面です</Text>
      <Button  title="かわいい動物の名前" onPress={onPressExampleButton} />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  text: {
    fontSize: 20,
    textAlign: 'center',
    margin: 30,
  },
})

ここで「かわいい動物の名前」ボタンを押すと、JavaScriptのコンソールでかわいい動物の名前が出力されるようにしたいです。 ネイティブブリッジを使うため、Javaにコードを書きます。

  • AndroidScreenPackage
package com.integratedandroidscreen;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

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

public class AndroidScreenPackage implements ReactPackage {

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

    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new AndroidScreenModule(reactContext));
        return modules;
    }
}
  • MainApplication.java
        protected List<ReactPackage> getPackages() {
            ...
            packages.add(new com.integratedandroidscreen.AndroidScreenPackage()); // ここだけ追加
            ...
          return packages;
        }

ビルドして実行すると、 ネイティブコードで記述したgetAnimalNameメソッドが呼ぶことができました。 かなり簡単なことしかしていませんが、以上がReact NativeからJavaコードを呼ぶ流れです。

Ⅱ. Androidの画面へ遷移

App.tsx

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

const { AndroidScreen } = NativeModules;


export default function App() {

  const onPressExampleButton = () => {
    AndroidScreen.moveAndroidScreen();
  };

  return (
    <View style={styles.container}>
      <Text style={styles.text}>React Nativeの画面です</Text>
      <Button  title="Androidの画面へ遷移" onPress={onPressExampleButton} />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  text: {
    fontSize: 20,
    textAlign: 'center',
    margin: 30,
  },
})

画面用のメソッドを追加 - AndroidScreenActivity.java

package com.integratedandroidscreen;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

public class AndroidScreenActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_androidscreen);
    }
}
package com.integratedandroidscreen;

import java.util.HashMap;
import java.util.Map;

import android.content.Intent;
import androidx.annotation.NonNull;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.modules.core.DeviceEventManagerModule;

public class AndroidScreenModule extends ReactContextBaseJavaModule {

    private static final String REACT_CLASS = "AndroidScreen";
    private final ReactApplicationContext reactApplicationContext;

    public AndroidScreenModule(ReactApplicationContext reactApplicationContext) {
        super(reactApplicationContext);
        this.reactApplicationContext = reactApplicationContext;
    }

    @Override
    public String getName() {
        return "AndroidScreen";
    }

    private String animal = "panda"; // かわいい動物

    @ReactMethod
    public void getAnimalName(Callback callback) {
        callback.invoke(animal);
    }
}
  • AndroidScreenModule
// 以下メソッドを追記
@ReactMethod
    public void moveAndroidScreen() throws InterruptedException {
        Intent intent = new Intent();
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setClassName(reactApplicationContext, "com.integratedandroidscreen.AndroidScreenActivity");
        reactApplicationContext.startActivity(intent);
    }

Manifestファイルに以下を追加

    <activity
            android:name="com.integratedandroidscreen.AndroidScreenActivity"
            android:label="@string/app_name">
     </activity>

画面レイアウト用にapp/res/layout/activity_androidscreen_xmlを追加

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".AndroidScreenActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Androidの画面です"
        android:textSize="34sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

gradleに以下を追加(レイアウト用に別途必要となったため)

implementation 'androidx.constraintlayout:constraintlayout:2.0.4'

ビルドして再実行することで、冒頭に示した画面遷移を実現することができました。

参考文献

blog.mitsuruog.info