bloc_ease 1.2.0 copy "bloc_ease: ^1.2.0" to clipboard
bloc_ease: ^1.2.0 copied to clipboard

A dart library to solve boilerplate issues with flutter_bloc by just using typedefs instead of defining state classes.

bloc_ease #

A dart library to solve boilerplate issues with flutter_bloc by just using typedefs instead of defining state classes.

bloc_ease_template

image

image

Index #

Problems This Library Addresses #

  1. Repeatedly writing the same types of states for every Bloc/Cubit (Initial, Loading, Success, Failure).
  2. Overriding == and hashCode, or using the Equatable package for all states.
  3. Handling every state in the UI, even when only the success state is needed.
  4. Returning the same widget for the same kind of state across all Blocs/Cubits (e.g., ProgressIndicator for the Loading state).
  5. Managing buildWhen to avoid handling every state.
  6. Adopting poor practices such as using a single-state class instead of inheritance.
  7. Managing multiple states together due to boilerplate code.

We are going to solve these using

  • Generics (Inherited states)
  • InheritedWidget (Global state widgets)
  • Builders
  • typedefs (Use templates) Don't worry about any of these. This package will take care of everything.

Solutions This Library Provides #

  1. Elimination of the need to write state classes for any Bloc/Cubit. Instead, utilize the states provided by this package with generics (e.g., SucceedState<Auth> vs SucceedState<User>).
  2. Global handling of common states such as Initial, Loading, and Failure states in the UI. This removes the necessity to manage these states wherever Bloc/Cubit is used.
  3. Provision of a builder that offers the success object in a type-safe manner, while autonomously handling other states.
  4. Utilization of typedefs to easily differentiate between states (e.g., typedef AuthSucceedState = SucceedState<Auth>). Snippets are included for IntelliJ and VSCode.

Readme #

The states InitialState, LoadingState, SucceedState, and FailedState can encapsulate most state. If a state cannot be represented within these states, it is likely that multiple states are being managed together.

  • Asynchronous CRUD Operation State: Typically falls into one of these four states:

    • Backend fetching
    • Device I/O Job
    • Multi-threaded operations
  • Synchronous Operation State: Can be one of three states, excluding LoadingState:

    • Parsing logic
    • Encryption/Decryption logic
    • Filtering a list based on a condition
  • Synchronous Operation: Can be represented by either SucceedState or FailedState:

    • Calculation (SucceedState<double>(10) vs FailedState<double>(DivideByZeroException()))
  • Specific State Representation: Some states can only be depicted as SucceedState:

    • Flutter's default counter app state SucceedState<int>(0)
    • Selecting app currency SucceedState<Currency>(USD()) or unit of temperature SucceedState<TemperatureUnit>(Celsius())

How to use? #

Step 1 - Configuring BlocEaseStateWidgetProvider #

BlocEaseStateWidgetProvider is used to configure the default widgets for InitialState, LoadingState, and FailedState. Ensure that this widget is wrapped around the MaterialApp so that it is accessible from everywhere.

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocEaseStateWidgetProvider( // <--
      initialStateBuilder: (state) => const Placeholder(),
      loadingStateBuilder: (state) => const Center(child: CircularProgressIndicator()),
      failureStateBuilder: (state) => Center(child: Text(state.message ?? 'Oops something went wrong!')),
      child: MaterialApp(
          //..
          ),
    );
  }
}
copied to clipboard

Step 2 - Create Bloc/Cubit with the snippet/template provided below. #

Use the shortcut bloceasebloc or bloceasecubit from the template to create a bloc or cubit based on the need. This creates a template that requires editing two names: Cubit name -> UserCubit Success Object -> User (This is the object expected from the success state of the bloc/cubit)

import 'package:bloc_ease/bloc_ease.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

typedef UserState = BlocEaseState<User>; // <-- Success Object

typedef UserInitialState = InitialState<User>;
typedef UserLoadingState = LoadingState<User>;
typedef UserSucceedState = SucceedState<User>;
typedef UserFailedState = FailedState<User>;

typedef UserBlocBuilder = BlocBuilder<UserCubit, UserState>;
typedef UserBlocListener = BlocListener<UserCubit, UserState>;
typedef UserBlocConsumer = BlocConsumer<UserCubit, UserState>;

