Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
flutterkaigi_2024.pdf
Search
Kyohei Ito
November 21, 2024
Programming
1.9k
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
1.1k
layerx_20241129.pdf
kyoheig3
2
530
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
go-spidermonkeyでAIエージェントのCode Modeを実装する
syumai
3
1.5k
型解析で実現する Go の言語内 DSL / Conference に Go! タイムテーブルの歩き方 for Gophers
mazrean
0
140
AI × TiDD / 2026.09.05 Redmine 大阪
tokudiro
1
120
The Past, Present, and Future of Enterprise Java
ivargrimstad
0
170
Seeing Through Serverless: Observability for AWS Lambda with ADOT and CloudWatch Application Signals
seike460
PRO
1
130
Family mrubyの進捗
kishima
1
110
変化を抱擁するドキュメントの作り方 - ビジネスルール駆動開発がもたらす、コードとの新しい関係
ioki
2
130
個人開発基盤をまるごとCloudflareに引っ越して爆速で総合的体験を向上させた話
tinykitten
0
140
【高い買い物LT会】初任給で話題の国産フィジカルAIを買った話
akagami
PRO
0
130
市販E-Readerを乗っ取れ 〜Embedded Swiftで電子ペーパーガジェットを制御する〜
trickart
0
150
App Storeの外へ──日本のiOSサイドローディング入門 for iOSDC Japan 2026
yuukiw00w
0
140
AIとGame Jamで、ゲームを完成させた話
takahirosaeki
0
110
Featured
See All Featured
Design in an AI World
tapps
1
310
Building AI with AI
inesmontani
PRO
1
1.2k
Agile Actions for Facilitating Distributed Teams - ADO2019
mkilby
0
280
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
[RailsConf 2023] Rails as a piece of cake
palkan
59
7k
30 Presentation Tips
portentint
PRO
1
390
The untapped power of vector embeddings
frankvandijk
2
1.9k
Producing Creativity
orderedlist
PRO
348
41k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
25
2.1k
Facilitating Awesome Meetings
lara
57
7.1k
Reality Check: Gamification 10 Years Later
codingconduct
0
2.3k
Tips & Tricks on How to Get Your First Job In Tech
honzajavorek
1
730
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 !