Atrae Tech Blog

People Tech Companyの株式会社アトラエのテックブログです。

求人メディアGreen の Flutterの構成について

f:id:atrae_tech:20210218154509p:plain

こんにちは、エンジニアの @muttsu_623 です。

最近、開発を頑張っている自分へのご褒美として念願だった『左右分離型キーボード』のMISTEL『Barocco MD770 静音赤軸』を購入しました。

購入してからまだ2週間くらいなのでまだ効果を実感できているわけではありませんが、肩が開かれた状態で姿勢良く開発を行うことができるため、長期的にみればいいお買い物になったかなと思います。



さて本題ですが、先日弊社の「求人サイト Green」のAndroidアプリをFlutterで作成しリリースしました。

Flutterのエンジニアの皆さんから想像以上の反響をいただきました。


私がFlutterの可能性に気づくことができ、ここまで着実にFlutterについてキャッチアップを行うことができたのは、私より先にFlutterコミュニティに貢献してくださった多くの方のおかげです。

ありがとうございます。

アプリをリリース後、今度は自分自身が実際にアプリをリリースした身として、 実際にどういう構成で運用しているのかをアウトプットすることにより、より多くの企業がFlutterでのアプリ制作を1つの選択肢に入れられ、 Flutterの採用事例が増えていき、よりコミュニティが活発になっていくのではないか と思いこの記事を執筆することにしました。

至らない点がありましたら、Flutterコミュニティを盛り上げていくためにも、ぜひ @muttsu_623 までご連絡いただけますと幸いです。




Flutterの基本的な書き方に関しては多くの記事が出ていると思うので今回はスキップします。

実際にどのような構成で作っているかに関して記載します。

宣伝

先日、一緒に開発を行なった先輩と一緒に社内でインタビューしていただきました。

- なぜ Flutter で開発したのか?
- リリースまでに苦労したことは?
- 開発中にこだわった点は?
- 今回の開発過程で凄かった点はズバリ何? 

などを開発裏話を赤裸々に語っておりますので、ご興味ある方はぜひこちらもご覧ください😊

note.com

また、私が所属している株式会社アトラエはこんな会社です。

speakerdeck.com


「求人メディアGreen」とは

株式会社アトラエで開発・運用している IT系人材に強みを持つ成功報酬型求人メディア です。

f:id:atrae_tech:20210218101956p:plain
Greenについて会社紹介資料より

f:id:atrae_tech:20210218102149p:plain
Greenについて会社説明資料より 2

詳細は こちら をご覧ください。


ディレクトリ構成

  • lib/ : Flutterのコードが存在しているディレクト
    • di/ : DIを行うためのファイル
    • domain/ : Flutterに依存しないピュアDartなクラス(Entity, Repository)
    • infrastructure/ : HTTP通信のクラス、ローカルDBのクラス、Daoクラス
    • presentation/ : View, State, State Controllerクラス
      • state_controller/ : State Controllerクラス
      • state/ : Stateクラス
      • ui/ : Viewクラス
    • util/ : Utilクラス
    • app_error.dart : 基本的にアプリ内で起きるエラーについては、AppError 型に変換しています。 
    • result.dart : 非同期処理の結果は Result 型に変換して利用しています。

AppError, Result<T> についてはコチラにまとめましたので、ぜひご覧ください。

zenn.dev


今回のFlutterでの開発には、私以外にサーバーサイドの経験が長いエンジニア2名、Androidの経験が長いエンジニアが1名参加しました。

参加してもらうエンジニア3名がスムーズにFlutterの開発に入れるよう、Flutterに依存したモジュールとFlutterに依存していない部分がわかりやすい構成になるように心がけました。


Controller, ViewModel, Usecase ⇄(Entity, Value Onject)⇄ Repository ⇄(Entity, Value Onject)⇄ Dao

上記の流れに関しては、開発しているプロジェクトがFlutterであっても、Goであっても、Kotlin(Android)であっても共通して行われる処理となり、基本的にはFlutterに依存したコードは薄くなります。


Repository, Dao それぞれは Interface になっているため、これらを継承した実体を Injection する際はFlutterや、DIライブラリに依存したコードが存在しますが、それ以外はピュアなDartのファイルたちになります。

言語仕様は多少勉強してもらうことになりますが、それ以外は普段から慣れている構成で作っていただける状態にしています。

State Controller, State, View は Flutterに依存したファイルたちになりますが、逆にいうと基本的にはそれらのみがFlutterに依存した状態になります。


状態管理

状態管理には StateNotifier, Riverpod を利用しています。

pub.dev

pub.dev


アプリ内で、求人情報State, メッセージスレッドState などのように扱いたい情報を State として管理するようにしています。

State Controller というクラスは、StateNotifier を継承してつくっており、このクラスが Repository を通してデータを取得や更新を行い、 State を変更します。