typedef UserBlocEaseBuilder = BlocEaseStateBuilder<UserCubit, User>;
typedef UserBlocEaseListener = BlocEaseStateListener<UserCubit, User>;
typedef UserBlocEaseConsumer = BlocEaseStateConsumer<UserCubit, User>;

class UserCubit extends Cubit<UserState> { //<--Cubit name
  UserCubit(this.userRepo)
          : super(const UserInitialState());

  final UserRepo userRepo;

  void fetchUser() async {
    emit(const UserLoadingState());

    try {
      final user = userRepo.fetchUser();
      emit(UserSucceedState(user));
    } catch (e) {
      emit(UserFailedState('Failed to fetch user', e));
    }
  }
}
copied to clipboard

Step 3 - Use <CubitName>BlocEaseBuilder instead of BlocBuilder in the UI #

<CubitName>BlocEaseBuilder (UserBlocEaseBuilder) is the builder used to access the Success Object configured in Step 2 with the succeedBuilder required field. All other states (InitialState, LoadingState, and FailedState) use the default widgets configured in Step 1.

class SomeWidget extends StatelessWidget {
  const SomeWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return UserBlocEaseBuilder( //<-- <CubitName>BlocEaseBuilder
      succeedBuilder: (user)    //<-- This provides the Success Object we configured in the Step 2.
        => SomeOtherWidget(user),
    );
  }
}
copied to clipboard

Example Snippets #

Fetching user details #

Fetching user usually needs 4 states.

  • Initial state - When not logged in
  • Loading state - When fetching in progress
  • Succeed state - When successfully fetched
  • Failed state - User not available / Failed to fetch

image

Fetching item details on opening item page #

Since we need to fetch the item on opening the page, this usually holds 3 states.

  • Loading state - When fetching in progress
  • Succeed state - when item fetched successfully
  • Failed state - When failed to fetch item

Notice that, ItemInitialState not used even though it can be accessed. image

Cache State with Ease - CacheExBlocEaseStateMixin #

By utilizing the CacheExBlocEaseStateMixin mixin with any Bloc or Cubit that emits BlocEaseState, you gain access to previous states, including exLoadingState, exSucceedState, and exFailedState. Additionally, the exSucceedObject allows direct access to the previous success object, if it exists. These extended states enable comparison and operations based on state changes.

import 'package:bloc_ease/bloc_ease.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

typedef CurrentUserState = BlocEaseState<User>;

// -- Uses Mixin CacheExBlocEaseStateMixin
class CurrentUserCubit extends Cubit<CurrentUserState> with CacheExBlocEaseStateMixin { 
  CurrentUserCubit()
      : super(const CurrentUserInitialState());
      
  void doSomething() {
    final userId = exSucceedObject?.id; //<-- We can access exSucceedObject
    if(userId != null) {
      ...
    }
  }

  void resetState() {
	emit(const CurrentUserInitialState());
	resetCache(); //<-- (Can call resetCache method to force reset cache)
  }
}

typedef CurrentUserInitialState = InitialState<User>;
typedef CurrentUserLoadingState = LoadingState<User>;
typedef CurrentUserSucceedState = SucceedState<User>;
typedef CurrentUserFailedState = FailedState<User>;
...
copied to clipboard
class SomeWidget extends StatelessWidget {  
  const SomeWidget({super.key});  
    
  @override  
  Widget build(BuildContext context) {  
    final currentUserCubit = context.read<CurrentUserCubit>();  
    return CurrentUserBlocEaseListener(  
      succeedListener: (user) {  
        final exUser = currentUserCubit.exSucceedObject; //<-- Can access exSucceedObject
        if(exUser?.email == null && user.email != null) {  
          welcomeUserViaEmail(user.email);  
        }  
      },  
      child: ...  
    );  
  }  
}
copied to clipboard

Tip: It is possible to animate transitions between loading states or success states by comparing the exLoadingState with the current loadingState or the exSucceedState with the current succeedState.

Listen to multiple Blocs - BlocEaseMultiStateListener #

BlocEaseMultiStateListener allows for monitoring multiple blocs or cubits that emit BlocEaseState. The primary use cases include:

  • Displaying a progress dialog when any cubit is in the LoadingState.
  • Showing an error message if any cubit emits a FailedState.
  • Displaying a success snackbar only when all cubits emit a SucceedState.
  • ...
