• トップ
  • ブログ一覧
  • Flutterでネイティブ画面を呼び出してみよう
  • Flutterでネイティブ画面を呼び出してみよう

    すどたく(エンジニア)すどたく(エンジニア)
    2023.11.01

    IT技術

    前回、OS固有の問題を解決するためにネイティブコードの呼び出し方法を解説しました。

    今回はさらなる問題解決のため、ネイティブの画面を呼び出せるようにしましょう。

    Flutter側の呼び出し処理

    今回はアプリのログイン処理をネイティブで行う、という想定で話を進めていきます。

    featureImg2023.06.05Flutterでネイティブコードを呼び出してみようiOS、Androidの両方に対応できて便利なFlutterですが、Flutterの仕様、OSの仕様でさまざまな問題が...

    Flutter側はInvokeMethodを使用し呼び出します。詳細は前回の記事で解説しています。

    1  Future<void> _goLoginNative() async {
    2    const MethodChannel channel = MethodChannel("Channel");
    3    String? resultText = await channel.invokeMethod("goLogin");
    4  }

    iOSの呼び出し

    XCodeを開きiOSのログイン画面を作ります。伝統的なStoryboard+ViewControllerの形式で作ります。ログインボタンを押すと通信し、ログイン結果(トークンなど)をFlutter側に返す、というイメージです。

    Swift側のAppDelegate実装です。

    1    override func application(
    2        _ application: UIApplication,
    3        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    4    ) -> Bool {
    5        GeneratedPluginRegistrant.register(with: self)
    6
    7        let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
    8        let methodChannel = FlutterMethodChannel(name: "Channel",binaryMessenger: controller as! FlutterBinaryMessenger)
    9        methodChannel.setMethodCallHandler({
    10            (call:FlutterMethodCall, result:@escaping FlutterResult) -> Void in
    11            print("method: \(call.method)")
    12            switch call.method {
    13            case "goLogin":
    14                let nextViewController = UIStoryboard(name: "Login", bundle: nil).instantiateInitialViewController() as! LoginViewController
    15                nextViewController.modalPresentationStyle = .fullScreen
    16
    17                nextViewController.flutterResult = result
    18                controller.present(nextViewController, animated: true)
    19            default :
    20                result(nil)
    21            }
    22          })
    23
    24        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    25    }

    methodChannelのハンドラを設定する段階でFlutterViewControllerを取得しています。FlutterではこのFlutterViewController一枚(正確には内包されているFlutterViewクラス)に各画面のUIを描画する作りになっています。とはいえネイティブアプリ上の扱いはあくまでViewControllerなので、いつも通りpresent等で遷移することができます。

    また、flutter側の処理を再開させるにはsetMethodCallHandler()で渡されるFlutterResultを保持しておく必要があります。今回はコードを短くするためLoginViewControllerのメンバ変数に持たせていますが、実際はdelegateなどでやりとりした方が無難でしょう。

    1class LoginViewController: UIViewController {
    2    var flutterResult: FlutterResult?
    3
    4    @IBAction func loginTapped(_ sender: UIButton) {
    5        // ここでログインの処理
    6        self.flutterResult?("ios_token")
    7        self.dismiss(animated: true)
    8    }
    9}

    LoginViewControllerにはボタン押下処理を記述しています。実際は通信し、ログイントークンやjsonを返す事になります。

    Androidの呼び出し

    Kotlinも同様にAndroid標準の画面遷移で移動します。

    1var result: MethodChannel.Result? = null
    2override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
    3    super.configureFlutterEngine(flutterEngine)
    4    MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "Channel").setMethodCallHandler { call, result ->
    5        when (call.method) {
    6            "goLogin" -> {
    7                val intent = Intent(this, LoginActivity::class.java)
    8                if (intent.resolveActivity(packageManager) != null) {
    9                    this.result = result
    10                    startActivityForResult(intent, 0)
    11                }
    12            }
    13            else ->
    14                result.success(null)
    15        }
    16    }
    17}
    18
    19override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    20    super.onActivityResult(requestCode, resultCode, data)
    21    val token = data?.extras?.getString("token")
    22    result?.success(token)
    23}

    Activityが終了したことをFlutter側に伝える必要ため今回はstartActivityForResultを使用し、LoginActivity画面から結果を取るようにしています。

    1class LoginActivity: Activity() {
    2    lateinit var binding : LoginBinding
    3
    4    override fun onCreate(savedInstanceState: Bundle?) {
    5        binding =   LoginBinding.inflate(layoutInflater)
    6        setContentView(binding.root)
    7
    8        binding.loginButton.setOnClickListener {
    9            // ここにログインの処理
    10            intent.putExtra("token", "android_token")
    11            setResult(RESULT_OK, intent)
    12            finish()
    13        }
    14        super.onCreate(savedInstanceState)
    15    }
    16}

    LoginActivityではiOSアプリと同じく、アクティビティ終了時に情報を渡します。

    Flutter側の受け取り処理

    1  Future<void> _goLoginNative() async {
    2    const MethodChannel channel = MethodChannel("Channel");
    3    String? resultText = await channel.invokeMethod("goLogin");
    4    if (resultText != null) {
    5      _success(resultText);
    6    }
    7  }
    8
    9  void _success(String token) {
    10    Navigator.push(
    11      context,
    12      MaterialPageRoute(builder: (context) => NextPage(token: token)),
    13    );
    14  }

    Flutter側の受け取りは画面遷移を行わないネイティブ呼び出しと同一です。

    ログインが成功しFlutterの次画面に遷移する、といったイメージです。

    実際の動作

    完成したものがこちらです。まずはFlutterでログイン画面を開き、

    Androidのネイティブ画面で「ログイン」ボタンを押すと、

    ネイティブ画面で取得した値(今回は「android_token」)をFlutterに持ってくることができます。

    あとはFlutter側で画面遷移を行うことで引き続きFlutterアプリとして動作します。

    まとめ

    Flutterはネイティブアプリ上では一つのViewController/Activityを使い回している、という点が分かれば処理をイメージするのは難しくないかと思います。

    前回以上にOSネイティブの知識が必要となりますが、Flutterで取りきれないライブラリやOS仕様を解決するための手段として知っておいて損はしませんね。

    ライトコードでは、エンジニアを積極採用中!

    ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。

    採用情報へ

    すどたく(エンジニア)

    すどたく(エンジニア)

    おすすめ記事

    エンジニア大募集中!

    ライトコードでは、エンジニアを積極採用中です。

    特に、WEBエンジニアとモバイルエンジニアは是非ご応募お待ちしております!

    また、フリーランスエンジニア様も大募集中です。

    background