RP:スタブと分ける - Ki-Kobayashi/flutter_wiki GitHub Wiki
main.dart ... ⇒ Screen ⇔ Controller ⇔ Service ⇔ Repository ⇔ DataSourceIF ⇔ DataSourceImpl(Stub / Remote)
.
DataSourceIF を作成し、実装として、リモート と ローカル で分ける
repository では、DataSourceIF を見る ことで、 リモート と ローカル化を気にせず利用できる
part 'article_rest_client.g.dart'; 🚨忘れずに!
@riverpod
ArticleDataSource articleDataSource(ArticleDataSourceRef ref) {
throw UnimplementedError(); 👈 未実装状態にする(main.dartのProviderScopeで注入)
}
abstract class ArticleDataSource {
Future<ApiResponse<CategoryNames>> getCategories();
/// 記事リストを取得する
Future<ApiResponse<ArticleResponse>> postCategoryArticles(
FindArticleListRequest reqBody);
}
.
抽象 :DIO + Retrofit 使用の実際のIF
part 'article_rest_client.g.dart'; 🚨忘れずに!
@riverpod
ArticleRestClient articleRestClient(ArticleRestClientRef ref) {
return ArticleRestClient(ref.read(dioProvider));
}
@RestApi()
abstract class ArticleRestClient implements ArticleDataSource {
factory ArticleRestClient(Dio dio) = _ArticleRestClient;
@override
@GET('/categories')
Future<ApiResponse<CategoryNames>> getCategories();
/// 記事リストを取得する
@override
@POST('/category-articles')
Future<ApiResponse<ArticleResponse>> postCategoryArticles(
@Body() FindArticleListRequest reqBody,
);
}
.
実装:スタブ用のデータソースを返却するクラス)
part 'stub_article_data_source.g.dart'; 🚨忘れずに!
@riverpod
StubArticleDataSource stubArticleDataSource(StubArticleDataSourceRef ref) {
return StubArticleDataSource();
}
class StubArticleDataSource implements ArticleDataSource {
@override
Future<ApiResponse<CategoryNams>> getCategories() async {
final content = json.decode(await rootBundle
.loadString('assets/stub/stub_categories.json'));
// 👇Json変換がネストしている時は、以下のように書く
return ApiResponse.fromJson(
content,
(Object? json) => CategoryNames.fromJson(json as Map<String, dynamic>),
);
}
/// 記事リスト取得
@override
Future<ApiResponse<ArticleResponse>> getArticles(
FindArticleListRequest reqBody) async {
var dataPath = '';
switch (reqBody.context) {
case '.*':
dataPath = 'assets/stub/stub_articles.json';
case 'お気に入り':
dataPath = 'assets/stub/stub_articles_favorite.json';
}
final content = json.decode(await rootBundle.loadString(dataPath));
return ApiResponse.fromJson(
content,
(Object? json) => ArticleResponse.fromJson(json as Map<String, dynamic>),
);
}
final content = json.decode(await rootBundle.loadString(
'assets/stub/stub_articles.json')) as Iterable<Article>;
// ✨List <Article> が返却
return content.map((item) => Article.fromJson(item)).toList();
.
part 'article_repository.g.dart'; 🚨忘れずに!
// 🚨ArticleDataSource か ここ に以下を定義するかはプロジェクト次第
@riverpod
ArticleDataSource articleDataSource(ArticleDataSourceRef ref) {
throw UnimplementedError();
}
@riverpod
ArticleRepository articleRepository(ArticleRepositoryRef ref) {
final repo = ArticleRepository(client: ref.read(articleDataSourceProvider));
return repo;
}
class ArticleRepository {
ArticleRepository({
required ArticleDataSource client,
}) : _client = client;
final ArticleDataSource _client;
/// 記事リストを取得
Future<ArticleResponse> fetchArticles({
required FindArticleListRequest reqBody,
}) async {
try {
logger.i('fetchArticles REQUEST: $reqBody');
final res = await _client.getArticles(reqBody);
if (res.statusCode != ApiStatusCode.success.statusCode) {
logger.e(
'fetchArticles statusCode: ${res.statusCode}, errMessage: ${res.message}');
throw NetworkExceptions.getApiError(
ApiStatusCodeError(apiStatusCode: res.statusCode),
);
}
logger.i('fetchArticles RESPONSE: $res');
return res.payload;
} on Exception catch (e) {
throw NetworkExceptions.getApiError(e);
}
}
}
.
bool isDevArticle = true;
Future<void> main() async {
// 💡UIの構築やライフサイクルイベント管理するもの
// ・ runApp()前に初期化したいものがあるなら必須
// (非同期処理、外部サービスの初期化時は必須)
// ・ 非同期処理完了前にウィジェットがビルドされることを防ぐ
WidgetsFlutterBinding.ensureInitialized();
// アプリ縦方向固定
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
]);
// google fontsの設定
GoogleFonts.config.allowRuntimeFetching = kDebugMode;
runApp(
ProviderScope(
overrides: [
sharedPreferencesProvider.overrideWithValue(
await SharedPreferences.getInstance(),
),
flutterSecureStorageProvider.overrideWithValue(
const FlutterSecureStorage(),
),
// 💡リリース時は、stubを見ない(RestClientのみにする)
// 記事データソース
articleDataSourceProvider.overrideWith((ref) => ref.watch(isDevArticle 👈ここ追加
? stubArticlesSourceProvider
: articleRestClientProvider)),
],
child: const Application(),
),
);
}
.
.
.
.
.
.
.
.
.