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
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Kyohei Ito
November 21, 2024
Programming
1.7k
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
510
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.7k
iosdc_2017.pdf
kyoheig3
4
940
ca.swift_2.pdf
kyoheig3
9
1.4k
Other Decks in Programming
See All in Programming
Lessons from Spec-Driven Development
simas
PRO
0
150
肥大化するレガシーコードに立ち向かうためのインターフェース分離と依存の逆転 / JJUG CCC 2026 Spring
hirokunimaeta
0
530
フロントエンドとバックエンドで「1文字」を揃えよう
youkidearitai
PRO
0
240
脅威をエンジニアリングの糧にして――現場編 / Turning Threats into Engineering Fuel — Field Edition
nrslib
0
270
Webフレームワークの ベンチマークについて
yusukebe
0
150
気づいたらRubyで100作品 ー クリエイティブコーディングが生活の一部になるまで / 100 Ruby Sketches Later: How Creative Coding Became Part of My Life
chobishiba
3
560
その問い、本当に正しいですか?AI時代のエンジニアに必要な哲学と認知科学 / ai-philosophy-cognitive-science
minodriven
5
2.9k
RTSPクライアントを自作してみた話
simotin13
0
520
過去最大のMCPアップデート! 2026-07-28 RC版の謎に迫る
licux
6
210
AI駆動開発で崩れていくコードベースを立て直す
kyoko_nr_nr
1
450
運用エージェントは "作る" から "育てる" へ - 記憶と自己進化の3層設計パターン / self-evolving-agents-three-layer-agent-design
gawa
12
3.6k
Vue × Nuxt × Oxc どこまで使える?実運用の現在地
andpad
0
140
Featured
See All Featured
Why Our Code Smells
bkeepers
PRO
340
58k
Typedesign – Prime Four
hannesfritz
42
3.1k
How to audit for AI Accessibility on your Front & Back End
davetheseo
0
410
How to Align SEO within the Product Triangle To Get Buy-In & Support - #RIMC
aleyda
2
1.5k
The Mindset for Success: Future Career Progression
greggifford
PRO
0
360
Automating Front-end Workflow
addyosmani
1370
210k
Statistics for Hackers
jakevdp
799
230k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
333
22k
End of SEO as We Know It (SMX Advanced Version)
ipullrank
3
4.2k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
32
3.4k
Raft: Consensus for Rubyists
vanstee
141
7.5k
Navigating the moral maze — ethical principles for Al-driven product design
skipperchong
2
390
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 !