Tech Blog

グローバルな家族アプリFammを運営するTimers inc (タイマーズ) の公式Tech Blogです。弊社のエンジニアリングを支える記事を随時公開。エンジニア絶賛採用中!→ https://timers-inc.com/engineering

Flutter初心者たちが3ヶ月で新規アプリをリリースした話 #Flutter

ネイティブエンジニアの桐山です。

Timersでは新規事業として、毎月無料でましかくプリントを印刷できるサービスを始めました! 新規アプリでFlutterを採用し、3ヶ月でiOSAndroidアプリをリリースした話の概要編をお届けします。

はじめに

この度弊社で新しい家族向けアプリをFlutterで作りました!

https://famm.us/ja/print/top

apps.apple.com

play.google.com

おおまかなフロー

Why Flutter?

弊社では元々家族アルバムアプリFamm年賀状アプリをネイティブで開発しており、写真を扱うネイティブ開発の知見も人的リソースもありました。新規アプリの開発するにあたって、ネイティブで開発した方が良いのでは?という意見が多かったのですが、Fammプリントは早くMVPでリリースしたいという要求があったため、Flutterが候補に上がってきました。

実際、年明けに1週間がっつりFlutterを触ってみると、ネイティブとさほど遜色ないUX、開発工数が短縮できるという展望が見えたので、Flutterを採用することに決めました。

また、今後新規アプリをスピーディーに両OSのアプリを機能をそろえた状態で検証を行うための、技術的な引き出しとして持っておきたいという会社目線での目的もありました。

余談ですが、弊社では月1で全社員がリモートで集まって各事業の様子や目標の進捗などを共有する場があるのですが、Flutterで新規アプリを作ると決まった後にLTでFlutterの紹介を行いました。

開発体制

  • iOSエンジニア2名
  • Androidエンジニア1.1名(1名は育休しつつ参加のため、0.1名換算※)

全員Flutterでのアプリ開発経験はありませんでした。

※ 詳細についてはこちら note.com

技術選定

紆余曲折はありながら最終的に以下のような構成になりました。1つ1つ解説していきます!

アーキテクチャ

  • MVVM + UseCase
    • BlueprintアプリなどもMVVMを採用しており、ベンチマークとなるサンプルがあった方が開発も進めやすいことから、採用しました。
    • Androidアプリの名残から、ViewModelからロジックを追い出したかったのでUseCaseも場所によって採用しています。

状態管理、画面遷移、依存性注入

おそらくFlutter開発の最初の肝となる部分になるかと思いますが、FammプリントではGetXを採用しました。

github.com

諸々については別の記事で解説をする予定です。

そのほか使用したライブラリ

Alice

https://pub.dev/packages/alice

面倒な設定なしに、通信ログを表示してくれます。

Cached network image

https://pub.dev/packages/cached_network_image

Imageクラスにエクステンションをして共通コンポーネント化しています。

extension ImageExtensions on Image {
  static CachedNetworkImage cachedNetwork(String url) => CachedNetworkImage(
        imageUrl: url,
        useOldImageOnUrlChange: true,
        cacheKey: url,
        errorWidget: (context, url, error) {
          debugPrint(error.toString());
          return const Center(
            child: Icon(
              Icons.image_not_supported_rounded,
              color: AppColors.secondaryText,
            ),
          );
        },
      );
}

dio

https://pub.dev/packages/dio

GetXでも通信クラスは用意されているのですが、謎のエラーに遭遇して解決できなかったかつ先ほど紹介したAliceを組み合わせられなかったことから、通信においてはdioというライブラリを使用しています。

freezed

https://pub.dev/packages/freezed

toJson/fromJson/toStringなどのボイラープレートをコマンドひとつで自動生成してくれます。

また、UIの状態を表現するデータオブジェクトを作るのにもとても便利です。

@freezed
class UploadUiState with _$UploadUiState {
  const factory UploadUiState.idle() = Idle;

  const factory UploadUiState.success(
    List<OrderImage> orderImages,
  ) = UploadSuccess;

  const factory UploadUiState.processing() = Processing;

  const factory UploadUiState.uploading(
    int totalCount,
    int progress,
  ) = Uploading;

  const factory UploadUiState.complete() = Complete;
}

lottie

https://pub.dev/packages/lottie

弊社では初めてイラストアニメーションを取り入れたUIを提供しました。 このライブラリではLottieのアニメーション定義ファイルを1行で描画してくれます。

Lottie.asset('assets/lottie/hoge.json')

ネイティブコード、OS差分について

