Airing

Airing

哲学系学生 / 小学教师 / 程序员,个人网站: ursb.me
github
email
zhihu
medium
tg_channel
twitter_id

Flutter Boost 混合開発実践とソースコード解析

1. 概要#

Flutter Boost は、闲鱼チームが開発した Flutter 混合開発フレームワークで、プロジェクトの背景については闲鱼のこの記事を参照してください:码上用它开始 Flutter 混合开发 ——FlutterBoost

この記事では、複数のエンジンに関する実際の問題について説明し、闲鱼が現在採用している混合ソリューションは同じエンジンを共有する方法であることを述べています。Flutter Boost の特徴は以下の通りです:

  • 再利用可能な汎用混合ソリューション
  • より複雑な混合モードをサポート、例えばホームページのタブをサポート
  • 非侵入型ソリューション:Flutter の変更に依存しない
  • 汎用ページライフサイクルをサポート
  • 統一された明確なデザインコンセプト

Flutter Boost は共有エンジンのモードを採用して実現されており、主な考え方は、Native コンテナ Container がメッセージによって Flutter ページコンテナ Container を駆動することにより、Native Container と Flutter Container の同期を達成することです。簡単に言えば、闲鱼は Flutter コンテナをブラウザのようにしたいと考えています。ページアドレスを入力し、コンテナがページの描画を管理します。Native 側では、コンテナを初期化し、コンテナに対応するページフラグを設定することだけを気にすればよいです。

ネット上に関連する接続ドキュメントや使用チュートリアルがないため、私もこの数日間に研究する時間を取ったので、整理して文書にしました。参考までにどうぞ。本文は Android 側の接続とソースコードのみを研究しており、iOS の部分は後日機会があれば補足記事を作成します。

注:この記事で接続する Flutter Boost のバージョンは 1.12.13 で、対応する Flutter SDK バージョンは 1.12.13-hotfixes で、現在の最新バージョンです。ただし、Flutter Boost のバージョンが更新された場合、接続方法や使用方法にいくつかの変更がある可能性があるため、この記事を参考にする際は 1.12.13 バージョンを確認してください。

2. 接続#

2.1 Flutter モジュールの作成#

始める前に、プロジェクトディレクトリが以下の構造になっていることを確認する必要があります:

--- flutter_hybrid
		--- flutter_module
		--- FlutterHybridAndroid 
		--- FlutterHybridiOS

つまり、iOS プロジェクトと Android プロジェクト、flutter_module ディレクトリが同じレベルにある必要があります。これにより、管理が容易になり、後続の統合コードのパスの一貫性が保証されます。

次に、Flutter モジュールを作成します:

cd flutter_hybrid
flutter create -t module flutter_module

注意が必要なのは、Android X をサポートする Flutter モジュールを作成する場合、コマンドに --androidx パラメータを追加する必要があることです:

flutter create --androidx -t module flutter_module 

注:依存関係のインストールが遅い場合は、国内の依存関係ミラーソースに切り替えることができます。

export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn

