
Flutterでネイティブ画面を呼び出してみよう
2023.11.08
前回、OS固有の問題を解決するためにネイティブコードの呼び出し方法を解説しました。
今回はさらなる問題解決のため、ネイティブの画面を呼び出せるようにしましょう。
Flutter側の呼び出し処理
今回はアプリのログイン処理をネイティブで行う、という想定で話を進めていきます。
Flutter側はInvokeMethodを使用し呼び出します。詳細は前回の記事で解説しています。
1 2 3 4 | Future<void> _goLoginNative() async { const MethodChannel channel = MethodChannel("Channel"); String? resultText = await channel.invokeMethod("goLogin"); } |
iOSの呼び出し
XCodeを開きiOSのログイン画面を作ります。伝統的なStoryboard+ViewControllerの形式で作ります。ログインボタンを押すと通信し、ログイン結果(トークンなど)をFlutter側に返す、というイメージです。
Swift側のAppDelegate実装です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) let controller: FlutterViewController = window?.rootViewController as! FlutterViewController let methodChannel = FlutterMethodChannel(name: "Channel",binaryMessenger: controller as! FlutterBinaryMessenger) methodChannel.setMethodCallHandler({ (call:FlutterMethodCall, result:@escaping FlutterResult) -> Void in print("method: \(call.method)") switch call.method { case "goLogin": let nextViewController = UIStoryboard(name: "Login", bundle: nil).instantiateInitialViewController() as! LoginViewController nextViewController.modalPresentationStyle = .fullScreen nextViewController.flutterResult = result controller.present(nextViewController, animated: true) default : result(nil) } }) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } |
methodChannelのハンドラを設定する段階でFlutterViewControllerを取得しています。FlutterではこのFlutterViewController一枚(正確には内包されているFlutterViewクラス)に各画面のUIを描画する作りになっています。とはいえネイティブアプリ上の扱いはあくまでViewControllerなので、いつも通りpresent等で遷移することができます。
また、flutter側の処理を再開させるにはsetMethodCallHandler()で渡されるFlutterResultを保持しておく必要があります。今回はコードを短くするためLoginViewControllerのメンバ変数に持たせていますが、実際はdelegateなどでやりとりした方が無難でしょう。
1 2 3 4 5 6 7 8 9 | class LoginViewController: UIViewController { var flutterResult: FlutterResult? @IBAction func loginTapped(_ sender: UIButton) { // ここでログインの処理 self.flutterResult?("ios_token") self.dismiss(animated: true) } } |
LoginViewControllerにはボタン押下処理を記述しています。実際は通信し、ログイントークンやjsonを返す事になります。
Androidの呼び出し
Kotlinも同様にAndroid標準の画面遷移で移動します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <span>var </span><span>result</span>: MethodChannel.Result? = <span>null </span> <span>override fun </span>configureFlutterEngine(flutterEngine: FlutterEngine) { <span>super</span>.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.<span>dartExecutor</span>.<span>binaryMessenger</span>, <span>"Channel"</span>).setMethodCallHandler <span>{ </span>call, result <span>-> </span><span> </span><span>when </span>(call.<span>method</span>) { <span>"goLogin" </span>-> { <span>val </span>intent = Intent(<span>this</span>, LoginActivity::<span>class</span>.<span>java</span>) <span>if </span>(intent.resolveActivity(<span>packageManager</span>) != <span>null</span>) { <span>this</span>.<span>result </span>= result startActivityForResult(intent, <span>0</span>) } } <span>else </span>-> result.success(<span>null</span>) } <span>} </span>} override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) val token = data?.extras?.getString("token") result?.success(token) } |
Activityが終了したことをFlutter側に伝える必要ため今回はstartActivityForResultを使用し、LoginActivity画面から結果を取るようにしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class LoginActivity: Activity() { lateinit var binding : LoginBinding override fun onCreate(savedInstanceState: Bundle?) { binding = LoginBinding.inflate(layoutInflater) setContentView(binding.root) binding.loginButton.setOnClickListener { // ここにログインの処理 intent.putExtra("token", "android_token") setResult(RESULT_OK, intent) finish() } super.onCreate(savedInstanceState) } } |
LoginActivityではiOSアプリと同じく、アクティビティ終了時に情報を渡します。
Flutter側の受け取り処理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | Future<void> _goLoginNative() async { const MethodChannel channel = MethodChannel("Channel"); String? resultText = await channel.invokeMethod("goLogin"); if (resultText != null) { _success(resultText); } } void _success(String token) { Navigator.push( context, MaterialPageRoute(builder: (context) => NextPage(token: token)), ); } |
Flutter側の受け取りは画面遷移を行わないネイティブ呼び出しと同一です。
ログインが成功しFlutterの次画面に遷移する、といったイメージです。
実際の動作
完成したものがこちらです。まずはFlutterでログイン画面を開き、
Androidのネイティブ画面で「ログイン」ボタンを押すと、
ネイティブ画面で取得した値(今回は「android_token」)をFlutterに持ってくることができます。
あとはFlutter側で画面遷移を行うことで引き続きFlutterアプリとして動作します。
まとめ
Flutterはネイティブアプリ上では一つのViewController/Activityを使い回している、という点が分かれば処理をイメージするのは難しくないかと思います。
前回以上にOSネイティブの知識が必要となりますが、Flutterで取りきれないライブラリやOS仕様を解決するための手段として知っておいて損はしませんね。
書いた人はこんな人

- スーパーユーザーと間違われることが多いです。
IT技術11月 1, 2023Flutterでネイティブ画面を呼び出してみよう
IT技術6月 5, 2023Flutterでネイティブコードを呼び出してみよう
ライトコードの日常2月 28, 2023YOUは何しにライトコードへ?〜すどたく編〜