2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Add-to-appを使って既存プロダクトにFlutterを導入した話

Last updated at Posted at 2021-06-16

はじめに

この記事はカービューでアプリ開発をしている@masa24@nbappsが既存のAndroidとiOSアプリにFlutterを導入した感想です。
Flutterの記事自体はいくつかあるものの、Add-to-appを利用して既存アプリに導入した事例は少なかったため、誰かの参考になればと思い本記事を執筆しました。

導入してみて良いと思った点

両OS対応

**Flutterを導入する最大のメリットです。**ネイティブで両OS対応しようとするとObjective-C、Swift、Java、Kotlinと4つもの言語の知識が必要になりますが、FlutterならDartの知識だけで済みます。
しかも標準のWidget(画面のコンポーネントです、AndroidのTextViewやiOSのUITextViewを思い浮かべてください)に沿って実装するだけでMaterial Designに沿った美しいデザインのアプリが完成します。

書きやすい・コードの量が少ない

Flutterはモバイルアプリ用フレームワークだからか、アプリによくある処理が非常に書きやすいです。
例えば画像を取得する処理は以下のように1行で書くことが出来ます。

image.dart
// ネットワークから取得した画像を表示
Image.network("画像のURL")
// アプリ内のリソースを利用して画像を表示
Image.asset("画像のパス")

非同期処理もお手の物です。

async.dart
// 非同期処理
Future<String> getString() {
    return Future.delayed(Duration(seconds: 1), () => "test");
}

// 非同期処理を受け取って何かを実行
void test() {
    getString().then((value){
        // valueを受け取って何かできる
    });
}

Android Studioで利用できるのも◎です。何かと悩まされるXcodeを使わなくて済みます!

更に、ネイティブで画面を作成する場合はXMLやxibでレイアウトを作成して、対応するViewとViewModelを作成して…と、1画面作るだけで結構な量のコードが必要になりますが、Flutterならコードがレイアウトを表してくれるためかさばりません。
Viewとレイアウトの繋ぎこみが不要なのも魅力です!

ビルドが爆速

FlutterにはHot Reloadという機能があります。(Hot Reload自体については公式ドキュメントを参考にしてください)
サブモジュールとして作ったFlutterプロジェクトをAndroid Studioで開くだけで利用可能になり、2回目のビルド以降は一瞬で完了するというスグレモノです。
これを使うことでFlutterプロジェクトだけを爆速で完成させることができます。あとはネイティブに組み込むだけです!

Hot Reloadについての公式ドキュメント:https://flutter.dev/docs/development/tools/hot-reload

つまづいた点

CachedEngineを使わないと画面遷移が遅い

Flutterで作成した画面を呼び出す方法はいくつかあります。ViewやFragmentとして呼び出す方法もありますが、Activityとして呼び出す方法をここでは紹介します。

test.kt
// FlutterEngineを使わない場合
FlutterActivity.createDefaultIntent(this)

// FlutterEngineを使わない場合2
FlutterActivity
  .withNewEngine()
  .initialRoute("/my_route")
  .build(this)

// CachedEngineを使う場合
FlutterEngineCache
  .getInstance()
  .put("my_engine_id", flutterEngine);

3つの呼び出し方がありますね。上記のうち上2つの呼び出し方でFlutterの画面を呼び出した場合、画面が表示されるまでに数秒かかってしまいます。
プロダクトとして利用する場合はCachedEngineが必須なレベルです。

CachedEngineを使って複数の画面を作った場合を実質サポートしていない

FlutterはWebを意識しており、routeという概念を使って遷移させます(URLみたいなものです)。
CachedEngineを使って複数の画面に遷移させようとすると、このrouteを指定するプロパティがありません。
この問題について、公式には「This is because a cached engine is expected to already be running Dart code, which means it’s too late to configure the initial route.」と書いてあります。
CachedEngineを使った時点でDartのコードが実行されるため、初期ルートを設定するには遅いからだそうです。

引用元:https://flutter.dev/docs/development/add-to-app/android/add-flutter-screen?tab=custom-activity-launch-kotlin-tab#initial-route-with-a-cached-engine

MethodChannelを活用すればなんとかなるのですが、いろいろ工夫が必要なため公式のサポートを待ちたいところです。