もちろん、最新の Android Studio を使用して Flutter モジュールを視覚的に作成することもできます(3.6.1 以上のバージョンが必要で、IDE に Flutter と Dart プラグインをインストールする必要があります)。具体的な方法は公式サイトの紹介を参照してください(https://flutter.dev/docs/development/add-to-app/android/project-setup)。ここでは詳しく説明しませんが、この記事で紹介するより一般的な方法で Flutter モジュールを作成し、統合することをお勧めします。

2.2 Flutter モジュールの統合#

Flutter モジュールを作成した後、Native プロジェクトに flutter_module を統合する必要があります。具体的には、2 つの方法があります:

  1. ソースコード依存
  2. arr 依存

2.2.1 ソースコード依存統合#

ソースコード依存の利点は、開発やデバッグが容易で、Android プロジェクトの settings.gradle と app ディレクトリ下の build.gradle ファイルに flutter_module への依存を追加するだけで済みます。

まず、settings.gradle ファイルに以下のコードを追加します:

include ':app'                                     // 既存の内容

setBinding(new Binding([gradle: this]))                                
evaluate(new File(                                                     
  settingsDir.parentFile,                                               
  'flutter_module/.android/include_flutter.groovy'                      
))

setBindingevaluate を追加した後、app/build.gradle:flutter 依存を追加できます:

...
dependencies {
  implementation project(':flutter')
...
}

同時に、このファイル内の android () 設定でコンパイル時の Java バージョンを Java 8 に指定する必要があります。そうしないとエラーが発生します。

compileOptions {
  sourceCompatibility 1.8
  targetCompatibility 1.8
}

最後に、gradle sync を実行して依存ライブラリをダウンロードします。統合が成功すると、左側のプロジェクトディレクトリにプロジェクトと同じレベルの flutter_module フォルダが表示されます。

2.2.2 arr 依存統合#

リモートでパッケージを使用する必要があり、リモートのマシンに Flutter 環境がない場合は、Flutter を arr ファイルにパッケージ化して依存関係を持つことができます。aar ファイルを生成した後、メインプロジェクトで参照します。flutter aar には Flutter SDK のコードが含まれているため、この方法では Flutter 環境は必要なく、サードパーティの迅速な統合にも適しています。

cd .android/
./gradlew flutter:assembleDebug

2.3 Flutter Boost 依存の追加#

まず、Flutter モジュールプロジェクトに flutter-boost 依存を追加します。つまり、pubspec.yaml ファイルの dev_dependencies 設定に flutter-boost 依存を追加します:

dev_dependencies:
  flutter_boost:
     git:
        url: 'https://github.com/alibaba/flutter_boost.git'
        ref: '1.12.13'

上記の flutter boost は AndroidX をサポートしています。support をサポートしたい場合は、ブランチを切り替える必要があります:

flutter_boost:
    git:
        url: 'https://github.com/alibaba/flutter_boost.git'
        ref: 'task/task_v1.12.13_support_hotfixes'

編集が完了したら、flutter_module ディレクトリで以下のコマンドを実行して依存関係をインストールします。

flutter packages get

その後、Android プロジェクトの app ディレクトリ下の build.gradle ファイルに :flutter_boost 依存を追加します。

dependencies {
    ...
    implementation project(':flutter')
    implementation project(':flutter_boost')
}

Flutter Boost は Flutter プラグインの形式で私たちのプロジェクトに統合されるため、いくつかの作業を行う必要があります。まず、app ディレクトリ下の build.gradle ファイルの先頭に以下のコードを追加します:

def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
    localPropertiesFile.withReader('UTF-8') { reader ->
        localProperties.load(reader)
    }
}

def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
    throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}

def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
    flutterVersionCode = '1'
}

def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
    flutterVersionName = '1.0'
}

これにより、Android プロジェクトの local.properties ファイルに私たちのローカル Flutter SDK の位置を指定する必要があります(このファイルがない場合は新しく作成します):

flutter.sdk = /Users/airing/flutter

最後に、プロジェクトディレクトリ下の settings.gradle に以下のコードを追加して flutter-plugin を導入します:

def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()

def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
    pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}

plugins.each { name, path ->
    def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
    include ":$name"
    project(":$name").projectDir = pluginDirectory
}

同様に、Android プロジェクトの依存関係を修正した後、gradle sync を実行する必要があります。

これで、Flutter Boost の統合が成功しました。次に、Flutter Boost を使用して混合開発を行います。混合開発は主に 2 つのシナリオに関係しています:

  1. Native プロジェクトに Flutter ページを追加すること、つまり単一の Flutter スクリーンを追加すること。
  2. Native ページに Flutter モジュールを埋め込むこと、つまり Flutter フラグメントを追加すること。

これらの 2 つの方法は Flutter の公式サイトで実践的に説明されています。ここでは、Flutter Boost を使用してどのように実現するかを見て、実装原理を探求します。

3. 混合開発 1: Flutter View#

image

3.1 Flutter モジュールで Flutter Boost を使用する#

まず、依存関係をインポートします。

import 'package:flutter_boost/flutter_boost.dart';

次に、main メソッドで実行される rootWidget に 2 つの新しいページを登録します。これにより、Native プロジェクトが遷移できるようになります。

import 'package:flutter/material.dart';
import 'package:flutter_boost/flutter_boost.dart';
import 'simple_page_widgets.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();

    FlutterBoost.singleton.registerPageBuilders({
      'first': (pageName, params, _) => FirstRouteWidget(),
      'second': (pageName, params, _) => SecondRouteWidget(),
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Boost example',
        builder: FlutterBoost.init(),
        home: Container(
            color:Colors.white
        ));
  }
}

