React Nativeからaar形式のアプリを呼び出す

やりたいこと

aar形式のアプリケーションが配布されてて、それをReact Nativeアプリから呼び出したい。

完成したもの

そもそもaarとは

Androidアプリケーションやライブラリプロジェクトを配布するためのバイナリ形式。
Androidライブラリプロジェクトのコンパイル済みコード、リソース、およびメタデータを1つのアーカイブにまとめたもの。 これにより、再利用可能なコンポーネントを簡単に他のAndroidアプリケーションに組み込むことができる。

やってみる

3つの手順に分けて説明していきます。

Ⅰ. Androidで画面遷移を作る
Ⅱ. 遷移先の画面をAAR化
Ⅲ. 遷移前の画面をReact Nativeにする

いきなりReact NativeからAARを呼び出したらなんだかもやっとなりそうなので、

Ⅰ. AndroidJava)コード→AndroidJava)コード
Ⅱ. AndroidJava)コード→AAR
Ⅲ. React Nativeコード→AAR

と段階を踏んでいきますが、周りくどかったら思いっきり下までスクロールしてください。

Ⅰ. Androidで画面遷移を作る

  1. Android Studioでアンドロイドプロジェクトを作成 File → New → New Project

  2. モジュールを作成 File → New Module → Android Library

プロジェクト直下に、上で指定した名前のライブラリができていればok。(以下mylibraryとして説明)

appディレクトリ配下にも通常通りJavaファイルを置けるが、aar形式に変換されるのは「mylibrary」以下のもの。
なので、ライブラリ化したコードは「mylibrary」のsrcディレクトリに入れる。

  1. モジュールに画面を書いてみる aar化する前にプロジェクトのソースディレクトリ(app/src/)のコードからモジュールの画面を呼び出してみたいと思います。(蛇足的かもしれませんが) 今はモジュールってなに?状態なので普通にコード書けるよ(aarに変換できる場所ってだけだよ)ってことを確かめてみたかったので。

「mylibrary/java/com/example/mylibrary/」以下にModule用のActivityを作成します。

package com.example.mylibrary;

import android.os.Bundle;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

public class ModuleActivity extends AppCompatActivity {

    private TextView textView;
    private boolean buttonTap = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

なんの変哲もないActivityです。

AndroidManifest.xmlとresフォルダも作ります。
AndroidManifest.xmlは以下。resは割愛

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        tools:targetApi="31">
        <activity
            android:name="com.example.mylibrary.ModuleActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

次に、いったんModuleから離れてプロジェクトのディレクトリ(「/app/java/com/example/aarlibrary」)にmylibrary以下のコードを呼び出すActivityを書きます。

package com.example.aarlibrary;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private TextView textView;
    private boolean buttonTap = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = findViewById(R.id.button);

        textView = findViewById(R.id.text_view);

        button.setOnClickListener( v -> {
            this.onClick();
        });
    }

    public void onClick() {
        Intent intent = new Intent();
        intent.setClassName(getApplicationContext(), "com.example.mylibrary.ModuleActivity");
        startActivity(intent);
    }
}

layoutフォルダもちろっといじりましたが、appディレクトリの画面からモジュールの画面を呼ぶことができました。

ここから遷移元の画面をReact Nativeにしていきます。 後、遷移先の画面はまだaar化していませんが、これからaar化していきます。

Ⅱ. AndroidモジュールをAAR化

  1. build.gradleの設定変更 ビルド時にAPKではなくAARファイルで出力するために、app/build.gradleに以下の設定をします。

  2. applicationIdを削除する

  3. pluginsブロックで宣言されているidを'com.android.application'から'com.android.library'にする
plugins {
    id 'com.android.library' // after
//    id 'com.android.application' //before
}
android {
  ... 
  defaultConfig {
  // applicationを削除
  ...
   }
}
  1. ビルドする プロジェクトのルートディレクトリで以下を実行
./gradlew clean build

ビルドに成功すれば、mylibrary/build/outputs/aar以下にaarファイルが出力されていると思います。

  1. Androidからaarを呼ぶ 今までは同一プロジェクトのネイティブコードから画面を呼んでいましたが、ここからはaarを呼びます。 といってもやることはたったの2つです。

  2. aarを配置

  3. gradleに依存関係を追記 -(後は呼び出すだけ)

