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
はじめてのDSPy - 言語モデルを『プロンプト』ではなく『プログラミング』するための仕組み
masahiro_nishimi
4
17k
オープンソースソフトウェアへの解像度🔬
utam0k
17
3.2k
Vue 3.6 時代のリアクティビティ最前線 〜Vapor/alien-signals の実践とパフォーマンス最適化〜
hiranuma
2
230
CSC509 Lecture 07
javiergs
PRO
0
250
CSC305 Lecture 10
javiergs
PRO
0
310
オンデバイスAIとXcode
ryodeveloper
0
270
Amazon ECS Managed Instances が リリースされた!キャッチアップしよう!! / Let's catch up Amazon ECS Managed Instances
cocoeyes02
0
110
Building, Deploying, and Monitoring Ruby Web Applications with Falcon (Kaigi on Rails 2025)
ioquatix
4
2.6k
AI駆動で0→1をやって見えた光と伸びしろ
passion0102
1
880
AkarengaLT vol.38
hashimoto_kei
1
130
CSC305 Lecture 12
javiergs
PRO
0
240
Google Opalで使える37のライブラリ
mickey_kubo
3
160
Featured
See All Featured
Imperfection Machines: The Place of Print at Facebook
scottboms
269
13k
Building a Modern Day E-commerce SEO Strategy
aleyda
44
7.9k
Unsuck your backbone
ammeep
671
58k
GraphQLとの向き合い方2022年版
quramy
49
14k
Designing for Performance
lara
610
69k
The Language of Interfaces
destraynor
162
25k
Designing Experiences People Love
moore
142
24k
Music & Morning Musume
bryan
46
6.9k
Producing Creativity
orderedlist
PRO
348
40k
Designing for humans not robots
tammielis
254
26k
How to Think Like a Performance Engineer
csswizardry
27
2.2k
[RailsConf 2023] Rails as a piece of cake
palkan
57
5.9k
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