コードは非常にシンプルです:

  1. initState の時に FlutterBoost.singleton.registerPageBuilders を使用してページを登録します。
  2. builder で FlutterBoost を初期化します。

3.2 Android プロジェクトで Flutter Boost を使用する#

Android プロジェクトに Flutter ページを追加することは、Flutter アクティビティを追加することを意味します(iOS では新しい FlutterViewController を追加することを意味します。ここでは iOS の実装について詳しく説明しません。興味のある方は Flutter Boost のサンプルコードとソースコードを自分で読んでください)。

ここでは、AndroidManifest.xml の Application 設定に Flutter Boost アクティビティを追加します:

<activity
  android:name="com.idlefish.flutterboost.containers.BoostFlutterActivity"
  android:theme="@style/Theme.AppCompat"
  android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
  android:hardwareAccelerated="true"
  android:windowSoftInputMode="adjustResize" >
  <meta-data android:name="io.flutter.embedding.android.SplashScreenDrawable" android:resource="@drawable/page_loading"/>
</activity>

さらに、AndroidManifest.xml に flutterEmbedding バージョン設定を追加する必要があります:

<meta-data android:name="flutterEmbedding"
	android:value="2">
</meta-data>

次に、FlutterBoost を初期化する作業を行います。Application の onCreate メソッドで初期化することをお勧めします:

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        INativeRouter router = new INativeRouter() {
            @Override
            public void openContainer(Context context, String url, Map<String, Object> urlParams, int requestCode, Map<String, Object> exts) {
               String  assembleUrl=Utils.assembleUrl(url,urlParams);
                PageRouter.openPageByUrl(context,assembleUrl, urlParams);
            }

        };

        FlutterBoost.BoostLifecycleListener boostLifecycleListener= new FlutterBoost.BoostLifecycleListener(){

            @Override
            public void beforeCreateEngine() {

            }

            @Override
            public void onEngineCreated() {

            }

            @Override
            public void onPluginsRegistered() {

            }

            @Override
            public void onEngineDestroy() {

            }

        };

        Platform platform = new FlutterBoost
                .ConfigBuilder(this,router)
                .isDebug(true)
                .whenEngineStart(FlutterBoost.ConfigBuilder.ANY_ACTIVITY_CREATED)
                .renderMode(FlutterView.RenderMode.texture)
                .lifecycleListener(boostLifecycleListener)
                .build();
        FlutterBoost.instance().init(platform);
    }
}

FlutterBoost を Android プロジェクトで初期化するには、4 つの作業が必要です:

  1. ルーティングメソッドを登録します(後で PageRouter の実装について説明します)。
  2. Flutter Boost のライフサイクルリスナーを追加します。これにより、Flutter Engine が作成される前、作成された後、破棄された後、Flutter プラグインが登録された後のコールバックイベントを受け取ることができます。
  3. Flutter Boost 設定を宣言し、ルーティングとライフサイクル関数を設定します。
  4. Flutter Boost を初期化します。

次に、Android プロジェクトでページルーティングのツールクラス PageRouter を実装します。ここでは、Flutter Boost のサンプルコードにある実装をそのまま示します。非常に包括的です:

public class PageRouter {

    public final static Map<String, String> pageName = new HashMap<String, String>() {{

        put("first", "first");
        put("second", "second");
        put("tab", "tab");
        put("sample://flutterPage", "flutterPage");
    }};

    public static final String NATIVE_PAGE_URL = "sample://nativePage";
    public static final String FLUTTER_PAGE_URL = "sample://flutterPage";
    public static final String FLUTTER_FRAGMENT_PAGE_URL = "sample://flutterFragmentPage";

    public static boolean openPageByUrl(Context context, String url, Map params) {
        return openPageByUrl(context, url, params, 0);
    }

