Wowgineer Notes

WowTechエンジニアの徒然開発日記

Wowgineer Notes

〜新たな"Wow"を生み出す〜

初めてFlutter開発に挑戦してわかったこと・困ったこと

こんにちは!アプリ開発チームでAndroidの開発を担当しているラジタです。

今回はFlutterフレームワークを使って、WowTalkのモバイル版のサンクス機能の開発を行いました。最近Flutterでの開発アプリや機能が多いので、この記事では、Flutterの開発について自分が困った点や、気に入った点を紹介したいと思います。

Flutterの説明

Flutterとは、2018年にGoogleが発表したマルチプラットフォームアプリ用のオープンソース(ソースコードが一般公開されている)型のフレームワークです。この枠組みの「Hot Reload」という、書いたコードを即座にアプリの挙動に反映できる機能を使うことで、より簡単かつ迅速に開発を進めることができるようになります。Flutterのフレームワークには、Googleが2011年に発表したDartと呼ばれるアプリ開発言語が使われています。

Dart言語説明

Dartとは、サーバー、モバイル、デスクトップ、ウェブアプリケーションを構築できる高水準のプログラミング言語です。Dartと他のプログラミング言語の違いは、Pubと呼ばれる独自のパッケージマネージャーが使われていることです。開発者はこれらのパッケージを使用して、DartおよびFlutterアプリを構築できます。

Flutterを使うメリット

  1. 開発速度の向上と開発コストの削減
    Native開発との大きな違いは、クロスプラットフォーム開発がある点です。これにより1つのソースコードでiOSとAndroidという複数のWowTalkアプリの機能の開発ができるようになるため、開発の速度が上がり、プラットフォーム間で実装しないといけないコードの総量を削減することができます。
  2. 保守と運用コストの削減
    WowTalkのサンクス以外の機能に関して、Androidアプリは、Androidアプリエンジニア(自分)が、iOSアプリはiOSアプリエンジニアが保守・運用を担当します。稼働が増える分、それに合わせて、保守と運用コストも増えます。クロスプラットフォーム(Flutter)開発の場合は、一つのソースコードでAndroidとIOS両方モバイルアプリを動かしますので、対応稼働が減り、必然的にコストも減ります。
  3. 仕様変更に強く、試験にかかる工数を抑えることができる
    WowTalk Native開発では、ソースコードの変更後は、変更点適応のためにモバイルアプリの再起動(再ビルド)をする必要があります。しかし、Flutterにはホットリロード(Hot Reload)と言う機能があり、開発中にcmd+sを押すと、変更点をすぐにリロード(更新)することができます。

Flutterを使うデメリット

  1. 端末独自のOSの影響を受けてしまう
    Flutterの大きなメリットは、一つのコードでWowTalk IOSとAndroidモバイルアプリ開発ができることを挙げましたが、OSごとに搭載されている機能(カメラ機能、位置情報機能など)に関しては、個別でソースコードの更新をする必要があります。また、iOSやAndroidのOSアップデートで追加された新しい機能をFlutterがサポートするまでに時間がかかる場合があります。
  2. ネイティブ開発と比べるとライブラリが少ない
    ネイティブ開発で用いられるアプリ開発言語は、(iOSアプリの場合はSwift、Androidアプリの場合はJavaなど)と比較すると少ないですが、徐々に増えてきています。
  3. ネイティブ開発の方が動作面で有利に働く場合がある
    Flutterは、マルチプラットフォームに対応していることから、保守性の面でも動作の改善は早いですが、UI側でのレンダリング、ドロップダウンスピード、テキストのフォーカスなどの面でNativeアプリと比べると、動きが良くないと感じる場合があります。

よく使うクラス

StatefulBuilderとStatelessWidget

Stateとは「状態」や「状況」という意味です。変数の値、端末内に保存しているデータやAPIで取得したデータ、入力フォーム等のUIの状態や入力内容、端末の状態などがこれにあたります。このウィジェットタイプは自分の変数を変更することで自身を再ビルドすることができ、表示内容を自動的に変更できます。

StatefulBuilderとは、StatelessWidgetをStatefulWidgetとして扱うためのウィジェットです。

今回この機能のモバイル版の開発にあたり、交換時に表示するダイアログは、StatefullBuilderを使って作成しました。

