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
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Soe than
November 19, 2022
Programming
91
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Flutter Bloc : State Management
Presentation about Flutter Bloc in DevFest Yangon 2022.
Soe than
November 19, 2022
Other Decks in Programming
See All in Programming
正しくソフトウェアを作る、前提を疑うための認知の視点 / doubt-premise
minodriven
21
7k
TSKaigi Night Talks 2026_TypeScriptでサプライチェーンの整合性を型に閉じ込める
geekplus_tech
0
400
dRuby over BLE
makicamel
2
390
ECSアプリログをFireLensでコスト削減しようとしたけど諦めた話 in Fargate×Node.js
akihisaikeda
2
4.2k
Contextとはなにか
chiroruxx
1
360
メソッドのジェネリクスでGoの夢は広がるか? / Kyoto.go #65
utgwkk
3
900
脅威をエンジニアリングの糧にして――現場編 / Turning Threats into Engineering Fuel — Field Edition
nrslib
0
290
JavaDoc 再入門
nagise
1
380
IBM Bobを活用したレガシーアプリの最新化
oniak3ibm
PRO
1
210
不変条件と整合性境界—ビジネスが決める設計判断と実現パターン / Invariants and Consistency Boundaries
nrslib
14
5.8k
並列実装の現場、2ヶ月間実務でAIを使い倒したAIもPCも私も限界が近い
ming_ayami
0
130
依存関係から依存物へ―Dependencyという言葉の歴史をひも解く
j_lee
0
130
Featured
See All Featured
Navigating the moral maze — ethical principles for Al-driven product design
skipperchong
2
400
Taking LLMs out of the black box: A practical guide to human-in-the-loop distillation
inesmontani
PRO
3
2.3k
Docker and Python
trallard
47
3.9k
Intergalactic Javascript Robots from Outer Space
tanoku
273
27k
Amusing Abliteration
ianozsvald
1
210
Abbi's Birthday
coloredviolet
3
8.2k
YesSQL, Process and Tooling at Scale
rocio
174
15k
Navigating the Design Leadership Dip - Product Design Week Design Leaders+ Conference 2024
apolaine
1
360
Building a A Zero-Code AI SEO Workflow
portentint
PRO
0
600
コードの90%をAIが書く世界で何が待っているのか / What awaits us in a world where 90% of the code is written by AI
rkaga
62
44k
Building Adaptive Systems
keathley
44
3.1k
Bash Introduction
62gerente
615
220k
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