    public static boolean openPageByUrl(Context context, String url, Map params, int requestCode) {

        String path = url.split("\\?")[0];

        Log.i("openPageByUrl",path);

        try {
            if (pageName.containsKey(path)) {
                Intent intent = BoostFlutterActivity.withNewEngine().url(pageName.get(path)).params(params)
                        .backgroundMode(BoostFlutterActivity.BackgroundMode.opaque).build(context);
                if(context instanceof Activity){
                    Activity activity=(Activity)context;
                    activity.startActivityForResult(intent,requestCode);
                }else{
                    context.startActivity(intent);
                }
                return true;
            } else if (url.startsWith(FLUTTER_FRAGMENT_PAGE_URL)) {
                context.startActivity(new Intent(context, FlutterFragmentPageActivity.class));
                return true;
            } else if (url.startsWith(NATIVE_PAGE_URL)) {
                context.startActivity(new Intent(context, NativePageActivity.class));
                return true;
            }

            return false;

        } catch (Throwable t) {
            return false;
        }
    }
}

3.3 Native プロジェクトで Flutter ページを開く#

呼び出しは非常に簡単で、Native ページのボタンに onClick リスナーをバインドして、登録した Flutter の first ページを開くことができます。また、map パラメータを渡すこともできます:

@Override
public void onClick(View v) {
    Map params = new HashMap();
    params.put("test1","v_test1");
    params.put("test2","v_test2");

    PageRouter.openPageByUrl(this, "first", params);
}

3.1 で Flutter でページを登録するコードを振り返ると、params パラメータがあることに気づきます。そうです、それは Native が Flutter を開くときに渡すパラメータです。これを印刷したり、ウィジェットに渡して追加の処理を行ったりできます:

FlutterBoost.singleton.registerPageBuilders({
      'first': (pageName, params, _) => {
        print("flutterPage params:$params");

        return FirstRouteWidget(params:params);
      },
      'second': (pageName, params, _) => SecondRouteWidget(),
});

3.4 Flutter ページで Native ページを開く#

同様に、Native で Flutter ページを開いた後、Flutter のビジネスが新しい Native ページを開く必要がある場合は、FlutterBoost.singleton.open を使用すればよいです。以下のように:

// 後のパラメータは、native の IPlatform.startActivity メソッドのコールバックで URL のクエリ部分に追加されます。
// 例えば:sample://nativePage?aaa=bbb
onTap: () => FlutterBoost.singleton
     .open("sample://nativePage", urlParams: <dynamic,dynamic>{
      "query": {"aaa": "bbb"}
}),

もちろん、このメソッドは Native ページを開くだけでなく、新しい Flutter ページを開くこともサポートしています。ルーティング名を正しく記述すればよいだけです。ここでは詳しく説明しません。

注:Flutter の JIT コンパイルモードのおかげで、flutter attach コマンドを使用してホットリロード機能を実現できるため、Flutter ページを開発する際にプロジェクトを再コンパイルする必要はありません。

4. 混合開発 2:Flutter Fragment#

image

プロジェクトに次のような Activity があると仮定します:

<activity
     android:name=".FlutterFragmentPageActivity"
     android:theme="@style/Theme.AppCompat"
     android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
     android:hardwareAccelerated="true"
     android:windowSoftInputMode="adjustResize">
     <meta-data android:name="io.flutter.embedding.android.SplashScreenDrawable" android:resource="@drawable/page_loading"/>
</activity>

対応するレイアウトには、プレースホルダーとして FrameLayout コンポーネントを追加する必要があります:

<FrameLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:id="@+id/fragment_stub"/>

最後に、対応する URL の Flutter ウィジェットを取得し、プレースホルダーコンポーネントに挿入します:

@Override
public void onClick(View v) {
    FlutterFragment mFragment = new FlutterFragment.NewEngineFragmentBuilder().url("flutterFragment").build();
    getSupportFragmentManager()
        .beginTransaction()
        .replace(R.id.fragment_stub, mFragment)
        .commit();
}

5. Flutter Boost ソースコード解析#

このセクションでは、Flutter Boost の原理を簡単に分析します。根本を理解することで、よりスムーズに使用できるようになります。ページ数の制約上、すべてのソースコードを分析することはできませんが、代表的なソースコードを分析し、残りの原理は読者に任せます。

このセクションでは、Dart 側から切り込み、2 つの API に注目します。1 つはページを登録する registerPageBuilders、もう 1 つはページを開く open です。Flutter Boost がこれらをどのように実現しているかを見ていきます。

5.1 ページの登録#

