エンジニアのはしがき

プログラミングの日々の知見を書き連ねているブログです

ガワネイティブを作るならFlutterが良いかもしれない

f:id:tansantktk:20201202224543p:plain

SwiftでiOSアプリを開発する傍ら、Androidアプリも楽して作れないものかとネットの海を彷徨ったところ、 Flutterというフレームワークにたどり着きました。

Flutterで単純なガワネイティブアプリを作るところまでを実践してみての所感を書いていきたいと思います。

What is Flutter?

Googleが開発したクロスプラットフォームフレームワークです。 同様のフレームワークにはReactNativeやXamarinがあります。

Flutterの強み

少ない記述でiOS, Android両方のソースコードを出力できる

  • 私はクロスプラットフォームフレームワークはXamarinしか書いたことが無いのですが、WebViewとアプリバーを配置するくらいなら150~200行程度で出来ました。

HotReload機能のおかげで、デバッグ中のソース変更の反映がとてもスムーズ

  • デバッグ前のビルドに時間はかかるものの、その後のソース更新の反映が高速です。画面のUI調整する時にはかなり重宝します。

将来的にデスクトップアプリ(Windows, macOS, Linux)のサポートもされる予定

  • まだアルファ版ではあるものの実際に試すことはできるようです。正式にリリースされれば、コスパの高いフレームワークになりそうです。

VSCodeだけでソース記述、デバッグができる

  • 専用のIDEは挙動が微妙だったり(Visual Studio for Macとか、Xcodeとか…)するので、これが地味にうれしいですね。

UIデザインを統一しやすい

  • Flutterはプラットフォームに依存しないUIを生成するので、AndroidとiOSでほぼ同じ見た目のアプリが作れます。

flutter.dev

Flutterの弱み

プラットフォーム固有の機能はそれぞれの言語、フレームワークで記述が必要

  • クロスプラットフォームでは必ずぶち当たる壁だと思います。要件定義の時点で固有の機能実装が多いと分かっているならFlutterは避けた方が賢明。

各プラットフォームの大規模アップデートへの対応速度が未知数

  • 平気で開発者に仕様変更を強要する例のリンゴ社さん等にGoogleがどこまでついていくかの問題。今は開発が盛んなものの、数年後の状況は分かりません。

専用言語のDartを知っておく必要がある

  • ただ、TypeScriptやC#, Javaあたりの静的型付けに近い記法なので、1つでも知っていれば習得は容易なように思います。

実際のソースコード

↓のようなアプリバーとWebViewを配置したシンプルなハリボテアプリを試しに作ってみました。

f:id:tansantktk:20201202220436p:plain:w300

main.dart

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:flutter_phoenix/flutter_phoenix.dart';
import 'constant.dart';

void main() {
  runApp(Phoenix(
    child: MyApp(),
  ));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: Constant.AppName,
      theme: ThemeData(
        brightness: Brightness.dark,
        primarySwatch: Colors.grey,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: Constant.AppName),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  WebViewController _webViewController;
  bool isWebViewLoading = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: Icon(Icons.power_settings_new),
          onPressed: () {
            showDialog(
                context: context,
                builder: (context) {
                  return AlertDialog(
                    title: Text('確認'),
                    content:
                        Text("アプリを再読み込みします。宜しいですか?"),
                    actions: <Widget>[
                      FlatButton(
                          child: Text('いいえ'),
                          onPressed: () => Navigator.pop(context)),
                      FlatButton(
                          child: Text('はい'),
                          onPressed: () => {
                                Phoenix.rebirth(context),
                                Navigator.pop(context)
                              }),
                    ],
                  );
                });
          },
        ),
        title: Text(widget.title),
        centerTitle: true,
      ),
      body: Stack(children: <Widget>[
        WebView(
          initialUrl: Constant.WebViewInitUrl,
          javascriptMode: JavascriptMode.unrestricted,
          onWebViewCreated: (WebViewController controller) async {
            _webViewController = controller;
          },
          onPageFinished: (finish) {
            setState(() {
              isWebViewLoading = false;
            });
          },
        ),
        isWebViewLoading
            ? Center(
                child: CircularProgressIndicator(
                  valueColor: new AlwaysStoppedAnimation<Color>(Colors.amber),
                ),
              )
            : Stack(),
      ]),
    );
  }
}

左上の電源アイコンをタップすると、アプリを初期化させています。 実装方法は複数あるようですが、私はflutter_phoenixというプラグインを今回使わせて頂きました。 記述がシンプルになるので良いですね。

実際は、この画面にさらにメニューやページ移動ボタン、更新ボタン等も付けていくことになると思いますが、 これだけの記述でiOSアプリとAndroidアプリが作れてしまうのには驚きました。

WebViewに対してJavaScriptを流し込むこともできる

下記のように記述すると、SwiftのようにjsファイルをWebViewに適用できます。 まだ使いどころは見つけられていませんが…。

import 'package:flutter/services.dart' show rootBundle;

...

  WebView(
    initialUrl: Constant.WebViewInitUrl,
    javascriptMode: JavascriptMode.unrestricted,
    onWebViewCreated: (WebViewController controller) async {
      _webViewController = controller;
      await loadJavaScriptAsync();
    },
    onPageFinished: (finish) {
      setState(() {
        isWebViewLoading = false;
      });
    },
  ),

...

  Future loadJavaScriptAsync() async {
    String jsCode = await rootBundle.loadString('assets/app.js');
    // FlutterからWebViewにJavaScriptコードを挿入
    await _webViewController.evaluateJavascript(jsCode);
  }

実務的に考えると

新規開発だけに着目するなら、コスパに優れたフレームワークだと思いますが、 保守のことを考えると、結局Android, iOSそれぞれの知識は必要になってきそうです。

保守する必要が無いのならば、書いてて楽しいとは思うのですが、 プラットフォーム固有のバグとかが出たら、どう直せばいいんだろう…という不安があり、 Kotlinが書けない私にとってはまだ手を出すのは早いフレームワークかなというのが、今の正直な感想です。

KotlinもSwiftもバリバリ書けるぜ!ってエンジニアは、選択肢に加えても良いのではないでしょうか。