【git submodule】複数のFlutterプロジェクトの共通コードをサブモジュール化する方法
Flutter大学にはスマホアプリ、Webアプリ、管理画面、FlutterWorkと4つのプロジェクトがあり、全て同じFirebaseを参照しています。そのため、Domain層やRepository層と言われる部分のコードがほとんど同じです(UserクラスやUserRepositoryなど)。そして全部Flutterで作られています。
スマホアプリ | web | 管理画面 | FlutterWork |
---|---|---|---|
つまり、同じようなFlutterのコードが全てのリポジトリにあり、こっちを更新したらあっちを更新しなければいけないという状態でした。
なので共通化しました。
共通部分のFlutterのコードを切り出して1つのpackageとし、git submoduleで連携することで、同じコードを使いまわせるようにした方法をこの記事に記したいと思います。
結論
先にどういう状態になったのか紹介します。
下図のファイル一覧にsalon_app_commonsというリンクが表示されているのがわかると思います。こちらがサブモジュールで、リンクをクリックすると共通コード(salon_app_commons)のリポジトリに飛びます。
アプリのリポジトリ
webのリポジトリ
共通部分(salon_app_commons)
2つのリポジトリはsalon_app_commonsをサブモジュールで持ち、同じコードを使いまわせます。以下みたいに呼び出して使うイメージです。
import 'package:salon_app_commons/salon_app_commons.dart';
final _userRepo = UserRepository();
final User user = await _userRepo.fetch();
例えばUserなんてWebでもアプリでも管理画面でも呼び出すので同じコードでいいですよね。それがライブラリとして呼び出せるようになった感じです。
具体的な手順
以下の4手順で行います。
- ①共通化するコードの選定
- ②共通コードをまとめたpackageのFlutterプロジェクトを作成
- ③使いたいプロジェクトで②のrepoをgit submoduleで接続する
- ④pubspec.yamlでサブモジュールをpathで呼び出せるようにする
この手順で1つずつ紹介していきます。
①共通化するコードの選定
元々のアプリとwebのリポジトリ構成は以下のようになっていました。
アプリ | web |
---|---|
基本的にクリーンアーキテクチャー的なディレクトリ構成にしていて、
- Presentation層...画面と画面ごとのロジック
- Domain層...Entity(単一要素)がメイン
- Repository層...Firebase等とのデータ通信
という役割に応じてフォルダ分けしていました。
アプリとwebで違うのは画面です。アプリはタブバー前提の画面構成ですが、webの方はマイページ中心の画面構成になっています。しかし、Domain層とRepository層は全くコードが同じなのでそこは共通化できるなと感じていました。ってか共通化しないと1度の更新で2度作業しなきゃいけないので手間ですよね。
ってことで、Domain層とRepository層を共通化するため、packageにして、その他便利クラスのUtilsやExtensionなども使いまわせそうなので含むことにしました。
最終的にはsalon_app_commonsのディレクトリ構成は以下のようになっています。
②共通コードをまとめたpackageのFlutterプロジェクトを作成
共通化できるコードは決めたので、アプリのコードをそのままコピーしてきて、packageを作りましょう。
AndroidStudioの場合は以下みたいに最初の選択肢が出ると思いますので、ここで、Flutter Packageを選択してください。
salon_app_commonsという名前のpackageで作った場合は、デフォルトでsalon_app_commons.dartができます。ソイツが全てのファイルをexportしてくれる役割としたいと思います。
以下のようにアプリのリポジトリのdomain、repositoryなどのフォルダもドラッグ&ドロップで突っ込みます。
salon_app_commons.dartのファイルでは、以下のようにそれぞれのファイルをexportして、このライブラリを使う側はsalon_app_commons.dartさえimportすれば全部のファイルを利用できるようにしておきます。
library salon_app_commons;
export 'package:salon_app_commons/domain/user.dart';
export 'package:salon_app_commons/extensions/list_extension.dart';
export 'package:salon_app_commons/repository/user_repository.dart';
export 'package:salon_app_commons/utils/format_utils.dart';
export '../utils/url_utils_native.dart' if (dart.library.html) '../utils/url_utils_web.dart';
補足:ネイティブアプリとwebの分岐
お気づきの方もいるかもしれませんが、上記のsalon_app_commons.dartで以下のようなexportをしています。
export '../utils/url_utils_native.dart' if (dart.library.html) '../utils/url_utils_web.dart';
これは、ネイティブアプリとwebで別のファイルをexportするというテクニックです。
import 'dart:html';
などFlutter Webでは使うけどアプリでimportするとビルドエラーになる物をうまくそれぞれのプラットフォームで動くように回避させるテクニックです。
以下を参考にしています。
また、Flutter大学の方は以下の松丸さんの勉強会の動画も参考になります。
③使いたいプロジェクトで②のrepoをgit submoduleで接続する
上記で作ったpackageをgithubにアップします。(↓Flutter大学の人は見れます)
これをsalon_appやsalon_app_webなどのリポジトリにsubmoduleとして導入します。
以下のコマンドです。
$ git submodule add git@github.com:flutteruniv/salon_app_commons.git
参考
次回以降のclone時
また、違うPCからや別の人がcloneしたい時は以下のコマンドで同期できます。
$ git submodule update --init --recursive
GithubAction対応
GithubActionを使ってる場合はcheckoutするときに以下のようにsubmodulesのoptionをつけてやると、submoduleも一緒にupdateしてくれます。GithubのPersonal Access TokenやSSH Keyは必要ですが。
- name: Checkout
uses: actions/checkout@v3
with:
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
submodules: recursive
参考
④pubspec.yamlでサブモジュールをpathで呼び出せるようにする
最後に、salon_app_commonsを使うプロジェクトのpubspec.yamlでpath指定をして、submoduleで取り込んだフォルダを参照できるようにして、flutter pub get
してあげれば完了です。
go_router: ^3.0.6
salon_app_commons:
path: salon_app_commons
まとめ
以上です。
Flutter大学のGitHub Organizationに入っていれば、これらのコードは見れますので、Flutter大学生は直接見てみてください。また、新規の入学もお待ちしております!
Discussion