Flutter Boost を使用するプロセスの最初のステップは、Flutter でページを登録することです。registerPageBuilders 関数を呼び出しますが、この関数がどのように実装されているかを見てみましょう。

flutter_boost.dart ファイルで、この関数のエントリを簡単に見つけることができます:

///Register a map builders
void registerPageBuilders(Map<String, PageBuilder> builders) {
  ContainerCoordinator.singleton.registerPageBuilders(builders);
}

これは ContainerCoordinator シングルトンの registerPageBuilders を呼び出しています。次に、container_coordinator.dart ファイルでこの関数の実装を見てみましょう:

final Map<String, PageBuilder> _pageBuilders = <String, PageBuilder>{};
  PageBuilder _defaultPageBuilder;

///Register page builder for a key.
void registerPageBuilder(String pageName, PageBuilder builder) {
  if (pageName != null && builder != null) {
    _pageBuilders[pageName] = builder;
  }
}

ここで PageBuilder を見つけることができ、これはウィジェットです。この関数は、登録したウィジェットを Map に格納し、指定したルーティング名をキーとして使用します。次に、_pageBuilders が定義された後、どのように使用されるかに注目します。

BoostContainerSettings _createContainerSettings(
      String name, Map params, String pageId) {
    Widget page;

    final BoostContainerSettings routeSettings = BoostContainerSettings(
        uniqueId: pageId,
        name: name,
        params: params,
        builder: (BuildContext ctx) {
          //Try to build a page using keyed builder.
          if (_pageBuilders[name] != null) {
            page = _pageBuilders[name](name, params, pageId);
          }

          //Build a page using default builder.
          if (page == null && _defaultPageBuilder != null) {
            page = _defaultPageBuilder(name, params, pageId);
          }

          assert(page != null);
          Logger.log('build widget:$page for page:$name($pageId)');

          return page;
        });

    return routeSettings;
  }

ここでは、_createContainerSettings 内でウィジェットをビルドした後、routeSetting を返します。この変数は _nativeContainerWillShow 内で pushContainer を呼び出すために使用され、_nativeContainerWillShow は _onMethodCall 内で呼び出されます。

bool _nativeContainerWillShow(String name, Map params, String pageId) {
    if (FlutterBoost.containerManager?.containsContainer(pageId) != true) {
      FlutterBoost.containerManager
          ?.pushContainer(_createContainerSettings(name, params, pageId));
    }
    return true;
  }
Future<dynamic> _onMethodCall(MethodCall call) {
    Logger.log("onMetohdCall ${call.method}");

    switch (call.method) {
      // 省略無関係なコード
      case "willShowPageContainer":
        {
          String pageName = call.arguments["pageName"];
          Map params = call.arguments["params"];
          String uniqueId = call.arguments["uniqueId"];
          _nativeContainerWillShow(pageName, params, uniqueId);
        }
        break;
    }
  	// 省略無関係なコード
}

これらのコードは、Dart 側が Native からの通信をリッスンし、Native がページコンテナを開く情報を送信した場合(willShowPageContainer)、FlutterBoost のコンテナマネージャーがユーザーが登録したルーティングページに基づいて新しいコンテナを開くことを示しています。ここでの pushContainer は、ルーティング管理やバインドリスナーなどの操作を行いますが、この部分のロジックは詳しく見ません。主に Dart と Native の相互通信を見ていきます。

5.2 通信#

ContainerCoordinator(BoostChannel channel) {
    assert(_instance == null);

    _instance = this;

    channel.addEventListener("lifecycle",
        (String name, Map arguments) => _onChannelEvent(arguments));

    channel.addMethodHandler((MethodCall call) => _onMethodCall(call));
  }

Flutter Boost で通信を担当するのは BoostChannel で、これは本質的には MethodChannel のラッパーであり、MethodChannel は Native と Flutter の通信手段の 1 つです。興味のある方は MethodChannel に関する資料を調べて理解を深めてください。

image

Flutter の公式サイトでの MethodChannel の紹介を読むことができます:https://flutter.dev/docs/development/platform-integration/platform-channels

5.3 ページを開く#

最後に、ページを開く関数 open の実装を見てみましょう。ライブラリ内で簡単に見つけることができます:

