Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Flutter Bloc : State Management

Soe than
November 19, 2022

Flutter Bloc : State Management

Presentation about Flutter Bloc in DevFest Yangon 2022.

Soe than

November 19, 2022
Tweet

Other Decks in Programming

Transcript

  1. Soe [email protected] Asia Flutter BLOC 19th Nov 2022

  2. Bloc ? • “Business Logic Components” • State Management •

    Based on stream/sink • Encapsulate Business Logic
  3. State Management?

  4. 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)
  5. setState(){}

  6. None
  7. None
  8. None
  9. None
  10. Cons of setstate(){} • Logic inside presentation • Unnecessary widget

    rebuilds • Hard to maintain state across app level
  11. Cons of setstate(){} Pro fi le Page Pro fi leView

    SettingsView
  12. Cons of setstate(){} Pro fi le Page Pro fi leView

    SettingsView
  13. Child to Child rendering Pro fi le Page Pro fi

    leView SettingsView
  14. What is Streams ?

  15. Stream Controller Sink Stream Streams • Async sequence of data

  16. BLOC Events States Ideology

  17. Components • Bloc • Cubit ( built on Bloc)

  18. Cubit Cubit UI Data Function States Response Request

  19. Step 1 : Add dependency pubspec.yaml dependencies: fl utter_bloc: ^8.1.1

  20. Let’s see the code

  21. 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; }
  22. Cubit Anatomy class FetchPokemonsCubit extends Cubit<FetchPokemonState> {}

  23. 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()); }
  24. 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)))); } }
  25. Cubit in Action Future<void> main() async { fi nal cubit

    = FetchPokemonsCubit(); cubit.fetchAllPokemons(); cubit.stream.listen( (event) { …// }); }
  26. BLOC BLOC UI Data Events States Response Request

  27. De fi ne Events abstract class FetchPokemonEvent extends Equatable {}

    class OnFetchPokemons extends FetchPokemonEvent{} class OnRefreshedClicked extends FetchPokemonEvent{}
  28. Bloc Anatomy class FetchPokemonsBloc extends Bloc<FetchPokemonEvent, FetchPokemonState> {}

  29. 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()) }
  30. 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)))); }); } }
  31. 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)); } ); } }
  32. Bloc in Action Future<void> main() async { fi nal bloc=

    FetchPokemonsBloc(); bloc.stream.listen( (state) { …// }); bloc.add(const OnFetchPokemons()); }
  33. Bloc inside Widgets • BlocProvider / MultiBlocProvider • BlocBuilder •

    BlocListener / MultiBlocListener • BlocConsumer
  34. BlocProvider

  35. Widget Widget Widget Widget Widget Widget FetchPokemonsCubit cubit = FetchPokemonsCubit()

  36. BlocProvider( create : (context)=>FetchPokemonsCubit(), lazy : true ) Providing instance

    Accessing instance • Cubit - context.read<FetchPokemonsCubit>().fetchAllPokemons() • Bloc - context.read<FetchPokemonsBloc>().add(const OnFetchPokemons())
  37. Widget Widget Widget Widget Widget Widget BlocProvider( create : (context)=>FetchPokemonsCubit()

    ) context.read<FetchPokemonsCubit>()….
  38. MultiBlocProvider BlocProvider( create:(context)=>BlocA(), child: BlocProvider( create:(context)=>BlocB(), child:.. ) )

  39. MultiBlocProvider MultiBlocProvider( providers: [ BlocProvider( create:(context)=>BlocA(), ), BlocProvider( create:(context)=>BlocB(), )

    ], child: .. . )
  40. HomePage Pro fi leScreen

  41. HomePage Pro fi leScreen BlocProvider( create : (context)=>Pro fi leCubit()

    ) context.read<Pro fi leCubit>()…. ✅ context.read<Pro fi leCubit>()…. 🚫
  42. HomePage Pro fi leScreen MaterialApp

  43. HomePage Pro fi leScreen MaterialApp

  44. Solution ?

  45. HomePage Pro fi leScreen MaterialApp 1. BlocProvider( create : (context)=>Pro

    fi leCubit() )
  46. 2. Navigator.push(context,MaterialPageRoute(builder : (cdx){ return BlocProvider.value( value :context.read<Pro fi leCubit>(),

    child : Pro fi lePage() ) }));
  47. 2. Navigator.push(context,MaterialPageRoute(builder : (cdx){ return BlocProvider.value( value :context.read<Pro fi leCubit>(),

    child : Pro fi lePage() ) }));
  48. 2. Navigator.push(context,MaterialPageRoute(builder : (ctx){ return BlocProvider.value( value :context.read<Pro fi leCubit>(),

    child : Pro fi lePage() ) }));
  49. BlocBuilder

  50. BlocBuilder Widget to rebuild the UI based on bloc state

    changes
  51. BlocBuilder Anatomy class _HomeScreenState extends State<HomeScreen> { ... /// @override

    Widget build(BuildContext context) { return Scaffold( ... /// ); } }
  52. 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 } ) ); } }
  53. 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){} } ) ); } }
  54. 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; } ) ); } }
  55. BlocListener

  56. BlocListener Listen to state changes

  57. BlocListener Anatomy class _HomeScreenState extends State<HomeScreen> { ... /// @override

    Widget build(BuildContext context) { return Scaffold( ... /// ); } }
  58. 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() ) ); } }
  59. 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() ) ); } }
  60. 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() ) )
  61. 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: .. . )
  62. BlocConsumer

  63. BlocConsumer BlocBuilder + BlocListener

  64. 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 } ) ) ); } }
  65. 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 } ) ); } }
  66. 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 } ) ); } }
  67. 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 } ) ); } }
  68. 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 ) ); } }
  69. 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
  70. 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}’); } }
  71. 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); } }
  72. 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); } }
  73. Unit Testing Bloc “Easy to test”

  74. Step 1 : Add dependency pubspec.yaml dev_dependencies: bloc_test: ^9.0.0 test:

    ^1.16.0
  75. Testing Bloc

  76. 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], ); }); }
  77. 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], ); }); }
  78. 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], ); }); }
  79. 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], ); }); }
  80. 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], ); }); }
  81. 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], ); }); }
  82. 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], ); }); }
  83. 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], ); }); }
  84. 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