class SomeWidget extends StatelessWidget {
  const SomeWidget({super.key});

  @override
  Widget build(BuildContext context) {
    // Remember: Both AuthBloc and UserBloc should emit BlocEaseState
    final blocEaseBlocs = [context.read<AuthBloc>(), context.read<UserBloc>()];
    return BlocEaseMultiStateListener(
      blocEaseBlocs: blocEaseBlocs,
      onStateChange: (states) {
        if(states.any((e) => e is LoadingState)) {
          showLoadingDialog();
        } else if(states.any((e) => e is FailedState)) {
          showErrorDialog();
        } else if(states.every((e) => e is SucceedState)) {
          showSuccessSnackBar();
        }
      },
      child: ...,
    );
  }
}
copied to clipboard

PRO TIP: If you want to handle only one state, you can simply use Generics like

class SomeWidget extends StatelessWidget {
  const SomeWidget({super.key});

  @override
  Widget build(BuildContext context) {
    // Remember: Both AuthBloc and UserBloc should emit BlocEaseState
    final blocEaseBlocs = [context.read<AuthBloc>(), context.read<UserBloc>()];
    return BlocEaseMultiStateListener<SucceedState>( //<-- If you just want to handle SucceedState
      blocEaseBlocs: blocEaseBlocs,
      onStateChange: (states) => showSuccessSnackBar(),
      child: ...,
    );
  }
}
copied to clipboard

Multi-State Builder - BlocEaseMultiStateBuilder #

The BlocEaseMultiStateBuilder allows for the combination of different blocs or cubits that emit BlocEaseState into a single widget. This utility offers several use cases, including:

  • Displaying a single loading indicator instead of one for each bloc.
  • Showing a single error widget instead of multiple error widgets on the screen.
  • Indicating loading progress by knowing how many blocs or cubits are in the LoadingState (automatically handled with BlocEaseStateWidgetProvider - progress field).
  • Rendering all widgets at once instead of loading them separately.

By default, only the successBuilder needs to be provided; all other states are managed by default with BlocEaseStateWidgetProvider.

Note: If any state is a FailedState, an error widget is displayed. If any state is an InitialState, the initial widget is shown. If any state is a LoadingState, the loading widget is rendered. Only if all states are SucceedState, the success widget is displayed.

class SomeWidget extends StatelessWidget {
  const SomeWidget({super.key});

  @override
  Widget build(BuildContext context) {
    // Remember: All of these Bloc/Cubit should exit BlocEaseState
    final blocEaseBlocs = [context.read<UserBloc>(), context.read<OrdersBloc>(), context.read<ReturnsBloc>(), context.read<WishlistBloc>()];
    return BlocEaseMultiStateBuilder( //<-- If you just want to handle SucceedState
      blocEaseBlocs: blocEaseBlocs,
      successBuilder: (states) => Dashboard(),
    );
  }
}
copied to clipboard

image

Templates #

Intellij and Android Studio #

Copy both templates at once -> Intellij/Android studio Settings -> Live Templates -> Create new template group as BlocEase -> Paste