MethodChannelを活用する例:

main.dart
void main() => runApp(testPage())
TestPage.dart
class testPage extends StatelessWidget{
    // 省略
}

class MyWidget extends StatefulWidget{
    @override
    _MyWidget createState() => _MyWidget();
}

class _MyWidget extends State<MyWidget> with WidgetsBindingObserver{
    @override
    void didChangeAppLifecycleState(AppLifecycleState state) {
        if(state == AppLifecycleState.resumed) {
            MethodChannel("methodchannel").setMethodCallHandler((MethodCall call) async {
                switch(call.method) {
                    case "test":
                        return Navigator.pushAndRemoveUntil(context, 
                                new MaterialPageRoute(builder: (context) => Route.getWidget(call.arguments)), (route => route.isFirst);
                }
            }
        }
    }
}
Route.dart
class Route{
    static Widget getWidget(String route) {
        switch(route) {
            case 'route1':
                return AnotherPage();
        }
    }
}
test.java
Intent intent = FlutterActivity
        .withCachedEngine("cachedengine")
        .build(context);

MethodChannel methodChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), "methodchannel");
methodChannel.setMethodCallHandler((call, result) -> {
    // 何らかの処理
});
methodChannel.invokeMethod("test", "route1");
test.swift
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let flutterViewController = FlutterViewController(engine: vcDelegate.flutterEngine, nibName: nil, bundle: nil)
let methodChannel = FlutterMethodChannel(name: "methodchannel", binaryMessenger: methodChannel.setMethodCallHandler {
    // 何らかの処理
}
viewControllerDelegate?.navigationController?.pushViewController(flutterViewController, animated: true)
methodChannel.invokeMethod("test", arguments:"route1")

ネストしがち

dartはとにかくネストします。百聞は一見に如かず、Flutter公式のサンプルアプリのコードを見てください。
92行目以降のネストにびっくりすると思います(Lisp経験者ならそうでもない?)
https://github.com/flutter/samples/blob/master/add_to_app/flutter_module/lib/main.dart

もっとも、これは慣れの問題であり、
・適宜関数として切り出せばよい
・Android Studioでは対応してるカッコを明示してくれる
ので書いているうちに気にならなくなるとは思います。

ビルドしないとレイアウトが見れない

画面のレイアウトを作る際、Androidではxml、iOSではxibで見ながら設計できますが、Flutterはビルドしないと見れません。
Hot Reloadが爆速なのですぐに気にならなくなると思いますが、最初はとっつきにくいと思います。

ネイティブ側とFlutter側で同じような変数・関数・リソースが必要

Flutter側とネイティブ側で処理を共有したりはできないので、変数・関数・リソースの重複が起きます。
例えば、HTTPメソッド(GET・POST・DELETEなど)は通信処理が必要なアプリではどうしても書く必要があります。
変数も、Flutterからネイティブ・もしくはネイティブからFlutterの処理を呼び出すMethodChannelという機能を使うときなどに重複しがちです。

methodchannel.dart
/// Flutterからネイティブの処理を呼び出す。
/// ここだけでも"methodchannel"と"test"の定数をFlutterとネイティブの両方で宣言する必要が…
void test() {
    MethodChannel("methodchannel").invokeMethod("test", null);
}
methodchannel.kt
MethodChannel methodChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), "methodchannel")
methodChannel.setMethodCallHandler((call, result) -> {
    if(call.method == "test") {
        // 何らかの処理
    }
});

リソース(画像など)も同じものが必要です。(これはもしかしたら回避策があるかもしれません。)

まとめ

以上、App-to-appを利用して良いところとつまづいたところを紹介しました。
まだまだ課題はたくさんありますが、実際に書いてみると可能性を感じるフレームワークです。
StackOverflowの調査記事( https://insights.stackoverflow.com/survey/2020 )でもMost Loved Frameworksとして第3位にランクインしています。
既存のアプリへの今すぐの導入はおすすめしませんが、新規でアプリを作る際にはおすすめです。今後とも注目ですね。
カービューではFlutterの導入のような技術的チャレンジを歓迎しています。応募をお待ちしております!
採用ページ:https://www.carview.co.jp/recruit/

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?