Future<Map<dynamic, dynamic>> open(String url,
      {Map<dynamic, dynamic> urlParams, Map<dynamic, dynamic> exts}) {
    Map<dynamic, dynamic> properties = new Map<dynamic, dynamic>();
    properties["url"] = url;
    properties["urlParams"] = urlParams;
    properties["exts"] = exts;
    return channel.invokeMethod<Map<dynamic, dynamic>>('openPage', properties);
  }

ここでの作業は、パラメータをラップして openPage メッセージを Native に送信することです。次に、Native 側がこのメッセージを受け取った後、どのように処理するかを見てみましょう。Android 側の Flutter Boost ソースコード内で FlutterBoostPlugin.java というファイルを見つけることができ、その中に MethodChannel のロジックがあり、Dart 側のメッセージをリッスンしています:

class BoostMethodHandler implements MethodChannel.MethodCallHandler {

        @Override
        public void onMethodCall(MethodCall methodCall, final MethodChannel.Result result) {

            FlutterViewContainerManager mManager = (FlutterViewContainerManager) FlutterBoost.instance().containerManager();
            switch (methodCall.method) {
                // 省略無関係なブランチ
                case "openPage": {
                    try {
                        Map<String, Object> params = methodCall.argument("urlParams");
                        Map<String, Object> exts = methodCall.argument("exts");
                        String url = methodCall.argument("url");

                        mManager.openContainer(url, params, exts, new FlutterViewContainerManager.OnResult() {
                            @Override
                            public void onResult(Map<String, Object> rlt) {
                                if (result != null) {
                                    result.success(rlt);
                                }
                            }
                        });
                    } catch (Throwable t) {
                        result.error("open page error", t.getMessage(), t);
                    }
                }
                break;
                default: {
                    result.notImplemented();
                }
            }
        }
    }

Dart からの openPage メッセージを受信すると、Android 側のコンテナマネージャー(FlutterViewContainerMananger)は、Dart 側から送信された設定データに基づいてコンテナを開きます。この openContainer は、最終的には抽象メソッドであり、ビジネス側で実装する必要があります。3.2 セクションで Android で Flutter Boost を初期化する際に行った最初の作業は、この openContainer を実装することでした。最終的には、私たちがラップした PageRouter ツールクラスに渡され、つまり context.startActivity() になります。

これで、Android プロジェクトに Flutter Boost を統合して、Flutter を Android プロジェクトに混合開発することができました。この記事では、Flutter Boost のソースコードを初歩的に分析しただけですが、後日、詳細な分析や iOS 側の接続使用について補足する機会があればお知らせします。お楽しみに。

附:Flutter Boost 接続実践(iOS 篇)#

1. 前言#

Android に Flutter Boost を接続した後、次に iOS プロジェクト(OC)に Flutter Boost を接続する方法を見ていきます。

この記事では、iOS プロジェクトに Flutter Boost を接続するプロセスを簡単に整理し、前文の補足とします。

前文を参照してください:Flutter Boost 混合開発実践とソースコード解析(Android の例)。Flutter モジュールも前文で生成したもので、ディレクトリ構造も前文と同様ですので、繰り返しません。

2. 接続#

2.1 プロジェクト準備#

Cocoapods を備えた空のプロジェクトを準備し、Podfile で以前準備した Flutter モジュールを依存関係として設定します:

flutter_application_path = '../flutter_module'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

target 'FlutterHybridiOS' do
	install_all_flutter_pods(flutter_application_path)
end

次に、プロジェクトのルートディレクトリで pod install を実行すると、Flutter モジュールが統合されます。Pods に以下のいくつかのモジュールが追加されているのが確認できれば、統合が成功したことになります。

image.png

Cocoapods を理解していない場合は、CocoaPods 使用ガイドを参照してください。

2.2 ルーティングクラスの実装#

この部分は、Flutter Boost の公式が提供する例をそのまま参考にすれば大丈夫です:https://github.com/alibaba/flutter_boost/blob/master/example/ios/Runner/PlatformRouterImp.h

PlatformRouterImp.h:

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <flutter_boost/FlutterBoost.h>

NS_ASSUME_NONNULL_BEGIN

@protocol FLBPlatform;