<template name="bloceasebloc" value="import 'package:bloc_ease/bloc_ease.dart';&#10;import 'package:flutter_bloc/flutter_bloc.dart';&#10;&#10;typedef $BlocName$State = BlocEaseState&lt;$SuccessType$&gt;;&#10;&#10;class $BlocName$Bloc extends Bloc&lt;$BlocName$Event,$BlocName$State&gt; {&#10;  $BlocName$Bloc()&#10;      : super(const $BlocName$InitialState());&#10;      &#10;  $Dependencies$&#10;      &#10;  void $FunctionName$() {&#10;    emit(const $BlocName$LoadingState());&#10;    &#10;    $ImplementationStart$&#10;  }&#10;}&#10;&#10;typedef $BlocName$InitialState = InitialState&lt;$SuccessType$&gt;;&#10;typedef $BlocName$LoadingState = LoadingState&lt;$SuccessType$&gt;;&#10;typedef $BlocName$SucceedState = SucceedState&lt;$SuccessType$&gt;;&#10;typedef $BlocName$FailedState = FailedState&lt;$SuccessType$&gt;;&#10;&#10;typedef $BlocName$BlocBuilder = BlocBuilder&lt;$BlocName$Bloc, $BlocName$State&gt;;&#10;typedef $BlocName$BlocListener = BlocListener&lt;$BlocName$Bloc, $BlocName$State&gt;;&#10;typedef $BlocName$BlocConsumer = BlocConsumer&lt;$BlocName$Bloc, $BlocName$State&gt;;&#10;&#10;typedef $BlocName$BlocEaseBuilder = BlocEaseStateBuilder&lt;$BlocName$Bloc, $SuccessType$&gt;;&#10;typedef $BlocName$BlocEaseListener = BlocEaseStateListener&lt;$BlocName$Bloc, $SuccessType$&gt;;&#10;typedef $BlocName$BlocEaseConsumer = BlocEaseStateConsumer&lt;$BlocName$Bloc, $SuccessType$&gt;;" description="BlocEase Four state bloc template" toReformat="false" toShortenFQNames="true">
  <variable name="BlocName" expression="" defaultValue="" alwaysStopAt="true" />
  <variable name="SuccessType" expression="" defaultValue="" alwaysStopAt="true" />
  <variable name="Dependencies" expression="" defaultValue="" alwaysStopAt="true" />
  <variable name="FunctionName" expression="" defaultValue="" alwaysStopAt="true" />
  <variable name="ImplementationStart" expression="" defaultValue="" alwaysStopAt="true" />
  <context>
    <option name="DART" value="true" />
    <option name="FLUTTER" value="true" />
  </context>
</template>
<template name="bloceasecubit" value="import 'package:bloc_ease/bloc_ease.dart';&#10;import 'package:flutter_bloc/flutter_bloc.dart';&#10;&#10;typedef $CubitName$State = BlocEaseState&lt;$SuccessType$&gt;;&#10;&#10;class $CubitName$Cubit extends Cubit&lt;$CubitName$State&gt; {&#10;  $CubitName$Cubit()&#10;      : super(const $CubitName$InitialState());&#10;      &#10;  $Dependencies$&#10;      &#10;  void $FunctionName$() {&#10;    emit(const $CubitName$LoadingState());&#10;    &#10;    $ImplementationStart$&#10;  }&#10;}&#10;&#10;typedef $CubitName$InitialState = InitialState&lt;$SuccessType$&gt;;&#10;typedef $CubitName$LoadingState = LoadingState&lt;$SuccessType$&gt;;&#10;typedef $CubitName$SucceedState = SucceedState&lt;$SuccessType$&gt;;&#10;typedef $CubitName$FailedState = FailedState&lt;$SuccessType$&gt;;&#10;&#10;typedef $CubitName$BlocBuilder = BlocBuilder&lt;$CubitName$Cubit, $CubitName$State&gt;;&#10;typedef $CubitName$BlocListener = BlocListener&lt;$CubitName$Cubit, $CubitName$State&gt;;&#10;typedef $CubitName$BlocConsumer = BlocConsumer&lt;$CubitName$Cubit, $CubitName$State&gt;;&#10;&#10;typedef $CubitName$BlocEaseBuilder = BlocEaseStateBuilder&lt;$CubitName$Cubit, $SuccessType$&gt;;&#10;typedef $CubitName$BlocEaseListener = BlocEaseStateListener&lt;$CubitName$Cubit, $SuccessType$&gt;;&#10;typedef $CubitName$BlocEaseConsumer = BlocEaseStateConsumer&lt;$CubitName$Cubit, $SuccessType$&gt;;&#10;" description="BlocEase Four state cubit template" toReformat="false" toShortenFQNames="true">
  <variable name="CubitName" expression="" defaultValue="" alwaysStopAt="true" />
  <variable name="SuccessType" expression="" defaultValue="SuccessType" alwaysStopAt="true" />
  <variable name="Dependencies" expression="" defaultValue="" alwaysStopAt="true" />
  <variable name="FunctionName" expression="" defaultValue="" alwaysStopAt="true" />
  <variable name="ImplementationStart" expression="" defaultValue="" alwaysStopAt="true" />
  <context>
    <option name="DART" value="true" />
    <option name="FLUTTER" value="true" />
  </context>
</template>
copied to clipboard

image

VSCode (TODO: Change and test) #

Copy -> VSCode -> Cmd(Ctrl) + Shift + P -> "Snippets: Configure User Snippets" -> dart.json -> Paste