return AlertDialog(
          title: Text(Localized.of(context).redeem_point_exchange_title),
01          content: StatefulBuilder(
              builder: (BuildContext context, StateSetter setState) {
            return Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
              Padding(
                  padding: const EdgeInsets.all(10),
                  child: Text(
                    Localized.of(context).redeem_point_exchange_sub_title,
                    style: const TextStyle(color: Colors.grey, fontSize: 12),
                  )),
              Padding(
                padding: const EdgeInsets.all(0),
                child: DropdownButtonHideUnderline(
                  child: DropdownButton2(
                    isExpanded: true,
......
                    value: _selectedPoint,
                    onChanged: (value) {
                      _selectedPoint = value as String;
                      _selectedItem = int.parse(_selectedPoint!);
                      exRate = (_selectedItem/exchangeRate).toString();
 02                     setState((){
                        requiredPoint = MessageUtil.format(Localized.of(context).redeem_point_exchange_required_point, exRate);
                        requiredPointDescription = MessageUtil.highlightText(requiredPoint,exRate, const TextStyle(color: Colors.grey, fontSize: 12), const TextStyle(color: myColors.point_highlight));
                      });
                    },

                ),
              ),
              Padding(
                  padding: const EdgeInsets.all(10),
03                  child: requiredPointDescription),
              Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: <Widget>[
......
  1. StatefulBuilderウィジェットを追加しています。
  2. DropDownのonChangeを行う際は、必要なポイントを計算し、setStateを使ってUI更新依頼を行います。
  3. UIに設定されているTextは更新されます。

以下はUIが更新される画面の例です。

ページウィジェット (StatelessWidget)とは、Stateの状態を持たない静的な(static)ウィジェットのことです。ウィジェットで使用する変数は親ウィジェットから渡された値を使用することしかできません。値の変更はウィジェット内でできません。

WowTalkのサンクス機能では、ユーザが獲得しているポイントを表示するヘッダー部分をStatelessWidgetで実装しました。

このヘッダー部分のポイント値は、APIから返ってくる値を表示する設定です。サンクステンプレートやギフトを交換した際に、APIから返ってきた値は更新されます。

ページ移動 (Navigator)

Flutterでの宣言的なUI構築で画面遷移の方法にNavigatorを使います。今回の実装でNavigator.pushとNavigator.popを使いました。

child: GestureDetector(
              onTap: () {
                Navigator.push<void>(context,
                    MaterialPageRoute(builder: (context) => GiftRecordView()));
              },

Navigator.pushを使うと、指定した画面(widget!)へ遷移します。

void pressOk(BuildContext context, String _selectedItem) {
    for (var gift in feePointGiftList) {
      if (gift.requiredPoint.toString() == _selectedItem) {
        exchangePointHandler(gift.giftId.toString());
        Navigator.pop(context);
      }
    }
  }

Navigator.popの場合、元の画面に戻ります。

大変だった点

アラートボックスの種類が多かった

ポイント交換ダイアログ画面のDropDownを表示する必要があり、調べてみると、複数の開発元から作られたライブラリーが見つかりました。最初にawesome_dropdownライブラリーを使って開発を行いました。動作確認した際に、ドロップダウンを開いてキャンセルで戻った場合、UIが残ってしまうという不具合が発生しました。また、IOSでタップしても反応しない不具合も発生しました。Googleで解決方法を調べても見つからなかったので今回はdropdown_button2ライブラリーを使っています。

大変だった実装

APIで取得したデータをViewに反映させるまでの流れがNativeと異なっていた

今回のプロジェクトでAPIからのデータ取得は、index.dartファイルをentry pointにしてViewに反映させるまでの流れになっていました。

AndroidのNative開発では、直接対象の画面からAPIを呼んでデータ取得することになっているので流れに関して悩みました。

WowTalkのNative側の実装では、画面に表示したいデータがある場合、ActivityからAPIへリクエストするためのクラス(WebIf)へリクエストを送り、返ってきたレスポンスをViewに渡して表示していますが、Flutterの実装では画面のルートViewにあたるindex.dartで子ビューで利用されるProviderを登録し、Providerが保持するBlocを使ってAPIへのリクエストと親ウィジェットから子ウィジェットへのデータの受け渡しを実現しています。

リンクをブラウザで開く方法

今回の対応で交換履歴からリンクを開く必要があったので、リンクをブラウザで開くコードを調べました。

まず①のコードでは上手くいきませんでした。

①上手くいかなかったコード

Future<void> _launchURL(String strUrl) async {
  final url = Uri.parse(strUrl);
  if (await canLaunchUrl(url)) {
    await launchUrl(url);
  }
}

②上手くできたコード

Future<void> _launchURL(String strUrl) async {
    if (await canLaunch(strUrl)) {
      await launch(strUrl);
    }
  }

原因を調べてみると、「launchUrl」メソッドとURLが有効か否かチェックする「canLaunchUrl」が、バージョン6.1.0から変更されたメソッドになっていました。今回使ってるのは、6.0.5バージョンなので②の書き方が正しかったです。

今回使ったライブラリーは「url_launcher | Flutter Package」です。

Debugについて

Flutterの開発中にDebugで実装確認する必要があり、BreakPointを設定し、手元の端末でDebugモードで動かしてみました。するとBreakポイントに止まりませんでした。

チーム内で相談したり、Googleで調べてみると、Emulatorだと上手くいくということが分かったので、Emulatorで試してみると、Debugが上手くできました。

改善する必要がある点としては、今回サンクス機能以外はNativeで開発しているので、WowTalkアプリと一緒にDebugができなくなっています。不具合対応やバージョンアップをするとまたFlutterのサンクスモジュールで別に対応してNative(wowtalk)アプリに混ぜる必要があります。

まとめ

Flutterを使って初めて開発しましたが、Flutterはすごく便利なフレームワークだと思いました。これからもFlutterを使って新しい機能開発をしたいと思います。

ワウテックには、多くの学びを得る機会と挑戦させてもらえる環境があります。
随時新メンバーを大募集中ですので、一緒に働ける方をお待ちしております!

f:id:wowtech-dev:20190313163146j:plain

参考

  1. URLを開くために使うライブラリー
    url_launcher
  2. カスタマイズができるドロップダウンライブラリー
    dropdown_button2awesome_dropdown