/**
 * プラットフォーム側のページの開閉を実装します。ページを開くために直接使用することは推奨されません。ページを開閉するには、FlutterBoostPlugin の open および close メソッドを使用することをお勧めします。
 * FlutterBoostPlugin はページの戻りデータの機能を備えています。
 */
@interface PlatformRouterImp : NSObject<FLBPlatform>
@property (nonatomic,strong) UINavigationController *navigationController;
@end

NS_ASSUME_NONNULL_END

PlatformRouterImp.m:

#import "PlatformRouterImp.h"
#import <flutter_boost/FlutterBoost.h>

@interface PlatformRouterImp()
@end

@implementation PlatformRouterImp

#pragma mark - Boost 1.5
- (void)open:(NSString *)name
   urlParams:(NSDictionary *)params
        exts:(NSDictionary *)exts
  completion:(void (^)(BOOL))completion
{
    BOOL animated = [exts[@"animated"] boolValue];
    FLBFlutterViewContainer *vc = FLBFlutterViewContainer.new;
    [vc setName:name params:params];
    [self.navigationController pushViewController:vc animated:animated];
    if(completion) completion(YES);
}

- (void)present:(NSString *)name
   urlParams:(NSDictionary *)params
        exts:(NSDictionary *)exts
  completion:(void (^)(BOOL))completion
{
    BOOL animated = [exts[@"animated"] boolValue];
    FLBFlutterViewContainer *vc = FLBFlutterViewContainer.new;
    [vc setName:name params:params];
    [self.navigationController presentViewController:vc animated:animated completion:^{
        if(completion) completion(YES);
    }];
}

- (void)close:(NSString *)uid
       result:(NSDictionary *)result
         exts:(NSDictionary *)exts
   completion:(void (^)(BOOL))completion
{
    BOOL animated = [exts[@"animated"] boolValue];
    animated = YES;
    FLBFlutterViewContainer *vc = (id)self.navigationController.presentedViewController;
    if([vc isKindOfClass:FLBFlutterViewContainer.class] && [vc.uniqueIDString isEqual: uid]){
        [vc dismissViewControllerAnimated:animated completion:^{}];
    }else{
        [self.navigationController popViewControllerAnimated:animated];
    }
}
@end

Flutter Boost は通常の push をサポートし、モーダルウィンドウを開くこともでき、手動で pop することもサポートします。

2.3 ルーティング管理のバインディング#

AppDelegate.h:

#import <UIKit/UIKit.h>
#import <flutter_boost/FlutterBoost.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (nullable, nonatomic, strong) UIWindow *window;
@end

AppDelegate.m:

#import "AppDelegate.h"
#import "PlatformRouterImp.h"
#import <flutter_boost/FlutterBoost.h>

@interface AppDelegate ()

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    PlatformRouterImp *router = [PlatformRouterImp new];
    [FlutterBoostPlugin.sharedInstance startFlutterWithPlatform:router
                                                        onStart:^(FlutterEngine *engine) {
                                                            
                                                        }];
	UITabBarController *tabVC = [[UITabBarController alloc] init];
	UINavigationController *rvc = [[UINavigationController alloc] initWithRootViewController:tabVC];
    router.navigationController = rvc;
    
	return YES;
}

3. 使用#

- (void)openClick:(UIButton *)button
{
    [FlutterBoostPlugin open:@"first" urlParams:@{kPageCallBackId:@"MycallbackId#1"} exts:@{@"animated":@(YES)} onPageFinished:^(NSDictionary *result) {
        NSLog(@"ページが終了したときに呼び出され、結果は:%@", result);
    } completion:^(BOOL f) {
        NSLog(@"ページが開かれました");
    }];
}

- (void)openPresentClick:(UIButton *)button
{
    [FlutterBoostPlugin open:@"second" urlParams:@{@"present":@(YES),kPageCallBackId:@"MycallbackId#2"} exts:@{@"animated":@(YES)} onPageFinished:^(NSDictionary *result) {
        NSLog(@"ページが終了したときに呼び出され、結果は:%@", result);
    } completion:^(BOOL f) {
        NSLog(@"ページが表示されました");
    }];
}

同様に、ここでは Native 側で 2 つの異なる方法を使用して、Flutter モジュール内で登録したルーティング名を開くことができます。

これで、iOS プロジェクトに Flutter Boost を接続することができました。混合開発の旅を始めましょう〜

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。