Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
flutterkaigi_2024.pdf
Search
Kyohei Ito
November 21, 2024
Programming
1.8k
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
flutterkaigi_2024.pdf
Kyohei Ito
November 21, 2024
More Decks by Kyohei Ito
See All by Kyohei Ito
flutter_kaigi_2025.pdf
kyoheig3
2
1k
layerx_20241129.pdf
kyoheig3
2
520
flutter_kaigi_2021.pdf
kyoheig3
0
1.2k
flutter_kmm_1.pdf
kyoheig3
1
1.2k
ca.swift_10.pdf
kyoheig3
0
720
iosdc_2018.pdf
kyoheig3
2
3.2k
orecon_vol1.pdf
kyoheig3
4
1.8k
iosdc_2017.pdf
kyoheig3
4
940
ca.swift_2.pdf
kyoheig3
9
1.4k
Other Decks in Programming
See All in Programming
1B+ /day規模のログを管理する技術
broadleaf
0
120
スマートグラスで並列バイブコーディング
hyshu
0
270
Vite+ Unified Toolchain for the Web
naokihaba
0
410
TAKTでAI駆動開発の品質を設計する
j5ik2o
7
1.6k
Mujeres en SEO Summit 2026 - Greatest Disaster Hits en Web Performance
guaca
0
220
ランチタイムLT会3周年!ランチタイムLT会を3年間続けられたお話
y0hgi
1
120
[2026年度第1回ORセミナー] 計画最適化ベンチャーと競技プログラミング人材
terryu16
0
280
フロントエンドとバックエンドで「1文字」を揃えよう
youkidearitai
PRO
0
780
Strategic Design in the Frontend: Moduliths & Micro Frontends @DDDEurope
manfredsteyer
PRO
0
140
Agentic UI
manfredsteyer
PRO
0
220
吝嗇家のためのAI活用 / AI development for miser - ChatGPT + Issue Driven Development
tooppoo
0
150
技術記事、 専門家としてのプログラマ、 言語化
mizchi
14
7.2k
Featured
See All Featured
Scaling GitHub
holman
464
140k
Reflections from 52 weeks, 52 projects
jeffersonlam
356
21k
Game over? The fight for quality and originality in the time of robots
wayneb77
1
210
The Cult of Friendly URLs
andyhume
79
6.9k
Automating Front-end Workflow
addyosmani
1370
210k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
133
19k
Designing for Timeless Needs
cassininazir
1
270
What the history of the web can teach us about the future of AI
inesmontani
PRO
1
630
Sam Torres - BigQuery for SEOs
techseoconnect
PRO
0
300
Neural Spatial Audio Processing for Sound Field Analysis and Control
skoyamalab
0
360
Prompt Engineering for Job Search
mfonobong
0
360
WENDY [Excerpt]
tessaabrams
11
38k
Transcript
࣮ફతύοέʔδઓུ FlutterKaigi 2024
About Me ҏ౻ ګฏ גࣜձࣾαΠόʔΤʔδΣϯτ Github: KyoheiG3 X: KyoheiG3
ΞδΣϯμ •֓ཁ •ύοέʔδׂͷ࣮ྫ •ύοέʔδͷׂ •ςετʹ͍ͭͯ
ΞδΣϯμ •֓ཁ •ύοέʔδׂͷ࣮ྫ •ύοέʔδͷׂ •ςετʹ͍ͭͯ
ύοέʔδׂͷύλʔϯྫ • ΞϓϦέʔγϣϯͱ cli ͳͲͷπʔϧ • ௨৴෦ͳͲͷ෦୯Ґ • ෳͷΞϓϦέʔγϣϯͱڞ௨ػೳ •
ػೳ୯Ґ • ϨΠϠʔ • ͚ͳ͍
ΞϓϦΛී௨ʹ։ൃ͢Δͱ ͜ͷܗʹͳΔ App ͚ͳ͍
෦୯ҐͰ͚Δ Γग़͞Εͨ෦ͷ୯ମς ετ͕Γ͘͢ͳΔ Network Location DB App
ڞ௨ػೳͷΓग़͠ Core ػೳΛڞ༗͢ΔΞϓϦ ͕૿͑Δͱෆཁͳػೳ͕૿ ͑Δ Core App
ػೳ୯ҐͰ͚Δ ଞͷػೳͷӨڹΛ͋·Γ ߟ͑ͳ͍͍ͯ͘໘ɺػೳ ಉ࢜ͷ࿈ܞ͕͍͠ App Core Noti fi cation Purchase
Login
ϨΠϠʔͰ͚Δ ֎ଆͷϨΠϠʔଆͷϨ ΠϠʔʹґଘ͍ͯ͠Δ Presentation App Application Infra Domain
ΫϦʔϯΞʔΩςΫνϟ ը૾ग़లɿThe Clean Code Blog by Robert C. Martin (Uncle
Bob) | The Clean Architecture
ΫϦʔϯΞʔΩςΫνϟ ґଘੑٯస App UI Driver Presenter Gateway Entity UseCase
• Ͳ͜ͰԿΛॲཧ͢Δ͔͕໌֬ʹͳ࣮ͬͯʹڧ੍ྗ͕ੜ·ΕΔ • DI ͕ඞਢͳͷͰࣗͣͱϢχοτςετͷΈ͕͏ • ςετͷࡍʹෆཁͳύοέʔδΛഉআͰ͖Δ • ύοέʔδͷॲཧΛฒྻʹߦ͑ΔΑ͏ʹͳΔ ׂ࣌ͷϝϦοτ
ׂ࣌ͷσϝϦοτ • ׳ΕΔ·Ͱࠞཚ͕ͪ͠ • ΠϯλʔϑΣʔεͳͲͷهड़͕૿͑Δ • ίʔυδϟϯϓ͕໘ʹͳΔ
ΞδΣϯμ •֓ཁ •ύοέʔδׂͷ࣮ྫ •ύοέʔδͷׂ •ςετʹ͍ͭͯ
None
riverpod • ΞϓϦશମͷঢ়ଶཧ • ґଘͷೖ͕༰қʢDIʣ • ϥϯλΠϜͰͳͯ͘ίϯύΠϧλΠϜͰܕͷΤϥʔ͕͔Δʢܕ҆શʣ
Ϣʔβ໊Λදࣔ͢Δ͚ͩͷ ΞϓϦΛ࣮ͯ͠ΈΔ • Repository ܦ༝Ͱσʔλ औಘ • UseCase ͰσʔλΛՃ •
UI ʹදࣔ
App ύοέʔδͳͲಛʹߟ͑ͣʹ࡞Δ
app/lib/src/repository/user.dart import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'user.g.dart'; @riverpod UserRepository userRepository(UserRepositoryRef ref) =>
UserRepository(ref); class UserRepository { const UserRepository(this.ref); final UserRepositoryRef ref; Future<User?> getUser() async { final response = await ref.read(dioProvider).user.fetch(); return User(response.id, response.name); } }
app/lib/src/state/user.dart import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../repository/user.dart'; part 'user.g.dart'; @riverpod class UserStateNotifier
extends _$UserStateNotifier { @override Future<UserState> build() async { final user = await ref.watch(userRepositoryProvider).getUser(); return UserState( user: user, loggedIn: user != null, ); } }
app/lib/src/widget/user_name.dart import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../state/user.dart'; class UserName extends
ConsumerWidget { const UserName({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final userState = ref.watch(userStateNotifierProvider); return userState.when( data: (state) { return Text('User name: ${state.user?.name}'); }, error: (error, _) { return Text('Error: $error'); }, loading: () { return const CircularProgressIndicator(); }, ); } }
ΞϓϦ࣮ߦ UserRepository ͕ฦͨ͠ Ϣʔβ໊͕දࣔ͞ΕΔ
App App ϨΠϠʔ͝ͱʹύοέʔδΛ͚Δ
ϨΠϠʔͷྫ • ΠϯϑϥϨΠϠʔ • DB API ʹΞΫηεͨ͠Γ͢Δ • Repository
ͳͲ • ΞϓϦέʔγϣϯϨΠϠʔ • ϏδωεϩδοΫ͕ू͞ΕΔ • UseCase ͳͲ
ΫϦʔϯΞʔΩςΫνϟ ͜ͷ͋ͨΓΛࢀߟʹ ύοέʔδΛ໋໊
ύοέʔδͷྫ • ΠϯϑϥϨΠϠʔ • Gateway ύοέʔδΛ࡞ • UserRepository ΛҠಈ •
ΞϓϦέʔγϣϯϨΠϠʔ • UseCase ύοέʔδΛ࡞ • UserStateNoti fi er ΛҠಈ
Gateway ύοέʔδͷ࡞ $ flutter create -t package gateway $ mkdir
-p gateway/lib/src/repository $ mv app/lib/src/repository/user.dart gateway/lib/src/repository/user.dart
Gateway ύοέʔδͷ࡞ name: gateway description: Gateway for infrastructure Layer environment:
sdk: '>=3.3.0 <4.0.0' dependencies: riverpod_annotation: dev_dependencies: build_runner: riverpod_generator:
Gateway ύοέʔδͷ࡞ export 'src/repository/user.dart';
UseCase ύοέʔδͷ࡞ $ flutter create -t package usecase $ mkdir
-p usecase/lib/src/state $ mv app/lib/src/state/user.dart usecase/lib/src/state/user.dart
UseCase ύοέʔδͷ࡞ name: usecase description: UseCase for domain Layer environment:
sdk: '>=3.3.0 <4.0.0' dependencies: riverpod_annotation: gateway: path: ../gateway dev_dependencies: build_runner: riverpod_generator: HBUFXBZύοέʔδΛ ґଘͱͯ͠Ճ͢Δ
UseCase ύοέʔδͷ࡞ import 'package:riverpod_annotation/riverpod_annotation.dart'; // import '../repository/user.dart'; import 'package:gateway/gateway.dart'; part
'user.g.dart'; @riverpod class UserStateNotifier extends _$UserStateNotifier { @override FutureOr<UserState> build() async { final user = await ref.watch(userRepositoryProvider).getUser(); return UserState( user: user, loggedIn: user != null, ); } } HBUFXBZύοέʔδΛ JNQPSU͢Δ HBUFXBZύοέʔδͷ 3FQPTJUPSZΛࢀরͰ͖Δ
UseCase ύοέʔδͷ࡞ export 'src/state/user.dart';
ΞϓϦຊମ name: app description: Application environment: sdk: '>=3.3.0 <4.0.0' flutter:
'>=3.19.0' dependencies: flutter: sdk: flutter flutter_riverpod: usecase: path: ../usecase dev_dependencies: flutter_test: sdk: flutter 6TF$BTFύοέʔδΛ ґଘͱͯ͠Ճ͢Δ
ΞϓϦຊମ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; // import '../state/user.dart'; import 'package:usecase/usecase.dart';
class UserName extends ConsumerWidget { const UserName({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final userState = ref.watch(userStateNotifierProvider); return userState.when( data: (state) { return Text('User name: ${state.user?.name}'); }, error: (error, _) { return Text('Error: $error'); }, loading: () { return const CircularProgressIndicator(); }, ); } VTFDBTFύοέʔδΛ JNQPSU͢Δ VTFDBTFύοέʔδͷ 4UBUFΛࢀরͰ͖Δ
App Gateway UseCase UseCase Gateway App ґଘ͚ͩΛٯసͤ͞Δ
UseCase ύοέʔδͷґଘ name: usecase description: UseCase for domain Layer environment:
sdk: '>=3.3.0 <4.0.0' dependencies: riverpod_annotation: # gateway: # path: ../gateway dev_dependencies: build_runner: riverpod_generator: HBUFXBZύοέʔδΛ ґଘ͔Βআ͢Δ
UseCase ύοέʔδͷґଘ import 'package:riverpod_annotation/riverpod_annotation.dart'; // import '../repository/user.dart'; import 'package:gateway/gateway.dart'; part
'user.g.dart'; @riverpod class UserStateNotifier extends _$UserStateNotifier { @override FutureOr<UserState> build() async { final user = await ref.watch(userRepositoryProvider).getUser(); return UserState( user: user, loggedIn: user != null, ); } } HBUFXBZύοέʔδ͕ JNQPSUͰ͖ͳ͘ͳΔ HBUFXBZύοέʔδͷ 3FQPTJUPSZΛࢀরͰ͖ͳ͍
UseCase ύοέʔδͷґଘ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'user.g.dart'; @riverpod UserRepository userRepository(_) =>
throw UnimplementedError(); abstract interface class UserRepository { Future<User?> getUser(); } ͻͱ·ͣ&SSPSΛUISPX ͓ͯ͘͠
UseCase ύοέʔδͷґଘ import 'package:riverpod_annotation/riverpod_annotation.dart'; // import 'package:gateway/gateway.dart'; import '../repository/user.dart'; part
'user.g.dart'; @riverpod class UserStateNotifier extends _$UserStateNotifier { @override FutureOr<UserState> build() async { final user = await ref.watch(userRepositoryProvider).getUser(); return UserState( user: user, loggedIn: user != null, ); } } VTFDBTFͷϑΝΠϧΛ JNQPSU͢Δ VTFDBTFύοέʔδͷ 3FQPTJUPSZΛࢀরͰ͖Δ
ΞϓϦ࣮ߦ UseCase ͷ UserRepository ͷΠϯλʔ ϑΣʔε͕ throw ͨ͠Τ ϥʔ͕දࣔ͞ΕΔ
ΞϓϦ࣮ߦ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'user.g.dart'; @riverpod UserRepository userRepository(_) => throw
UnimplementedError(); abstract interface class UserRepository { Future<User?> getUser(); } વ͍͕ͭ͜දࣔ͞ΕΔʂ
riverpod ʹΑΔґଘղܾ • overrideWith ʢಈతɺ ref ͋Γʣ • overrideWithValue ʢ੩తɺ
ref ͳ͠ʣ • ProviderScope Ͱ্ॻ͖ͷઃఆ
Gateway ύοέʔδͷೖ name: gateway description: Gateway for infrastructure Layer environment:
sdk: '>=3.3.0 <4.0.0' dependencies: riverpod_annotation: usecase: path: ../usecase dev_dependencies: build_runner: riverpod_generator: VTFDBTFύοέʔδΛ ґଘʹՃ͢Δ
Gateway ύοέʔδͷೖ import 'package:usecase/usecase.dart'; final userRepositoryOverride = userRepositoryProvider.overrideWith(UserRepositoryImpl.new); class UserRepositoryImpl
implements UserRepository { const UserRepositoryImpl(this.ref); final UserRepositoryRef ref; Future<User?> getUser() async { final response = await ref.read(dioProvider).user.fetch(); return User(response.id, response.name); } } VTFDBTFύοέʔδͷ 3FQPTJUPSZΛ্ॻ͖͢Δ VTFDBTFύοέʔδͷ 3FQPTJUPSZΛܧঝ͢Δ
Gateway ύοέʔδͷೖ name: app description: Application environment: sdk: '>=3.3.0 <4.0.0'
flutter: '>=3.19.0' dependencies: flutter: sdk: flutter flutter_riverpod: gateway: path: ../gateway usecase: path: ../usecase dev_dependencies: flutter_test: sdk: flutter HBUFXBZύοέʔδΛ ґଘͱͯ͠Ճ͢Δ
Gateway ύοέʔδͷೖ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:gateway/gateway.dart'; class UserPage
extends StatelessWidget { const UserPage({super.key}); @override Widget build(BuildContext context) { return ProviderScope( overrides: [ userRepositoryOverride, ], child: const UserName(), ); } } HBUFXBZύοέʔδΛ JNQPSU͢Δ 3FQPTJUPSZΛ্ॻ͖͢Δ είʔϓΛઃఆ͢Δ
ΞϓϦ࣮ߦ Gateway ͷ UserRepository ͕ฦͨ͠ Ϣʔβ໊͕දࣔ͞ΕΔΑ͏ ʹͳΔ
Gateway σʔλͷྲྀΕ App UseCase
UseCase Gateway App ґଘͷํ
ύοέʔδߏ App Gateway UseCase
ґଘΛٯసͤ͞ΔͨΊʹΔ͜ͱ • ٯస͍ͤͨ͞ґଘॲཧͷΠϯλʔϑΣʔεΛఆٛ → UseCase • ఆٛͨ͠ΠϯλʔϑΣʔεΛܧঝͯ͠ґଘॲཧΛ࣮ → Gateway •
࣮ͨ͠ґଘॲཧΛೖ → App
ProviderScope ͷઃఆͷ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:gateway/gateway.dart'; class App
extends StatelessWidget { const App({super.key}); @override Widget build(BuildContext context) { return ProviderScope( overrides: [ artistRepositoryOverride, authRepositoryOverride, notificationRepositoryOverride, searchRepositoryOverride, userRepositoryOverride, userProfileRepositoryOverride, ], child: const Page(), ); } } ্ॻ͖ͷ͚ͩมΛઃఆ ͢Δඞཁ͕͋Δ
https://github.com/KyoheiG3/ override_pod override_pod
override_pod • ύοέʔδͷ Override มΛूΊͯྻͷมΛ࡞ • ΞϊςʔγϣϯͱδΣωϨʔλͷ 2ͭͷύοέʔδ
override_pod name: gateway description: Gateway for infrastructure Layer environment: sdk:
'>=3.3.0 <4.0.0' dependencies: riverpod_annotation: usecase: path: ../usecase override_pod_annotation: dev_dependencies: build_runner: riverpod_generator: override_pod_generator: BOOPUBUJPOΛՃ͢Δ HFOFSBUPSΛՃ͢Δ
Before import 'package:usecase/usecase.dart'; final userRepositoryOverride = userRepositoryProvider.overrideWith(UserRepositoryImpl.new); class UserRepositoryImpl implements
UserRepository { const UserRepositoryImpl(this.ref); final UserRepositoryRef ref; Future<User?> getUser() async { final response = await ref.read(dioProvider).user.fetch(); return User(response.id, response.name); } }
After import 'package:override_pod_annotation/override_pod_annotation.dart'; import 'package:usecase/usecase.dart'; @overridePod final pod = userRepositoryProvider.overrideWith(UserRepositoryImpl.new);
class UserRepositoryImpl implements UserRepository { const UserRepositoryImpl(this.ref); final UserRepositoryRef ref; Future<User?> getUser() async { final response = await ref.read(dioProvider).user.fetch(); return User(response.id, response.name); } } 0WFSSJEFͷม໊ࣗ༝ !PWFSSJEF1PEΛ͚Δ Πϯϙʔτ͢Δ
Before import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:gateway/gateway.dart'; class App extends
StatelessWidget { const App({super.key}); @override Widget build(BuildContext context) { return ProviderScope( overrides: [ artistRepositoryOverride, authRepositoryOverride, notificationRepositoryOverride, searchRepositoryOverride, userRepositoryOverride, userProfileRepositoryOverride, ], child: const Page(), ); } } શͯͷมΛઃఆ͍ͯͨ͠
After import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:gateway/gateway.dart'; class App extends
StatelessWidget { const App({super.key}); @override Widget build(BuildContext context) { return ProviderScope( overrides: gatewayPodOverrides, child: const Page(), ); } } ग़དྷ্͕ͬͨมΛ ઃఆ͢Δ͚ͩ
ΞδΣϯμ •֓ཁ •ύοέʔδׂͷ࣮ྫ •ύοέʔδͷׂ •ςετʹ͍ͭͯ
App Gateway UseCase App UI Driver Presenter Gateway Entity UseCase
ׂ͝ͱʹύοέʔδΛ͚Δ
• ΦϒδΣΫτ • ڞ௨ͷࣝΛදͨ͠ͷ Entity
Entity class User { const User({ required this.id, required this.name,
required this.createdAt, }); final int id; final String name; final String createdAt; }
• Repository ͷΠϯλʔϑΣʔεΛఆٛ • ෳͷ Repository ͷॲཧΛ࣮ߦ • ϢʔβΞϓϦͷঢ়ଶΛ AsyncNoti
fi er Ͱอ࣋ UseCase
UseCase @riverpod class UserStateNotifier extends _$UserStateNotifier { @override FutureOr<UserState> build()
async { final user = await ref.watch(userRepositoryProvider).getUser(); return UserState( user: user, loggedIn: user != null, ); } Future<void> logout() async { await ref.read(userRepositoryProvider).logout(); state = const AsyncValue.data( UserState( user: null, loggedIn: false, ), ); } }
• UseCase ʹґଘ • Driver ͷΠϯλʔϑΣʔεΛఆٛ • Repository ͷ۩ମతͳॲཧΛ࣮ Gateway
Gateway @overridePod final pod = userRepositoryProvider.overrideWith(UserRepositoryImpl.new); class UserRepositoryImpl implements UserRepository
{ const UserRepositoryImpl(this.ref); final UserRepositoryRef ref; Future<User?> getUser() async { final response = await ref.read(dioProvider).user.fetch(); return User( id: response.id, name: response.name, createdAt: response.createdAt, ); } Future<void> logout() async { await ref.read(dioProvider).user.logout(); } }
Gateway @overridePod final pod = locationRepositoryProvider.overrideWith(LocationRepositoryImpl.new); class LocationRepositoryImpl implements LocationRepository
{ const LocationRepositoryImpl(this.ref); final LocationRepositoryRef ref; @override Future<LocationPermission> requestPermission() { return ref.read(locationProvider).requestPermission(); } @override Stream<LocationPosition> get position { return ref.read(locationProvider).getPositionStream(distanceFilter: 10); } }
• Gateway ʹґଘ • Driver ͷ۩ମతͳॲཧͷ࣮ • σόΠεʹґଘͨ͠ύοέʔδΛϥοϓ Driver
Driver import 'package:geolocator/geolocator.dart' as geolocator; @overridePod final pod = locationProvider.overrideWithValue(LocationImpl());
class LocationImpl implements Location { @override Future<LocationPermission> requestPermission() { return geolocator.Geolocator.requestPermission() .then((value) => value.toLocationPermission()); } @override Stream<LocationPosition> getPositionStream({ required int distanceFilter, }) { return geolocator.Geolocator.getPosition( locationSettings: locator.LocationSettings( distanceFilter: distanceFilter, ), ).map((event) => event.toLocationPosition()); } }
Presenter (Controller) • UseCase ʹґଘ • Repository ͷॲཧݺͼग़͞ͳ͍ • ෳͷ
State ͔Β ViewModel Λ࡞ͯ͠ UI ʹฦ͢ • UI ͔ΒͷೖྗΛड͚͚Δ
Presenter (Controller) @riverpod Future<UserPageModel> userPageController(UserPageControllerRef ref) async { final userState
= await ref.watch(userStateNotifierProvider.future); return UserPageModel( userName: userState.currentUser.name, createdAt: userState.currentUser.createdAt, action: UserPageAction( logout: () async { await ref.read(userStateNotifierProvider.notifier).logout(); }, ), ); }
UI • Presenter ʹґଘ • ViewModel Λड͚औͬͯ Widget Ͱදࣔ •
Ϣʔβͷૢ࡞Λ Controller ʹ͑Δ
UI class UserPage extends StatelessWidget { const UserPage({ super.key, required
this.model, }); final UserPageModel model; @override Widget build(BuildContext context) { return Scaffold( body: Column( children: [ Text(model.userName), Text(model.createdAt), ElevatedButton( onPressed: model.action.logout, child: const Text('Logout'), ), ], ), ); } }
App • Gateway / Driver / UI ʹґଘ • ্ॻ͖ͨ͠ґଘΛ
ProviderScope Ͱೖ
App import 'package:driver/driver.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:gateway/gateway.dart'; import
'package:ui/ui.dart'; void main() { runApp( ProviderScope( overrides: [ ...driverPodOverrides, ...gatewayPodOverrides, ], child: const App(), ), ); }
Driver Gateway UseCase Presenter UI ॲཧͷྲྀΕ
ґଘͷํ UI UseCase Gateway Presenter Driver App
App UI Driver Presenter Gateway Entity UseCase ύοέʔδߏ
ΫϦʔϯΞʔΩςΫνϟ 'SBNFXPSL%FWJDF ʹؔΘΔ෦
Flutter σόΠεʹґଘ͍ͯ͠Δύοέʔδ App UI Driver
Presenter Gateway Entity UseCase Flutter σόΠεʹґଘ͍ͯ͠ͳ͍ύοέʔδ
App UI Driver Presenter Gateway Entity UseCase ύοέʔδߏ
None
Melos • ઃఆͨ͠σΟϨΫτϦͷ pubspec.yaml Λ୳ͯ͠ղੳ • pubspec_overrides.yaml Λࣗಈੜͯ͠ϩʔΧϧͷύοέʔδͷґଘΛղܾ • λεΫͷฒྻॲཧ
ϩʔΧϧͷύοέʔδͷґଘΛղܾ name: app packages: - packages/**
ϩʔΧϧͷύοέʔδͷґଘΛղܾ $ melos bootstrap
ϩʔΧϧͷύοέʔδͷґଘΛղܾ # melos_managed_dependency_overrides: usecase dependency_overrides: usecase: path: ../usecase
ϩʔΧϧͷύοέʔδͷґଘΛղܾ name: gateway description: Gateway for infrastructure Layer environment: sdk:
'>=3.3.0 <4.0.0' dependencies: riverpod_annotation: usecase: # path: ../usecase dev_dependencies: build_runner: riverpod_generator: VTFDBTFύοέʔδͷ QBUIΛলུͰ͖Δ
λεΫͷฒྻॲཧ • concurrency Ͱฒྻ࣮ߦΛมߋՄೳ • orderDependents Ͱύοέʔδͷґଘؔάϥϑʹج͍ͮͯෳͷύοέʔ δͰεΫϦϓτΛ࣮ߦՄೳ • packageFilters
ʹͯ·Δύοέʔδશͯʹରͯ͠ฒྻͰλεΫΛ࣮ߦ͠ ͯ͘ΕΔ
λεΫͷฒྻॲཧ name: app packages: - packages/** scripts: gen: run: melos
gen:all --no-select gen:all: run: dart run build_runner build --delete-conflicting-outputs exec: concurrency: 8 orderDependents: true packageFilters: dependsOn: build_runner
ΞδΣϯμ •֓ཁ •ύοέʔδׂͷ࣮ྫ •ύοέʔδͷׂ •ςετʹ͍ͭͯ
fl utter test --coverage --update-goldens • --coverage • ςετͷΧόϨοδΛ lcov.info
ϑΝΠϧʹग़ྗ͢Δ • --update-goldens • Golden Test ͷߋ৽༻ͷը૾ग़ྗಉ࣌ʹߦ͏
Melos ΛͬͨϢχοτςετͷ࣮ߦ name: app packages: - packages/** scripts: test: run:
melos test:all --no-select test:all: run: flutter test --coverage --update-goldens exec: concurrency: 8 packageFilters: dirExists: - test UFTUσΟϨΫτϦ͕͋Δ ύοέʔδ͚࣮ͩߦ͢Δ
ϚϧνύοέʔδͷςετͷΧόϨοδ • ෳύοέʔδͰಉ͡ύεͷϑΝΠϧΧόϨοδ͕߹ࢉ͞ΕͨΓ͢Δ • ΠϯλʔϑΣʔεͷΈͷΧόϨοδͷܭଌɺΤϥʔΛ throw ͍ͯ͠Δ͚ͩ ͩͬͨΓɺ࣮ࡍʹॲཧ͕ແ͍ͷͰෆཁ
https://github.com/KyoheiG3/ lcov_excluder lcov_excluder
lcov_excluder • ΧόϨοδʹؚ·ΕΔෆཁͳܭଌϑΝΠϧΛআ֎ • আ֎ઃఆ͢Δ lcov.info Λɺҙͷ .yaml ʹఆ࣮ٛͯ͠ߦ
ΧόϨοδܭଌͷআ֎ઃఆྫ coverage: default: exclude: - lib/**/*.*.dart targets: - usecase: sourceRoot:
packages/usecase exclude: - lib/src/repository/*
ΧόϨοδܭଌͷআ֎ઃఆྫ $ dart run lcov_excluder exclude
·ͱΊ • ύοέʔδΛ͚Δ߹ׂΛҙࣝ͢Δ • Flutter Device ͳͲӨڹͷେ͖ͳͷ΄Ͳԁͷ֎ଆʹஔ͘Α͏ʹ͢Δ • Өڹͷখ͞ͳͷԁͷଆʹஔ͘Α͏ʹ͢Δ
• ςετͳͲฒྻͰ࣮ߦ͢Δ • ΠϯλʔϑΣʔεͷςετͷΧόϨοδܭଌߦΘͳ͍
Thanks !