また、State の変更を扱いやすくするため、 flutter_hooks というライブラリも利用しています。

pub.dev

class JobOfferPage extends StatelessWidget  {
  ...
  return Center(
    child: HookBuilder(builder: (BuildContext context) {
      final companyName = useProvider(jobOfferStateControllerProvider.state.select((value) => value.companyName));
      return Text(companyName);
    }),
  );
}

class JobOfferState {
  const JobOfferState(this.id, this.companyName);

  final int id;
  final String companyName;
  ...
}

final jobOfferStateControllerProvider = StateNotifierProvider.autoDispose.family(
  (ref, int jobOfferId) => JobOfferStateController(
    JobOfferState(id: jobOfferId),
    ref.read(jobOfferRepositoryImplProvider),
  ),
);

class JobOfferStateController extends StateNotifier {
  JobOfferStateController(
    JobOfferState state,
    this.repository,
  ): super(state);

  final JobOfferRepository repository;

  void getJobOffer() {
    ...
  }
  
  ...

}

StateState Controller もあくまでデータの状態を管理するものとして、View に関するロジックは含んでいません。

そのため、用意しているメソッドたちも

void onButtonPressed() {
  ...
}

のようなものは生えていなく、

void getJobOffer() {
  ...
}

のようなメソッドのみ生えています。

View から直接 jobOfferStateController.getJobOffer というメソッドを呼ぶ形で実装を行なっています。

ViewState Controller から情報を得ることはなく、あくまでも State の状態が変わった通知を受けて、 View を再描画する MVC のような構成になっています。


StateNotifierState を変更する際に、メンバ変数が複数存在していると値を変更するのがめんどうなため、 freezed を利用し、変更したい値のみを変更すればいいようにしています。

pub.dev

Aというクラスの b というメンバ変数のみを変更したい場合

freezedを利用しない場合

// in A
class A {
  const A(this.b, this.c, this.d);

  final B b;
  final C c;
  final D d;
}

// in A Controller
class AController extends StateNotifier {
  const AController();
  fun change(B newB) {
    state = A(newB, state.c, state.d)
  }
}

freezedを利用したい場合(freezedは)

// in A
part 'a.freezed.dart'

@freezed
abstract class A with _$A {
  factory A({
    B b,
    C c,
    D d,
  }) = _A;
  A._();

// in A Controller
class AController extends StateNotifier {
  const AController();
  fun change(B newB) {
    state = state.copyWith(b: b);
  }
}

このように書くことができ、変更させたいメンバ変数のみを copyWith というメソッドに渡すことによって新しい state をつくることができます。


以前こちらの資料でも解説したので、ぜひご覧ください。

speakerdeck.com

※資料中で、State ControllerSingleton にしてしまっていると記載しましたが、こちらは RiverpodautoDispose というメソッドにより解消されました。


DI

上記のように、 Repository, DaoInterface になっているため、実体を Injection する必要がありました。

これに関しては、上記で紹介した Riverpod を利用しています。

Provider というライブラリのほうが一般的で、Riverpod はまだ v1.0.0 にもなっていないライブラリです。

pub.dev

しかしながら、

