概要
約半年間、独学でFlutterを勉強しました。
自分なりに開発の型が見えてきたのでGitのリポジトリと記事を残したいと思います。
本記事はその概要編です。
flutterプロジェクトの開始時に作成されるサンプルアプリを置き換えたもので、基本的な仕様やUIは一緒ですが、カウントを1つ進めるたびにsharedPreferencesに現在のカウントが保存されるようになっています。
デザインパターン
こちらの大変わかりやすい記事を参考にさせていただきました!
私が現在作成している別のアプリはLayer-firstを採用しているのですが、コード量が増えて見通しが悪くなってきたため、今回公開したリポジトリでは勉強も兼ねてFeature-firstを採用しています。
規模に応じて使い分けることが使い分ける必要があるなと感じます。
おおまかなディレクトリ構成
lib
├ features/
│ ├ common_utils/
│ └ #feature-1
│ ├ application/
│ ├ domain/
│ └ infrastructure/
├ presentation/
└ main.dart
レイヤードアーキテクチャに関しては、前述の記事内で詳しく(かつ非常にわかりやすく!)説明がされているので、そちらの記事をご参照ください。
(私もまだ勉強中)
Application
Domain
Infreastructure
Presentation
の各層に対して、本記事とは別の個別記事で実装内容を書き残していこうと思いますが、main.dartとfeatures直下にあるcommon_utilsに関してはどの層にも属していないので本記事で触れようと思います。
(設定で変更できるようなので、presentation/router内に入れてもいいかもしれません)
main.dart
void main() {
Widget app = ScreenUtilInit(
designSize: const Size(baseWidth, baseHeight),
minTextAdapt: true,
splitScreenMode: true,
builder: (context, child) {
return const MyApp();
},
);
runApp(ProviderScope(overrides: prdProviderOverrides, child: app));
}
ScreenUtils
ScreenUtilInit()でScreenUtilを使用する準備をしています。
このパッケージを利用するとデバイスのサイズに応じて高さ、幅、文字サイズなどの値を柔軟にリサイズしてくれるようになります。
(詳しくはpresentation層編の記事で後述します)
runApp
runAppにrepositoryをoverrideするためにProviderScope()を渡してあげています。
usecaseでrepotiroryを呼び出すとこは全て下記のような未実装エラーを投げるようになっているので、ここでoverrideしてあげないとrepository層は機能しません。
final counterRepositoryProvider = Provider<CounterRepository>(
(_) => throw UnimplementedError(),
);
abstract interface class CounterRepository {
//保存したcountを取得する
Future<Count?> fetch();
//countを保存する
Future<void> saveCount(Count count);
}
下記のように環境に応じたrepositoryのOverriesを用意しておき、flavorなどを利用して条件分岐すれば簡単にrepositoryを切り替えることができます!
final prdProviderOverrides = [
counterRepositoryProvider.overrideWithValue(PrdCounterRepository())
];
final mockProviderOverrides = [
counterRepositoryProvider.overrideWithValue(MockCountRepository())
];
このmain.dartではprdProviderOverridesがProviderScopeのoverridesに渡されているので、counterRepositoryが呼ばれている個所では代わりにPrdCounterRepositoryが呼ばれることになります。
common_utils
各featuresで共通で使用するMixinなどの機能を配置しています。
MixinについてはApplication層編で触れているので、そちらをご覧ください。
page_state
Presentation層で使用するstateを提供するproviderを配置しています。
このテンプレートではusecaseの実行中かどうかをboolで保持しているoverlayLoadingProviderと現在アプリに適用されているthemeを保持しているthemeProviderがあります。
//アプリに適用されるthemeを保持するプロバイダー
final themeProvider =
StateProvider<ThemeData>((ref) => AppTheme.lightThemeData());
themeはapp.dartで下記のように適用されるので、この値が別のthemeで更新されるとアプリのデザインが切り替わります。
@override
Widget build(BuildContext context, WidgetRef ref) {
//router
final router = ref.watch(goRouterProvider);
//theme
final ThemeData themeData = ref.read(themeProvider);
return MaterialApp(
//ページをタップするとfocusを外す
home: GestureDetector(
onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
//loading画面をメインの画面に重ねる
child: Stack(children: [
MaterialApp.router(
//以下の階層でthemeが適用される
theme: themeData,
routerConfig: router,
),
const LoadingScreen(),
])));
}
おわりに
概要編は以上になります。
随時投稿予定の以下の記事もよろしくお願いします!