Slide 1

Slide 1 text

Flutter Add To ANDPAD App Copyright © 2020 Present ANDPAD Inc. This information is confidential and was prepared by ANDPAD Inc. for the use of our client. It is not to be relied on by and 3rd party. Proprietary & Confidential 無断転載・無断複製の禁止 potatotips #71

Slide 2

Slide 2 text

自己紹介 - kotaro.kudo - 株式会社アンドパッド アプリ開発チーム - GitHub: ham-burger - Twitter: derakudo - SIerで金融システム開発→別の会社でtoCアプリ開発→現職 - Flutterはここ3ヶ月くらい

Slide 3

Slide 3 text

はじめに - 9月のアップデートでFlutterのAdd To Appを導入しました - あまり導入事例を見かけないので共有

Slide 4

Slide 4 text

アジェンダ 1. Add To App 2. モチベーション 3. なぜReact Nativeじゃないのか 4. 実装方法 5. 良かったこと 6. 悩んだこと 7. 残課題 8. ドキュメントをちゃんと読んでおけば・・・ 9. 今後の展望

Slide 5

Slide 5 text

Add To App - ネイティブアプリにFlutterモジュールを取り込む仕組み - Flutter側でモジュールを作成し、ネイティブとFlutterはMethodChannelを利用して メッセージを送受信する

Slide 6

Slide 6 text

モチベーション - リリース済みアプリが5つ。今後も増える見込み Kotlin/Swift Kotlin/Swift Flutter Swift ReactNative →Flutter(WIP)

Slide 7

Slide 7 text

モチベーション - アプリの数とやりたいことに対して開発リソースが足りない - アプリ開発を担当している正社員は 4人 - それぞれの社員がAndroidとiOS両方の対応を行っている - 複数回同様の実装をしているときの虚無感 - 少しでも開発スピードを上げたい

Slide 8

Slide 8 text

なぜReactNativeじゃないのか - ReactNativeにも同様の仕組みはあるらしい - https://reactnative.dev/docs/integration-with-existing-apps - アプリチーム内の雰囲気やその他諸々を考慮 - https://tech.andpad.co.jp/entry/2020/02/12/114953 - https://techplay.jp/event/784745

Slide 9

Slide 9 text

実装方法 - https://flutter.dev/docs/development/add-to-app/android/project-setup - https://github.com/flutter/samples/tree/master/add_to_app

Slide 10

Slide 10 text

良かったこと - 開発スピードが上がった - hot reloadがあるのでビルド待ちが少ない - モジュール単体で動作確認できるように debug用画面を用意しておく - 特にiOSネイティブは開発中のビルド待ち時間が長いイメージ - 実装差分を気にするコストは無くなった

Slide 11

Slide 11 text

悩んだところ - モジュール化の単位(Viewだけ?画面ごと?) - アプリ共通の処理は既にネイティブ側で実装済みなのでそれを活用するか迷った - 今回の画面でいうとhttpなど - 今回は認証トークンなど最低限のデータのみ連携して呼び出せるようにした - テストもFlutterにまとめたかった V VM M V Android Flutter V M V M VM ネイティブ側でアレコレせずに ... 最低限のデータ 全部のデータ Flutter側で完結するようにする 似たような 共通クラス

Slide 12

Slide 12 text

残っている課題 - Flutterの状態管理は必要 - Add To App自体はFlutter Moduleを呼び出すだけで、画面遷移をマネジメントしてくれるわけでは ない - 呼び出し直後は透過、 MethodChannelで画面遷移させた後に fadeで表示させている - ベストプラクティスがまだわからない A B C A A C Android Flutter Androidの画面遷移に追従して いないので、そのままだと前回 呼び出した画面のまま

Slide 13

Slide 13 text

残っている課題 - クリーンビルドが遅くなった - CIがなんか遅いとの苦情が - FlutterとAndroidどちらもビルドするから - 未解決。神待ち - アプリサイズが大きくなった - AndroidではAPKで40MBほど - AABだともう少し小さい?(未確認) - toCだとDynamic feature moduleなど使ってインストールコストを下げる工夫が必要かもしれない

Slide 14

Slide 14 text