  • 実際にコードを書いてみて致命的なバグがないこと
  • Provider の作者が改めに開発したライブラリが Riverpodであること
  • 個人的には Riverpod のほうが直感的でわかりやすかった

という理由から、 Riverpod を問題なく利用しています。

Riverpodの具体的な使い方に関しては、こちらの記事が勉強になりました。

【神パッケージ】 Riverpod の使い方【Flutter】|いしかわ|note


APIリクエス

APIリクエストでは、dio というライブラリを利用しています。

pub.dev

一般的には、 http を選択されているところもあるかもしれませんが、Interceptorなどが利用しやすく、dio のほうがGitHub上でのstar数が圧倒的に多かったため、こちらを利用しています。

pub.dev

しかしながら、1点不安があり、Flutterのライブラリは現在null safety対応が進んでいるのですが、 dio に関してはメインのContributerたちがメッキリGitHubに現れなくなっており、その対応が進んでいなさそうな点です。

気にかけている人はもちろんいて、flutterchinaからflutter communityに移りそうな雰囲気はありますが、こちらは様子見が必要だなと思っています。


ローカルDB

ローカルDBでは、hive を利用しています。

pub.dev

こちらも一般的には、 sqflite というライブラリが利用されているのかなと思います。

pub.dev

ただ、sqflite はリファレンスを読んだときにどうしても直感的にわかりにくく、、

元々Androidの開発でRealmを利用していたこともあり、NoSQLのライブラリのほうが慣れていましたし、直感的にわかりやすいと思っていました。

色々とライブラリを見ましたが、 hive のリファレンスがしっかりしていたことや、元々開発していた人の手から離れ、しっかりと運用されたライブラリになっていたため、信頼感もあったので hive を利用することにしました。

ローカルDBの検討についてはこちらの記事が勉強になりました。

kabochapo.hateblo.jp


環境変数設定

よくある現象かと思いますが、ビルド環境を local, dev, qa, stage, production, ...etc と分けたいニーズはあるかなと思います。

弊社のプロダクトも、APIのBase URLやFirebaseの設定ファイルなど、環境によって設定したい環境変数や読み込みたいファイルの違いがありました。

具体的な方法は参考にさせてもらった以下の記事に任せ、ここでは避けますが、build時に dart-define というオプションをつけることにより、Flutter内やAndroid, iOSそれぞれのビルドスクリプト内で環境変数として読み取ることができます。

itnext.io

この環境変数を利用して、google-services.json の読み込みなどを制限しています。


Flutterで開発を行なってみてよかった点

1. なんといっても1つのソースコードiOS, Android両方のアプリがつくれる

今回はAndroidアプリのみをFlutterでリリースしましたが、今後はiOSもFlutterでリリースする予定です。

Android用につくっていたソースコードでそのままiOSビルドを行なったところ、ほぼ違和感がない状態でiOSでもスムーズにアプリが動きました。

自分はAndroid用にレイアウトを組み、ソースコードを書いていただけなのに、いざiOSでビルドしてもきれいに動いたときは本当に感動です。

Flutterを採用した理由の大きな1つとして、クロスプラットフォーム開発のチャレンジがありましたが、まさにクロスプラットフォームのメリットを感じた瞬間でした。


2. レイアウト構築が想像以上に楽

Reactなどの宣言的UIに慣れている人からしたら当たり前かもしれませんが、Androidで普段xmlでレイアウトを組んでいる身からするとこんなにも楽なのかと感動しました。

また、他の開発者の3人もすぐにレイアウト構築に取りかかることができたくらいハードルは低いと思っています。


3. ホットリロードが便利

こちらもReactの人にとっては馴染み深いと思いますが、これが非常に速いし便利だなと思っています。

AndroidでもApply Changesが存在しますが、それよりも断然早くUIやロジックの変更を反映することができるので楽です。


Flutterで開発を行なってみてつらかった点

1. ホットリロードがバグることがある

これはコードの書き方が悪く起こっている問題かもしれませんが、ホットリロードしてもうまく動かず、アプリをキルして再度リスタートさせるとうまくいくケースがあります。

あまりにもホットリロードを信じすぎるとそれによって沼にハマってしまうこともあるので注意してください。


2. 多少UIにニセモノ感がある

開発者でなければ気づかない気がしていますが、微妙にネイティブで標準で用意されているものと比べるとニセモノ感を感じてしまうUIはあります。

ほとんどないので気になりませんが、そういったものがあった場合はチームのデザイナーやPMと相談して決めるのがいいかなと思います。


3. UIをAndroidに寄せるか、iOSに寄せるか、出し分けるか

今回はAndroidアプリのみをFlutterで作成したため、すべて Material Design のものを利用しました。

しかしながら、日本においては iOSユーザーのほうが多いため、iOSもFlutterでつくるとなるとどちらのUIに寄せるべきかはチームで議論になると思います。

弊社としてもここは悩みどころになっています。

もしくは以下のリポジトリを参考にしながら、iOSAndroidでUIを分けることも選択肢の1つになってくるかなと感じています。

github.com


最後に

Flutterで開発を行なったことにより、当初の目的通り、バラバラでソースコードを書くよりも、1つのソースコードに対して複数人が関わることができるようになったため、開発速度やリソースの流動性を生み出しやすい状況になったかなと思っています。

弊社だけでなく、他の多くの会社にとってFlutterを採用することで今までとは全然違う開発体験を送ることができると思っておりますので、Flutterを採用する企業が増えていくように今後も発信していければと思います。


GreenのAndroidアプリはリリースしましたが、正直まだまだFlutterを通して開発的にやりたいこと、事業としてやりたいことが盛り沢山な状況です。

会社を大きくしていく上で、事業を伸ばしていく上で、技術をトコトン信じて、トコトンレベルの高い技術を勉強して、使えるようになって、新たに生み出したいと思っている、そんな人たちと一緒に働きたいと思っています。

今、Greenという10年以上続いているサービスをアプリをFlutter、バックエンドをGoでゴリゴリ開発していますし、wevoxやYentaという事業部でもそれぞれ技術力を活かして、会社と伸ばしていこうと必死に働いているエンジニアがたくさんいます。

自分の技術力を大切な仲間のために使って、より高いレベルへどんどん挑戦していきたいというエンジニアの方は、ぜひ気軽に連絡くださると嬉しいです。

Twitter @muttsu_623 への連絡でも構いません。お待ちしています。


会社説明資料

speakerdeck.com


中途採用募集ポジション一覧

note.com