まず、aarを呼びたいプロジェクトのapp/libs配下にaarファイルをコピーします。 もしこの記事と同じ手順でやっている方がいたら、プロジェクトは新しく作っても、先のプロジェクトからモジュール部分を削除して使ってもよいです。

次にapp/gradleに以下を記述します。

implementation files('libs/mylibrary-release.aar')

最後に、画面を呼び出すActivityで呼び出すクラス名を変更します。 クラス名はAARのパッケージ名に合わせます。

intent.setClassName(getApplicationContext(), "com.example.mylibrary.ModuleActivity"); 

これで、aarファイルから画面を呼び出すことができました。

Ⅲ. React NativeからAARを呼び出す。

React NativeからAARを呼び出すと書きましたがブリッジの仕組みを使えば
①React NativeからAndroidのネイティブコードを呼び出して
AndroidからAARを呼び出す
に言い換えられます。 ②はもうクリアしています。 また、①は以下の記事で説明させていただいているのでご参照ください。 teisyoku-tabetai.hatenablog.com

なのであとはやるだけになります。

  1. aarをReact Nativeプロジェクトに配置後、Gradleの依存関係を追加 aarをandroid/app/libs以下に格納。 app/build.gradleに以下を追加
implementation files('libs/mylibrary-release.aar')
  1. android/java/com/xx/に必要なネイティブコードを追加

  2. AndroidScreenModule

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";
    }

    @ReactMethod
    public void moveAndroidScreen() throws InterruptedException {
        Intent intent = new Intent();
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setClassName(reactApplicationContext, "com.example.mylibrary.ModuleActivity");
        reactApplicationContext.startActivity(intent);
    }
}
  • AnndroidScreenPackage
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
@Override
        protected List<ReactPackage> getPackages() {
          @SuppressWarnings("UnnecessaryLocalVariable")
          List<ReactPackage> packages = new PackageList(this).getPackages();
            packages.add(new AndroidScreenPackage());  // ★ここだけ追加
          return packages;
        }

実行

npx react-native run-android

するといくつかのエラーが派生。

  • エラー①
* What went wrong:
Execution failed for task ':app:processDebugMainManifest'.
> Manifest merger failed : Attribute application@allowBackup value=(false) from AndroidManifest.xml:12:7-34
        is also present at [mylibrary-release.aar] AndroidManifest.xml:9:9-35 value=(true).
        Suggestion: add 'tools:replace="android:allowBackup"' to <application> element at AndroidManifest.xml:7:5-12:19 to override.

aarのAndroidManifest.xmlとプロジェクトのものが競合してるらしく、applicationに以下を追加で解決。

<application
  ...
  tools:replace="android:allowBackup"
  ...
>
  • エラー②
 uses-sdk:minSdkVersion 21 cannot be smaller than version 24 declared in library [mylibrary-releas.aar] 

Sdkのバージョンがaarでは最低24なのにReact Nativeの方で21になっているというエラーのため、build.gradleのbuildscript部分を下記のように変更

buildscript {
  ext {
    ...
    minSdkVersion = 24 // 修正後
    ...
  }
}
  • エラー③
* What went wrong:
Execution failed for task ':app:processDebugResources'.
> A failure occurred while executing com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask$TaskAction
   > Android resource linking failed
     ERROR:C:\xxx\integratedAndroidScreen\android\app\build\intermediates\incremental\debug\mergeDebugResources\merged.dir\values\values.xml:2170: AAPT: error: resource attr/colorPrimaryVariant (aka com.integratedandroidscreen:attr/colorPrimaryVariant) not found.

androidアプリの画面作成に必要なマテリアルが見つからないというエラーのため、以下の依存関係(aar化したgradleには存在していた)を追加で解決。

implementation 'com.google.android.material:material:1.8.0'

以上までで、冒頭に示したようにReact Nativeからaar形式のアプリ呼び出しを実現できることができました。 React Nativeへのaar組み込みはできるにはできますが、バージョンの互換性だったりbuild.gradleの依存関係だったりには少し注意を払う必要がありそうです。

参考文献

qiita.com

pursue.fun