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
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
SourceGeneratorのススメ
htkym
0
190
AI巻き込み型コードレビューのススメ
nealle
1
160
20260127_試行錯誤の結晶を1冊に。著者が解説 先輩データサイエンティストからの指南書 / author's_commentary_ds_instructions_guide
nash_efp
1
940
AgentCoreとHuman in the Loop
har1101
5
230
そのAIレビュー、レビューしてますか? / Are you reviewing those AI reviews?
rkaga
6
4.5k
Basic Architectures
denyspoltorak
0
670
Amazon Bedrockを活用したRAGの品質管理パイプライン構築
tosuri13
4
270
LLM Observabilityによる 対話型音声AIアプリケーションの安定運用
gekko0114
2
430
Package Management Learnings from Homebrew
mikemcquaid
0
210
15年続くIoTサービスのSREエンジニアが挑む分散トレーシング導入
melonps
2
190
余白を設計しフロントエンド開発を 加速させる
tsukuha
7
2.1k
Oxlintはいいぞ
yug1224
5
1.3k
Featured
See All Featured
Color Theory Basics | Prateek | Gurzu
gurzu
0
200
Primal Persuasion: How to Engage the Brain for Learning That Lasts
tmiket
0
250
The Spectacular Lies of Maps
axbom
PRO
1
520
Conquering PDFs: document understanding beyond plain text
inesmontani
PRO
4
2.3k
The untapped power of vector embeddings
frankvandijk
1
1.6k
Accessibility Awareness
sabderemane
0
51
Build The Right Thing And Hit Your Dates
maggiecrowley
38
3k
What the history of the web can teach us about the future of AI
inesmontani
PRO
1
430
世界の人気アプリ100個を分析して見えたペイウォール設計の心得
akihiro_kokubo
PRO
66
36k
Raft: Consensus for Rubyists
vanstee
141
7.3k
Producing Creativity
orderedlist
PRO
348
40k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
231
22k
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