ネイティブコードは今のところ0%

  • iOSInfo.plist・Build PhaseやAndroidbuild.gradleは触るので最低限そこだけ理解できればiOS/Android開発未経験でも十分開発を進めることが可能な印象です。
  • 画像処理でうまくいかない事象があり、その部分をネイティブコードで書くことで解決できないかを検討中です。

OS差分、OS判定を入れている箇所

  • 通信時のUserAgent
  • 分析に必要なデバイス名、バージョン情報の取得
  • Push通知許諾、写真アクセス許諾、IDFA許諾アラートをiOSのみ表示
  • ボタンの角丸
  • アラート、ダイアログ、ナビゲーションバー、ボトムナビゲーション、ローディングをOSごとに出しわけ
  • アイコンの出しわけ
  • Push通知受信設定

UIについてはできるだけそれぞれのOSで自然に見えるようにOS判定を入れて表示の出しわけをしていますが、割り切ってOS共通で同様のUIにすることでOS判定は上記からかなり減らせると思います。

Test

静的解析

Unitテスト

  • ViewModelでロジックがある部分は基本的にUnitテストを書く方針にしています。
  • Mockオブジェクトの生成にはMockitoを使用しています。

UIテスト

  • Flutter公式の呼び方はIntegration Testとなっています。
  • 新規会員登録〜注文完了までのフローでUIテストを書くことに挑戦したのですが、OSが表示するUI(アラート、写真ピッカーなど)を操作できないため一旦保留になりました。
    • github.com
    • issueにも上がっているので状況はウォッチしていきたいです。

開発スピード向上のための事前準備

UIコンポーネントの定義

FammのDesign Systemに則り、あらかじめ使用するであろうコンポーネントを定義することでスムーズな開発ができたと思います。

  • 色、テーマ
  • フォント
  • スペース
  • ボタン
  • テキスト
  • テキストフィールド(テキスト入力・選択)
  • アラート、ダイアログ、ナビゲーションバー、ボトムナビゲーション、ローディングをOSごとに出しわけするラッパークラス

CI(Bitrise)

テストアプリを配信するワークフロー

静的解析結果はDangerがFlutter対応していなかったため、GitHubのPRにコメントする運用

  • flutter analyze --write=flutter_analyze_report.txtを実行して結果をtxtに保存
  • envmanを使用して環境変数に結果を保存
#!/usr/bin/env bash

RESULT=$(<$BITRISE_SOURCE_DIR/flutter_analyze_report.txt);

if [ -z "$RESULT" ]; then
  RESULT="No issue."; // 空文字だとGitHubコメント時にエラーになる
fi

envman add --key FLUTTER_ANALYZE_RESULT --value "$RESULT"

開発を進める中で工夫したこと

開発に必要な情報をまとめたドキュメント

弊社では知見や設計、仕様、手順書などをesaにまとめる文化があるのですが、今回のFlutter開発においても1つのesaに情報を集約することであれなんだっけ?の時間を減らすことができたと思います。

esa.io

目次

コードレビュー

iOSAndroidアプリでは基本的に1人のApproveがあればマージできる運用にしていますが、下記の理由により最低Approveを2人にする運用に変更しました。

  • 知識の横展開を広く行いたかった
  • 全員がFlutter初心者のため目を増やしたかった

モブ会

毎週火曜木曜で45分定期モブ会をセッティングしています。アジェンダがない会はスキップする運用ですが、リリースから1ヶ月経った今もスキップ率はかなり低めなので価値は高いと感じています。

話していること例

  • スプリント中に実装している内容で詰まっている箇所の相談
  • 設計の相談
  • 実装した内容で今後使えそうなものの共有
  • Flutter最新情報の共有
  • YouTubeの動画鑑賞

Flutter Widget of the Week を見て取り入れたものの一部

www.youtube.com

www.youtube.com

www.youtube.com

www.youtube.com

最後に

実際にFlutterでアプリ開発を進めていくうちに、以前のiOS開発にはもう戻れなくなってしまいました(後日この辺りの話も記事にできればと思います)。

Flutterを採用するメリット・デメリットはそれぞれ多々あるかと思いますが、メンバーやプロダクトの特徴によって適切に技術選定することをおすすめします。

今後のFlutterの発展が楽しみです!

PR

子育て家族アプリFammを運営するTimers inc.では現在エンジニアを積極採用中! オンラインでの面談やカジュアルランチなどもやってますので是非お気軽にご連絡ください!

採用HP: https://timers-inc.com/recruit/engineering

timers-inc.com

timers-inc.com

Timersでは各職種を積極採用中!

急成長スタートアップで、最高のものづくりをしよう。

募集の詳細をみる