Search code examples
flutterdependency-injectionrefactoringblocstate-management

Flutter: Many bloc providers cluttering widget tree


Im making a type of chat app, and has a need for multiple blocs/repositories/etc. This has lead to my widget tree looking like this:

enter image description here

And also has lead to a nasty looking build method in my main.dart:

  @override
  Widget build(BuildContext context) {
    return MultiRepositoryProvider(
      providers: [
        RepositoryProvider(
          create: (_) => AuthRepository(),
        ),
        RepositoryProvider(
          create: (_) => FirestoreRepository(),
        ),
        RepositoryProvider(
          create: (context) => StorageRepository(),
        ),
        RepositoryProvider(
          create: (context) => MessagingRepository(),
        ),
      ],
      child: MultiBlocProvider(
        providers: [
          BlocProvider<AuthBloc>(
            create: (context) => AuthBloc(
              authRepository: context.read<AuthRepository>(),
            ),
          ),
          BlocProvider<BottomnavbarCubit>(
            create: (context) => BottomnavbarCubit(),
            child: MainPage(),
          ),
          BlocProvider<SignupCubit>(
            create: (context) => SignupCubit(
              authRepository: context.read<AuthRepository>(),
            ),
          ),
          BlocProvider<LocaluserCubit>(
            create: (context) => LocaluserCubit(),
            child: MaterialApp(),
          ),
          BlocProvider<SigninCubit>(
            create: (context) => SigninCubit(
              authRepository: context.read<AuthRepository>(),
            ),
          ),
          BlocProvider<UpdateCubit>(
            create: (context) => UpdateCubit(
              firestoreRepository: context.read<FirestoreRepository>(),
            ),
          ),
          BlocProvider<ProfileBloc>(
              create: (context) => ProfileBloc(
                    authBloc: BlocProvider.of<AuthBloc>(context),
                    databaseRepository: context.read<FirestoreRepository>(),
                  )),
          BlocProvider<VoteBloc>(
              create: (context) => VoteBloc(
                  firestoreRepository: context.read<FirestoreRepository>())),
          BlocProvider<MessageBloc>(
              create: (context) => MessageBloc(
                  firestoreRepository: context.read<FirestoreRepository>())),
          BlocProvider<LeaderboardBloc>(
              create: (context) => LeaderboardBloc(
                  databaseRepository: context.read<FirestoreRepository>())),
          BlocProvider<CommentBloc>(
              create: (context) => CommentBloc(
                  firestoreRepository: context.read<FirestoreRepository>())),
          BlocProvider<EditbioCubit>(
            create: (context) => EditbioCubit(),
          ),
          BlocProvider<CommentCubit>(
            create: (context) => CommentCubit(),
          ),
          BlocProvider<SearchBloc>(
            create: (context) => SearchBloc(),
          ),
          BlocProvider(
            create: (context) => SwipeBloc(
              firestoreRepository: context.read<FirestoreRepository>(),
              profileBloc: context.read<ProfileBloc>(),
            ),
          ),
          BlocProvider<StingrayBloc>(
              create: (context) => StingrayBloc(
                    authBloc: BlocProvider.of<AuthBloc>(context),
                    databaseRepository: context.read<FirestoreRepository>(),
                  )),
        ],
        child: MultiProvider(
          providers: [
            Provider<AuthRepository>(
              create: (_) => AuthRepository(),
            ),
            StreamProvider<List<Stingray?>>.value(
              value: FirestoreRepository().stingrays,
              initialData: [],
            ),
          ],
          child: MaterialApp(
            title: 'Fishbowl',
            theme: theme(),
            onGenerateRoute: AppRouter.onGenerateRoute,
            initialRoute: Wrapper.routeName,
          ),
        ),
      ),
    );
  }

Is this normal? I looked into GetIt, but i read that it is difficult to do dependency injection with blocs. Not sure if this is a fixable issue, or if this is just how working with multiple blocs work. Thank you!


Solution

  • Q: Is this normal?
    A: Yes, this is perfectly normal. The MultiBlocProvider is used in the same way and this is the resulting tree structure.

    Q: I looked into GetIt, but i read that it is difficult to do dependency injection with blocs
    A: No, it's not difficult to do DI with Blocs. Rather its very simple. And, it's just like injecting other instance. However, there is a cost and problem associated with it.

    You can inject using below code:

    return BlocProvider(
    create: (ctx) => getIt<SomeBloc>(),
    child: SomeChildWidget(),
    );
    

    To use getIt with Bloc, there is only one problem that must be addressed properly.

    1. Bloc's cannot and must not be dependency injected in Flutter using getIt. The reason being Bloc maintains the state changes and creation and destruction of Bloc's and Cubit's. If you just inject it's instance, then BlocProvider will always get the same instance with previous states and all. This will cause us to manually reset the state of Bloc leading to unnecessary state management which is against the Bloc's principles of state management. Events should update states not us.

    2. Bloc should not me DIed. However, the dependencies used by Bloc can be DIed. FOr instance, repository and data sources can be injected into the Bloc. But never the Bloc itself.

    We use Bloc extensively with GetIt, just that we do not use it on the Bloc instance itself, rather with the dependencies needed for the Bloc's.

    UPDATE::

    We can use @injectable to make new instances of BLoC's as well. However, when we need to pass in parameters in constructor as per usage to BLoC, I personally refrain from creating them manually.