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
87
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
20250628_非エンジニアがバイブコーディングしてみた
ponponmikankan
0
710
Agentic Coding: The Future of Software Development with Agents
mitsuhiko
0
120
Quand Symfony, ApiPlatform, OpenAI et LangChain s'allient pour exploiter vos PDF : de la théorie à la production…
ahmedbhs123
0
210
Deep Dive into ~/.claude/projects
hiragram
14
11k
ふつうの技術スタックでアート作品を作ってみる
akira888
1
1.1k
The Evolution of Enterprise Java with Jakarta EE 11 and Beyond
ivargrimstad
0
180
たった 1 枚の PHP ファイルで実装する MCP サーバ / MCP Server with Vanilla PHP
okashoi
1
280
顧客の画像データをテラバイト単位で配信する 画像サーバを WebP にした際に起こった課題と その対応策 ~継続的な取り組みを添えて~
takutakahashi
1
310
AI駆動のマルチエージェントによる業務フロー自動化の設計と実践
h_okkah
0
210
Azure AI Foundryではじめてのマルチエージェントワークフロー
seosoft
0
190
イベントストーミング図からコードへの変換手順 / Procedure for Converting Event Storming Diagrams to Code
nrslib
2
970
VS Code Update for GitHub Copilot
74th
2
670
Featured
See All Featured
Reflections from 52 weeks, 52 projects
jeffersonlam
351
21k
How to Think Like a Performance Engineer
csswizardry
25
1.7k
The Illustrated Children's Guide to Kubernetes
chrisshort
48
50k
GraphQLの誤解/rethinking-graphql
sonatard
71
11k
How GitHub (no longer) Works
holman
314
140k
How to train your dragon (web standard)
notwaldorf
96
6.1k
Embracing the Ebb and Flow
colly
86
4.7k
The Pragmatic Product Professional
lauravandoore
35
6.7k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
PRO
20
1.3k
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
44
2.4k
Designing Experiences People Love
moore
142
24k
Making Projects Easy
brettharned
116
6.3k
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