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
Flutter Bloc : State Management
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
Soe than
November 19, 2022
Programming
0
89
Flutter Bloc : State Management
Presentation about Flutter Bloc in DevFest Yangon 2022.
Soe than
November 19, 2022
Tweet
Share
Other Decks in Programming
See All in Programming
AI巻き込み型コードレビューのススメ
nealle
1
150
AI時代のキャリアプラン「技術の引力」からの脱出と「問い」へのいざない / tech-gravity
minodriven
20
7k
CSC307 Lecture 05
javiergs
PRO
0
500
ぼくの開発環境2026
yuzneri
0
170
それ、本当に安全? ファイルアップロードで見落としがちなセキュリティリスクと対策
penpeen
7
2.5k
QAフローを最適化し、品質水準を満たしながらリリースまでの期間を最短化する #RSGT2026
shibayu36
2
4.3k
なるべく楽してバックエンドに型をつけたい!(楽とは言ってない)
hibiki_cube
0
140
コントリビューターによるDenoのすゝめ / Deno Recommendations by a Contributor
petamoriken
0
200
AIと一緒にレガシーに向き合ってみた
nyafunta9858
0
180
humanlayerのブログから学ぶ、良いCLAUDE.mdの書き方
tsukamoto1783
0
190
AI Agent Tool のためのバックエンドアーキテクチャを考える #encraft
izumin5210
6
1.8k
フロントエンド開発の勘所 -複数事業を経験して見えた判断軸の違い-
heimusu
7
2.8k
Featured
See All Featured
DBのスキルで生き残る技術 - AI時代におけるテーブル設計の勘所
soudai
PRO
62
49k
How to Talk to Developers About Accessibility
jct
2
130
How To Speak Unicorn (iThemes Webinar)
marktimemedia
1
380
Abbi's Birthday
coloredviolet
1
4.7k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
61k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
122
21k
Groundhog Day: Seeking Process in Gaming for Health
codingconduct
0
92
A better future with KSS
kneath
240
18k
KATA
mclloyd
PRO
34
15k
How Software Deployment tools have changed in the past 20 years
geshan
0
32k
Breaking role norms: Why Content Design is so much more than writing copy - Taylor Woolridge
uxyall
0
160
エンジニアに許された特別な時間の終わり
watany
106
230k
Transcript
Soe Than@Pickles Asia Flutter BLOC 19th Nov 2022
Bloc ? • “Business Logic Components” • State Management •
Based on stream/sink • Encapsulate Business Logic
State Management?
State Management? UI =f( state ) Application state Build Layout
of the screen Any interactions that reflects UI changes (e.g - Form validation, network request , database access)
setState(){}
None
None
None
None
Cons of setstate(){} • Logic inside presentation • Unnecessary widget
rebuilds • Hard to maintain state across app level
Cons of setstate(){} Pro fi le Page Pro fi leView
SettingsView
Cons of setstate(){} Pro fi le Page Pro fi leView
SettingsView
Child to Child rendering Pro fi le Page Pro fi
leView SettingsView
What is Streams ?
Stream Controller Sink Stream Streams • Async sequence of data
BLOC Events States Ideology
Components • Bloc • Cubit ( built on Bloc)
Cubit Cubit UI Data Function States Response Request
Step 1 : Add dependency pubspec.yaml dependencies: fl utter_bloc: ^8.1.1
Let’s see the code
De fi ne States @freezed class FetchPokemonState with _$FetchPokemonsState {
const factory FetchPokemonsState.initial() = FetchPokemonsStateInitial; const factory FetchPokemonsState.loading() = FetchPokemonsStateLoading; const factory FetchPokemonsState.error(String errMsg) = FetchPokemonsStateError; const factory FetchPokemonsState.data(List<PokemonListUi> pokemons) = FetchPokemonsStateData; }
Cubit Anatomy class FetchPokemonsCubit extends Cubit<FetchPokemonState> {}
Cubit Anatomy class FetchPokemonsCubit extends Cubit<FetchPokemonState> { fi nal FetchAllPokemon
fetchAllPokemon; fi nal PokemonUiMapper uiMapper; FetchPokemonsCubit({required this.fetchAllPokemon, required this.uiMapper}) : super(const FetchPokemonState.initial()); }
Cubit Anatomy class FetchPokemonsCubit extends Cubit<FetchPokemonState> { fi nal FetchAllPokemon
fetchAllPokemon; fi nal PokemonUiMapper uiMapper; FetchPokemonsCubit({required this.fetchAllPokemon, required this.uiMapper}) : super(const FetchPokemonState.initial()); fetchAllPokemons() async { emit(const FetchPokemonState.loading()); fi nal result = await fetchallpokemon(NoParams()); emit(result.fold( ( l ) => FetchPokemonState.error(l.message), (r) => FetchPokemonState.data(uiMapper.maps(r)))); } }
Cubit in Action Future<void> main() async { fi nal cubit
= FetchPokemonsCubit(); cubit.fetchAllPokemons(); cubit.stream.listen( (event) { …// }); }
BLOC BLOC UI Data Events States Response Request
De fi ne Events abstract class FetchPokemonEvent extends Equatable {}
class OnFetchPokemons extends FetchPokemonEvent{} class OnRefreshedClicked extends FetchPokemonEvent{}
Bloc Anatomy class FetchPokemonsBloc extends Bloc<FetchPokemonEvent, FetchPokemonState> {}
Bloc Anatomy class FetchPokemonsBloc extends Bloc<FetchPokemonEvent, FetchPokemonState> { fi nal
FetchAllPokemon fetchAllPokemon; fi nal PokemonUiMapper uiMapper; FetchPokemonsCubit({required this.fetchAllPokemon, required this.uiMapper}) : super(const FetchPokemonState.initial()) }
Bloc Anatomy class FetchPokemonsBloc extends Bloc<FetchPokemonEvent, FetchPokemonState> { fi nal
FetchAllPokemon fetchAllPokemon; fi nal PokemonUiMapper uiMapper; FetchPokemonsCubit({required this.fetchAllPokemon, required this.uiMapper}) : super(const FetchPokemonState.initial()) { on<OnFetchPokemons>((event, emit) async { emit(const FetchPokemonState.loading()); fi nal result = await fetchallpokemon(NoParams()); emit(result.fold( ( l ) => FetchPokemonState.error(l.message), (r) => FetchPokemonState.data(uiMapper.maps(r)))); }); } }
Bloc Anatomy class FetchPokemonsBloc extends Bloc<FetchPokemonEvent, FetchPokemonState> { fi nal
FetchAllPokemon fetchAllPokemon; fi nal PokemonUiMapper uiMapper; FetchPokemonsCubit({required this.fetchAllPokemon, required this.uiMapper}) : super(const FetchPokemonState.initial()) { on<OnFetchPokemons>((event, emit) async { emit(const FetchPokemonState.loading()); fi nal result = await fetchallpokemon(NoParams()); emit(result.fold( ( l ) => FetchPokemonState.error(l.message), (r) => FetchPokemonState.data(uiMapper.maps(r)))); }, transformer: (events, mapper) { return events.debounce(Duration(milliseconds:300)); } ); } }
Bloc in Action Future<void> main() async { fi nal bloc=
FetchPokemonsBloc(); bloc.stream.listen( (state) { …// }); bloc.add(const OnFetchPokemons()); }
Bloc inside Widgets • BlocProvider / MultiBlocProvider • BlocBuilder •
BlocListener / MultiBlocListener • BlocConsumer
BlocProvider
Widget Widget Widget Widget Widget Widget FetchPokemonsCubit cubit = FetchPokemonsCubit()
BlocProvider( create : (context)=>FetchPokemonsCubit(), lazy : true ) Providing instance
Accessing instance • Cubit - context.read<FetchPokemonsCubit>().fetchAllPokemons() • Bloc - context.read<FetchPokemonsBloc>().add(const OnFetchPokemons())
Widget Widget Widget Widget Widget Widget BlocProvider( create : (context)=>FetchPokemonsCubit()
) context.read<FetchPokemonsCubit>()….
MultiBlocProvider BlocProvider( create:(context)=>BlocA(), child: BlocProvider( create:(context)=>BlocB(), child:.. ) )
MultiBlocProvider MultiBlocProvider( providers: [ BlocProvider( create:(context)=>BlocA(), ), BlocProvider( create:(context)=>BlocB(), )
], child: .. . )
HomePage Pro fi leScreen
HomePage Pro fi leScreen BlocProvider( create : (context)=>Pro fi leCubit()
) context.read<Pro fi leCubit>()…. ✅ context.read<Pro fi leCubit>()…. 🚫
HomePage Pro fi leScreen MaterialApp
HomePage Pro fi leScreen MaterialApp
Solution ?
HomePage Pro fi leScreen MaterialApp 1. BlocProvider( create : (context)=>Pro
fi leCubit() )
2. Navigator.push(context,MaterialPageRoute(builder : (cdx){ return BlocProvider.value( value :context.read<Pro fi leCubit>(),
child : Pro fi lePage() ) }));
2. Navigator.push(context,MaterialPageRoute(builder : (cdx){ return BlocProvider.value( value :context.read<Pro fi leCubit>(),
child : Pro fi lePage() ) }));
2. Navigator.push(context,MaterialPageRoute(builder : (ctx){ return BlocProvider.value( value :context.read<Pro fi leCubit>(),
child : Pro fi lePage() ) }));
BlocBuilder
BlocBuilder Widget to rebuild the UI based on bloc state
changes
BlocBuilder Anatomy class _HomeScreenState extends State<HomeScreen> { ... /// @override
Widget build(BuildContext context) { return Scaffold( ... /// ); } }
BlocBuilder Anatomy class _HomeScreenState extends State<HomeScreen> { ... /// @override
Widget build(BuildContext context) { return Scaffold( body :BlocBuilder<FetchPokemonCubit,FetchPokemonsState>( builder : (context, state) { /// return widgets based on state } ) ); } }
BlocBuilder Anatomy class _HomeScreenState extends State<HomeScreen> { ... /// @override
Widget build(BuildContext context) { return Scaffold( body :BlocBuilder<FetchPokemonCubit,FetchPokemonsState>( builder : (context, state) { if(state is FetchPokemonsStateError){} if(state is FetchPokemonsStateData){} if(state is FetchPokemonsStateLoading){} } ) ); } }
BlocBuilder Anatomy class _HomeScreenState extends State<HomeScreen> { ... /// @override
Widget build(BuildContext context) { return Scaffold( body :BlocBuilder<FetchPokemonCubit,FetchPokemonsState>( builder : (context, state) { if(state is FetchPokemonsStateError){} if(state is FetchPokemonsStateData){} if(state is FetchPokemonsStateLoading){} }, buildWhen: (prev, current) { return prev != current; } ) ); } }
BlocListener
BlocListener Listen to state changes
BlocListener Anatomy class _HomeScreenState extends State<HomeScreen> { ... /// @override
Widget build(BuildContext context) { return Scaffold( ... /// ); } }
BlocListener Anatomy class _HomeScreenState extends State<HomeScreen> { ... /// @override
Widget build(BuildContext context) { return Scaffold( body :BlocListener<FetchPokemonCubit,FetchPokemonsState>( listener: (context, state) { /// e.g - navigate to next page, show toast }, child: Container() ) ); } }
BlocListener Anatomy class _HomeScreenState extends State<HomeScreen> { ... /// @override
Widget build(BuildContext context) { return Scaffold( body :BlocListener<FetchPokemonCubit,FetchPokemonsState>( listener: (context, state) { /// e.g - navigate to next page, show toast }, listenWhen: (prev,current) => prev != current, child: Container() ) ); } }
MultiBlocListener BlocListener<BlocA,BlocAState>( listener: (context, state) { /// e.g - navigate
to next page, show toast }, child:BlocListener<BlocB,BlocBState>( listener: (context, state) { /// e.g - navigate to next page, show toast }, child: Container() ) )
MultiBlocListener MultiBlocListener( listeners: [ BlocListener<BlocA,BlocAState>( listener: (context, state) { ///
e.g - navigate to next page, show toast }, ), BlocListener<BlocB,BlocBState>( listener: (context, state) { /// e.g - navigate to next page, show toast }, ) ], child: .. . )
BlocConsumer
BlocConsumer BlocBuilder + BlocListener
class _HomeScreenState extends State<HomeScreen> { ... /// @override Widget build(BuildContext
context) { return Scaffold( body :BlocListener<FetchPokemonCubit,FetchPokemonsState>( listener: (context, state) { /// e.g - navigate to next page, show toast }, child:BlocBuilder<FetchPokemonCubit,FetchPokemonsState>( builder : (context, state) { /// return widgets based on state } ) ) ); } }
class _HomeScreenState extends State<HomeScreen> { ... /// @override Widget build(BuildContext
context) { return Scaffold( body :BlocConsumer<FetchPokemonCubit,FetchPokemonsState>( listener: (context, state) { /// e.g - navigate to next page, show toast }, builder : (context, state) { /// return widgets based on state } ) ); } }
class _HomeScreenState extends State<HomeScreen> { ... /// @override Widget build(BuildContext
context) { return Scaffold( body :BlocConsumer<FetchPokemonCubit,FetchPokemonsState>( listener: (context, state) { /// e.g - display toast, dialog }, builder : (context, state) { /// return widgets based on state } ) ); } }
class _HomeScreenState extends State<HomeScreen> { ... /// @override Widget build(BuildContext
context) { return Scaffold( body :BlocConsumer<FetchPokemonCubit,FetchPokemonsState>( listener: (context, state) { /// e.g - display toast, dialog }, builder : (context, state) { /// return widgets based on state } ) ); } }
class _HomeScreenState extends State<HomeScreen> { ... /// @override Widget build(BuildContext
context) { return Scaffold( body :BlocConsumer<FetchPokemonCubit,FetchPokemonsState>( listener: (context, state) { /// e.g - display toast, dialog }, builder : (context, state) { /// return widgets based on state }, listenWhen : (prev, current) => prev != current, buildWhen :(prev,current) => prev != current ) ); } }
Observing Cubit class FetchPokemonsCubit extends Cubit<FetchPokemonsState> { // Impl @override
void onChange(Change<FetchPokemonsState> change){ super.onChange(change); print(‘Cubit ${change.currentState} -> ${change.nextState}’); } } Trigger before emitting state to UI
Observing Bloc class FetchPokemonsBloc extends Bloc<FetchPokemonEvent, FetchPokemonState>{ // Impl @override
void onChange(Change<FetchPokemonsState> change){ super.onChange(change); print(‘Cubit ${change.currentState} -> ${change.nextState}’); } @override void onEvent(FetchPokemonEvent event){ super.onEvent(event); print(‘Event ${event}’); } @override void onTransition(Transition<FetchPokemonEvent,FetchPokemonsState> transition){ super.onTransition(transition); print(‘ ${transition.event} -> ${transition.currentState} -> ${transition.nextState}’); } }
Error handling class FetchPokemonsCubit extends Cubit<FetchPokemonsState> { // constructor fetchAllPokemons()
async { emit(const FetchPokemonState.loading()); fi nal result = await fetchallpokemon(NoParams()); emit(result.fold(( l ) { addError(Exception(l.message),StackTrace.current); return FetchPokemonState.error(l.message) }, (r) => FetchPokemonState.data(uiMapper.maps(r)))); } @override void onError(Object error,StactTrace stackTrace){ print(‘$error,$stackTrace’); super.onError(error,stackTrace); } }
Observer class PokemonBlocObserver extends Observer { @override void onTransition(Bloc bloc,
Transition<FetchPokemonEvent,FetchPokemonsState> transition){ super.onTransition(transition); print(‘ ${transition.event} -> ${transition.currentState} -> ${transition.nextState}’); } @override void onChange(Blocbase bloc, Change<FetchPokemonsState> change){ super.onChange(change); print(‘Cubit ${change.currentState} -> ${change.nextState}’); } @override void onError(Blocbase bloc, Object error,StactTrace stackTrace){ print(‘$error,$stackTrace’); super.onError(error,stackTrace); } }
Unit Testing Bloc “Easy to test”
Step 1 : Add dependency pubspec.yaml dev_dependencies: bloc_test: ^9.0.0 test:
^1.16.0
Testing Bloc
Testing Bloc void main(){ late CounterBloc counterBloc; setUp((){ counterBloc =
CounterBloc(); }); group(‘CounterBloc’,(){ test(‘initial state is 0’,(){ expect(counterBloc.state,0); }); blocTest(‘emits [1] when CounterIncremented is added’, build: () => counterBloc, act : (bloc) => bloc.add(CounterIncrementPressed()), expect: () => [1], ); blocTest(‘emits [-1] when CounterDecremented is added’, build: () => counterBloc, act : (bloc) => bloc.add(CounterDecrementPressed()), expect: () => [-1], ); }); }
Testing Bloc void main(){ late CounterBloc counterBloc; setUp((){ counterBloc =
CounterBloc(); }); group(‘CounterBloc’,(){ test(‘initial state is 0’,(){ expect(counterBloc.state,0); }); blocTest(‘emits [1] when CounterIncremented is added’, build: () => counterBloc, act : (bloc) => bloc.add(CounterIncrementPressed()), expect: () => [1], ); blocTest(‘emits [-1] when CounterDecremented is added’, build: () => counterBloc, act : (bloc) => bloc.add(CounterDecrementPressed()), expect: () => [-1], ); }); }
Testing Bloc void main(){ late CounterBloc counterBloc; setUp((){ counterBloc =
CounterBloc(); }); group(‘CounterBloc’,(){ test(‘initial state is 0’,(){ expect(counterBloc.state,0); }); blocTest(‘emits [1] when CounterIncremented is added’, build: () => counterBloc, act : (bloc) => bloc.add(CounterIncrementPressed()), expect: () => [1], ); blocTest(‘emits [-1] when CounterDecremented is added’, build: () => counterBloc, act : (bloc) => bloc.add(CounterDecrementPressed()), expect: () => [-1], ); }); }
Testing Bloc void main(){ late CounterBloc counterBloc; setUp((){ counterBloc =
CounterBloc(); }); group(‘CounterBloc’,(){ test(‘initial state is 0’,(){ expect(counterBloc.state,0); }); blocTest(‘emits [1] when CounterIncremented is added’, build: () => counterBloc, act : (bloc) => bloc.add(CounterIncrementPressed()), expect: () => [1], ); blocTest(‘emits [-1] when CounterDecremented is added’, build: () => counterBloc, act : (bloc) => bloc.add(CounterDecrementPressed()), expect: () => [-1], ); }); }
Testing Bloc void main(){ late CounterBloc counterBloc; setUp((){ counterBloc =
CounterBloc(); }); group(‘CounterBloc’,(){ test(‘initial state is 0’,(){ expect(counterBloc.state,0); }); blocTest(‘emits [1] when CounterIncremented is added’, build: () => counterBloc, act : (bloc) => bloc.add(CounterIncrementPressed()), expect: () => [1], ); blocTest(‘emits [-1] when CounterDecremented is added’, build: () => counterBloc, act : (bloc) => bloc.add(CounterDecrementPressed()), expect: () => [-1], ); }); }
Testing Bloc void main(){ late CounterBloc counterBloc; setUp((){ counterBloc =
CounterBloc(); }); group(‘CounterBloc’,(){ test(‘initial state is 0’,(){ expect(counterBloc.state,0); }); blocTest(‘emits [1] when CounterIncremented is added’, build: () => counterBloc, act : (bloc) => bloc.add(CounterIncrementPressed()), expect: () => [1], ); blocTest(‘emits [-1] when CounterDecremented is added’, build: () => counterBloc, act : (bloc) => bloc.add(CounterDecrementPressed()), expect: () => [-1], ); }); }
Testing Bloc void main(){ late CounterBloc counterBloc; setUp((){ counterBloc =
CounterBloc(); }); group(‘CounterBloc’,(){ test(‘initial state is 0’,(){ expect(counterBloc.state,0); }); blocTest(‘emits [1] when CounterIncremented is added’, build: () => counterBloc, act : (bloc) => bloc.add(CounterIncrementPressed()), expect: () => [1], ); blocTest(‘emits [-1] when CounterDecremented is added’, build: () => counterBloc, act : (bloc) => bloc.add(CounterDecrementPressed()), expect: () => [-1], ); }); }
Testing Bloc void main(){ late CounterBloc counterBloc; setUp((){ counterBloc =
CounterBloc(); }); group(‘CounterBloc’,(){ test(‘initial state is 0’,(){ expect(counterBloc.state,0); }); blocTest(‘emits [1] when CounterIncremented is added’, build: () => counterBloc, act : (bloc) => bloc.add(CounterIncrementPressed()), expect: () => [1], ); blocTest(‘emits [-1] when CounterDecremented is added’, build: () => counterBloc, act : (bloc) => bloc.add(CounterDecrementPressed()), expect: () => [-1], ); }); }
Thanks for joining 🤗 • Github link - https://github.com/soethan98/Pokemon •
Bloc Doc - https://bloclibrary.dev • Slides - https://speakerdeck.com/soethan/ fl utter-bloc-state-management