Loading...
Loading...
Flutter 프로젝트의 Data 레이어 패턴 — DataSource 인터페이스·구현, Repository, DTO와 도메인 모델 매핑, freezed 모델의 `fromJson`, 그리고 `rxdart`의 `BehaviorSubject`로 만드는 반응형 저장소. "DataSource 만들기", "Repository 구현", "리포지토리", "로컬/원격 데이터 소스", "BehaviorSubject", "스트림 저장소", "DTO 매핑", "fromJson" 같은 표현에 트리거합니다.
npx skill4agent add junsuk5/survival-flutter-skills flutter-data-layerResult<D, E extends Error>// 단일 소스 → DataSource
abstract interface class RecipeDataSource {
Future<List<Map<String, dynamic>>> getRecipes();
}
// 도메인 관점의 접근 API → Repository
abstract interface class RecipeRepository {
Future<List<Recipe>> getRecipes();
Future<Recipe?> getRecipe(int id);
}RecipeMaplib/domain/package:flutter/...freezed// lib/domain/repository/recipe_repository.dart
abstract interface class RecipeRepository {
Future<List<Recipe>> getRecipes();
Future<Recipe?> getRecipe(int id);
}freezedjson_serializable// lib/domain/model/recipe.dart
import 'package:freezed_annotation/freezed_annotation.dart';
import 'recipe_ingredient.dart';
part 'recipe.freezed.dart';
part 'recipe.g.dart';
class Recipe with _$Recipe {
const factory Recipe({
required String category,
required int id,
required String name,
required String image,
required String chef,
required String time,
required double rating,
required List<RecipeIngredient> ingredients,
(false) bool isFavorite,
}) = _Recipe;
factory Recipe.fromJson(Map<String, Object?> json) => _$RecipeFromJson(json);
}dart run build_runner build --delete-conflicting-outputs// lib/data/data_source/remote/remote_recipe_data_source_impl.dart
class RemoteRecipeDataSourceImpl implements RecipeDataSource {
Future<List<Map<String, dynamic>>> getRecipes() async {
// http 호출 또는 mock
await Future.delayed(const Duration(microseconds: 500));
return _mockData['recipes']!;
}
}// lib/data/data_source/local/default_local_storage.dart
class DefaultLocalStorage implements LocalStorage {
// SharedPreferences, sqflite 등으로 구현
}RecipeDataSourceLocalStorageRemoteRecipeDataSourceImplDefaultLocalStorageImplImplMap<String, dynamic>// lib/data/repository/mock_recipe_repository_impl.dart
class MockRecipeRepositoryImpl implements RecipeRepository {
final RecipeDataSource _recipeDataSource;
const MockRecipeRepositoryImpl({
required RecipeDataSource recipeDataSource,
}) : _recipeDataSource = recipeDataSource;
Future<List<Recipe>> getRecipes() async {
final recipes = await _recipeDataSource.getRecipes();
return recipes.map(Recipe.fromJson).toList();
}
Future<Recipe?> getRecipe(int id) async {
final recipes = await getRecipes();
return recipes.where((e) => e.id == id).firstOrNull;
}
}get_itRepositorydomaindataRecipe.fromJsonrxdartBehaviorSubject// lib/data/repository/mock_bookmark_repository_impl.dart
class MockBookmarkRepositoryImpl implements BookmarkRepository {
final _ids = <int>{2, 3};
final _controller = BehaviorSubject<Set<int>>();
MockBookmarkRepositoryImpl() {
_controller.add(_ids);
}
Stream<Set<int>> bookmarkIdsStream() => _controller.stream;
Future<void> toggle(int id) async {
if (_ids.contains(id)) {
_ids.remove(id);
} else {
_ids.add(id);
}
_controller.add(_ids);
}
}FutureBehaviorSubject// lib/domain/use_case/get_saved_recipes_use_case.dart
Stream<List<Recipe>> execute() async* {
final recipes = await _recipeRepository.getRecipes();
await for (final ids in _bookmarkRepository.bookmarkIdsStream()) {
yield recipes.where((e) => ids.contains(e.id)).toList();
}
}lib/domain/use_case/<verb>_<noun>_use_case.dartexecute(...)Future<T>Future<Result<D, E>>Stream<T>class GetSavedRecipesUseCase {
final RecipeRepository _recipeRepository;
final BookmarkRepository _bookmarkRepository;
const GetSavedRecipesUseCase({
required RecipeRepository recipeRepository,
required BookmarkRepository bookmarkRepository,
}) : _recipeRepository = recipeRepository,
_bookmarkRepository = bookmarkRepository;
Stream<List<Recipe>> execute() async* { ... }
}lib/domain/model/<name>.dartlib/domain/repository/<name>_repository.dartabstract interface classlib/data/data_source/{remote|local}/<name>_data_source_impl.dartlib/data/repository/<name>_repository_impl.dartBehaviorSubjectdiSetup()dart run build_runner build --delete-conflicting-outputsMap<String, dynamic>domain/package:flutter/material.dartRepository