{
  "BlocEase Bloc": {
    "prefix": ["bloceasebloc"],
    "description": "BlocEase Four state bloc template",
    "body": [
      "import 'package:bloc_ease/bloc_ease.dart';",
      "import 'package:flutter_bloc/flutter_bloc.dart';",
      "",
      "typedef ${1:BlocName}State = BlocEaseState<${2:SuccessType}>;",
      "",
      "typedef ${1}InitialState = InitialState<${2}>;",
      "typedef ${1}LoadingState = LoadingState<${2}>;",
      "typedef ${1}SucceedState = SucceedState<${2}>;",
      "typedef ${1}FailedState = FailedState<${2}>;",
      "",
      "typedef ${1}BlocBuilder = BlocBuilder<${1}Bloc, ${1}State>;",
      "typedef ${1}BlocListener = BlocListener<${1}Bloc, ${1}State>;",
      "typedef ${1}BlocConsumer = BlocConsumer<${1}Bloc, ${1}State>;",
      "",
      "typedef ${1}BlocEaseBuilder = BlocEaseStateBuilder<${1}Bloc, ${2}>;",
      "",
      "class ${1}Bloc extends Bloc<${1}Event,${1}State> {",
      "\t${1}Bloc() : super(const ${1}InitialState());",
      "",
      "\t${3}",
      "}",
    ]
  },
  "BlocEase Cubit": {
    "prefix": ["bloceasecubit"],
    "description": "BlocEase Four state cubit template",
    "body": [
      "import 'package:bloc_ease/bloc_ease.dart';",
      "import 'package:flutter_bloc/flutter_bloc.dart';",
      "",
      "typedef ${1:CubitName}State = BlocEaseState<${2:SuccessType}>;",
      "",
      "typedef ${1}InitialState = InitialState<${2}>;",
      "typedef ${1}LoadingState = LoadingState<${2}>;",
      "typedef ${1}SucceedState = SucceedState<${2}>;",
      "typedef ${1}FailedState = FailedState<${2}>;",
      "",
      "typedef ${1}BlocBuilder = BlocBuilder<${1}Cubit, ${1}State>;",
      "typedef ${1}BlocListener = BlocListener<${1}Cubit, ${1}State>;",
      "typedef ${1}BlocConsumer = BlocConsumer<${1}Cubit, ${1}State>;",
      "",
      "typedef ${1}BlocEaseBuilder = BlocEaseStateBuilder<${1}Cubit, ${2}>;",
      "",
      "class ${1}Cubit extends Cubit<${1}State> {",
      "  ${1}Cubit() : super(const ${1}InitialState());",
      "",
      "  $3",
      "}"
    ]
  }
}
copied to clipboard

Tips and Tricks #

Using BlocEaseListener and BlocEaseConsumer #

The template also generates <CubitName>BlocEaseListener and <CubitName>BlocEaseConsumer which can be used instead of BlocListener and BlocConsumer.

class BlocEaseListenerExampleWidget extends StatelessWidget {
  const BlocEaseListenerExampleWidget({super.key});

  @override
  Widget build(BuildContext context) {
    // All fields are optional
    return UserBlocEaseListener( //<-- <CubitName>BlocEaseListener
      initialListener: () {},
      loadingListener: ([progress]) {},
      failureListener: ([message, exception, retryCallback]) {},
      succeedListener: (user) {},
      child: //..//,
    );
  }
}

class BlocEaseConsumerExampleWidget extends StatelessWidget {
  const BlocEaseConsumerExampleWidget({super.key});

  @override
  Widget build(BuildContext context) {
    // Other than succeedBuilder, all fields are optional.
    return UserBlocEaseConsumer( //<-- <CubitName>BlocEaseConsumer
      initialListener: () {},
      loadingListener: ([progress]) {},
      failureListener: ([message, exception, retryCallback]) {},
      succeedListener: (user) {},

      initialBuilder: () {},
      loadingBuilder: ([progress]) {},
      failureBuilder: ([message, exception, retryCallback]) ={},
      succeedBuilder: (user) => SomeWidget(user),
    );
  }
}
copied to clipboard

Work with Bloc #

Use the shortcut bloceasebloc from the template to create a bloc based on your need with all the typedefs defined for you.

import 'package:bloc_ease/bloc_ease.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

part 'user_event.dart';

typedef UserState = BlocEaseState<User>;