ドキュメントをちゃんと読んでおけば・・・ - Flutterモジュールを触れない人も環境を整える必要があった - ソースコードビルドする方法を選択していた - 配布形式をAARにすることで改善 - https://flutter.dev/docs/development/add-to-app/android/project-setup#option-a---depend -on-the-android-archive-aar

Slide 15

Slide 15 text

今後の展望 - アプリのモジュール化推進の1手段として検討していきたい - https://tech.andpad.co.jp/entry/2020/07/30/191711

Slide 16

Slide 16 text

ご清聴ありがとうございました 株式会社アンドパッドはアプリエンジニアを大募集中です! 様々な技術に挑戦できる環境があります、奮ってご応募ください! 採用ページ : https://recruit.andpad.co.jp/ Tech Blog : https://tech.andpad.co.jp HR ブログ : https://note.com/andpad_hr

Slide 17

Slide 17 text

実装のサンプル(Android)

Slide 18

Slide 18 text

対象画面 - ページングする一覧画面 - タップするとWebViewの詳細画面を開く - WebViewはネイティブ側の既存実装を活用 - 開発中の仕様変更が少なそうだった - 最初は簡単なところから試すほうが良さそう

Slide 19

Slide 19 text

実装方法 - リポジトリの準備 kudo_kotaro@kudo-kotaronoMacBook-Pro add-to-app-sample % ls MyApplication my_flutter

Slide 20

Slide 20 text

実装方法 - setting.gradle include ':app' // assumed existing content rootProject.name = "My Application" setBinding(new Binding([gradle: this])) // new evaluate(new File( // new settingsDir.parentFile, // new 'my_flutter/.android/include_flutter.groovy' // new ))

Slide 21

Slide 21 text

実装方法 - gradle dependencies { implementation project(':flutter') // new }

Slide 22

Slide 22 text

実装方法 - Flutter engineの有効化 class AndpadApplication : MultiDexApplication() { lateinit var flutterEngine: FlutterEngine override fun onCreate() { super.onCreate() flutterEngine = FlutterEngine(this) flutterEngine.dartExecutor.executeDartEntrypoint( DartExecutor.DartEntrypoint.createDefault() ) FlutterEngineCache .getInstance() .put("flutterEngine", flutterEngine) } }

Slide 23

Slide 23 text

実装方法 - ActivityでFlutter画面起動 - FlutterはFragmentとして起動したほうが扱いやすい気がする class SampleActivity : AppCompatActivity() { private val flutterEngine by lazy { (applicationContext as AndpadApplication).flutterEngine } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val fragment: FlutterFragment = FlutterFragment .withCachedEngine("flutterEngine") .build() supportFragmentManager .beginTransaction() .add(R.id.container, fragment) .commitNow() } }

Slide 24

Slide 24 text

実装方法 - MethodChannel経由でルーティングと必要な情報の連携 private val methodChannel by lazy { MethodChannel(flutterEngine.dartExecutor, "methodChannel") } val arguments = mutableMapOf( // 起動画面判別用のパス "path" to "/sample_paging_list", ).apply { // 共通パラメータ(認証トークンetc) putAll(getMappedFlutterHttpClientParams()) } // Fragment起動後にFlutterに画面遷移を通知 methodChannel.invokeMethod("navigate", arguments)

Slide 25

Slide 25 text

実装方法 - Flutter側のルーティング処理 methodChannel = MethodChannel("methodChannel"); methodChannel.setMethodCallHandler((call) async { switch (call.method) { case "navigate": // 画面遷移の処理 } });

Slide 26

Slide 26 text

実装方法 - Flutter側のルーティング処理 case "navigate": final arguments = call.arguments; String path = arguments["path"].toString(); switch (path) { case "sample_paging_list": // 画面Object生成処理 default: return null; }

Slide 27

Slide 27 text

実装方法 - 画面にデータを連携する処理 case "sample_paging_list": final accessToken = arguments["access_token"] as String; return Navigator.of(context) .push(MaterialPageRoute(builder: (context) { final itemId = arguments["item_id"] as int; return SamplePagingListScreen( accessToken: accessToken, itemId: itemId); }));