typedef UserInitialState = InitialState<User>;
typedef UserLoadingState = LoadingState<User>;
typedef UserSucceedState = SucceedState<User>;
typedef UserFailedState = FailedState<User>;

typedef UserBlocBuilder = BlocBuilder<UserBloc, UserState>;
typedef UserBlocListener = BlocListener<UserBloc, UserState>;
typedef UserBlocConsumer = BlocConsumer<UserBloc, UserState>;

typedef UserBlocEaseBuilder = BlocEaseStateBuilder<UserBloc, User>;
typedef UserBlocEaseListener = BlocEaseStateListener<UserBloc, User>;
typedef UserBlocEaseConsumer = BlocEaseStateConsumer<UserBloc, User>;

class UserBloc extends Bloc<UserEvent,UserState> {
  UserBloc()
      : super(const UserInitialState()){
    // on...
  } 
}
copied to clipboard

Take advantage of Records when defining SuccessObject type. #

In some cases, we need multiple params as Success object. In that case, we could easily take advantage of Records instead of creating a data class for that.

import 'package:bloc_ease/bloc_ease.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

typedef UserState = BlocEaseState<(User, String)>; // <-- Success Object

typedef UserInitialState = InitialState<(User, String)>;
typedef UserLoadingState = LoadingState<(User, String)>;
typedef UserSucceedState = SucceedState<(User, String)>;
typedef UserFailedState = FailedState<(User, String)>;

typedef UserBlocBuilder = BlocBuilder<UserCubit, UserState>;
typedef UserBlocListener = BlocListener<UserCubit, UserState>;
typedef UserBlocConsumer = BlocConsumer<UserCubit, UserState>;

typedef UserBlocEaseBuilder = BlocEaseStateBuilder<UserCubit, (User, String)>;

class UserCubit extends Cubit<UserState> {
  UserCubit() : super(const UserInitialState());

  //..//
}
copied to clipboard

Testing #

Testing is also totally straight-forward as just using Bloc/Cubit.

blocTest<UserCubit, UserState>(
        'emits UserSucceedState after fetching user',
        setUp: () {
          when(repo.fetchUser).thenAnswer((_) async => mockUser);
        },
        build: () => UserCubit(repository: repo),
        act: (cubit) => cubit.fetchUser(),
        expect: () => UserSucceedState(mockUser), //<--
        verify: (_) => verify(repository.fetchUser).called(1),
      );
copied to clipboard

Take advantage of all typedefs generated by this template. #

One of the painful work with using BlocBuilder is that we need to write the entire boilerplate everytime. Take advantage of the typedefs generated by the template provided.

  • UserBlocBuilder instead of BlocBuilder<UserCubit, UserState>
  • UserBlocListener instead of BlocListener<UserCubit, UserState>
  • UserBlocConsumer instead of BlocConsumer<UserCubit, UserState>

Overriding the default state widgets for a certain page or widget tree #

If we wrap the same BlocEaseStateWidgetProvider over some widget tree, all the default widgets gets overridden with this new implementation. So all the BlocEaseBuilders comes under this widget use this overridden widgets as default case.

class SomePage extends StatelessWidget {
  const SomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocEaseStateWidgetProvider(
      initialStateBuilder: () => const SizedBox(),
      loadingStateBuilder: ([progress]) => const CustomLoader(),
      failureStateBuilder: ([exception, message, retryCallback]) => Text(message ?? 'Oops something went wrong!'),
      child: //..//,
    );
  }
}
copied to clipboard

Example projects #

These example projects are taken from the official flutter_bloc package examples. So that its easy to compare the implementation. Also it passes all the test cases.

  1. complex_list
  2. flutter_shopping_cart

Features and bugs #

Please file feature requests and bugs at the issue tracker.

Connect with me @Bharath #

image

13
likes
140
points
251
downloads
screenshot

Publisher

unverified uploader

Weekly Downloads

2024.09.22 - 2025.04.06

A dart library to solve boilerplate issues with flutter_bloc by just using typedefs instead of defining state classes.

Repository (GitHub)
View/report issues

Topics

#bloc #flutter-bloc #state-management

Documentation

API reference

Funding

Consider supporting this project:

www.buymeacoffee.com

License

BSD-2-Clause (license)

Dependencies

flutter, flutter_bloc, rxdart

More

Packages that depend on bloc_ease