felangel / bloc

A predictable state management library that helps implement the BLoC design pattern
https://bloclibrary.dev
MIT License
11.76k stars 3.39k forks source link

docs: What is the best way to use riverpod with bloc for dependency injection? #4159

Closed MiniSuperDev closed 5 months ago

MiniSuperDev commented 5 months ago

Hi, if i want to use riverpod for the dependency injection instead of provider what is the best way?

Using riverpod over provider for dependency injection can have several advantages explained better here, but in general we can have blocs of the same type and avoid using ProxyProvider.

I see riverbloc, but using it makes it very coupled to riverpod and difficult to reuse the widgets in a project that does not use it.

By the way, this is not a request to change provider to riverpod, but a way to document the best way to use it in conjunction with riverpod, since it is very popular for dependency injection and other things that are not relevant here.

Below you can see an example of what I'm doing, maybe do you recommend other way, create extension, widgets, etc.? In my example we can reuse the bloc, and the widgets that use the bloc in project with and without riverpod.

Thanks

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'main.g.dart';

class CounterCubit extends Cubit<int> {
  CounterCubit() : super(0);

  void increment() => emit(state + 1);
}

@riverpod // <- generate conuterCubitProvider
CounterCubit counterCubit(CounterCubitRef ref) {
  final cubit = CounterCubit();
  ref.onDispose(cubit.close);
  return cubit;
}

void main() {
  runApp(const ProviderScope(child: MainApp()));
}

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(home: CounterScreen());
  }
}

class CounterScreen extends ConsumerWidget {
  const CounterScreen({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
        body: ListView(
      padding: const EdgeInsets.all(16),
      children: [
        CounterView(bloc: ref.watch(counterCubitProvider)),
        const Divider(),
        ElevatedButton(
          onPressed: () => ref.invalidate(counterCubitProvider),
          child: const Text('Simulate dependency change'),
        ),
      ],
    ));
  }
}

class CounterView extends StatelessWidget {
  const CounterView({
    super.key,
    this.bloc,
  });
  final CounterCubit? bloc;

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<CounterCubit, int>(
        bloc: bloc,
        builder: (context, state) {
          return Column(
            children: [
              Text('Counter: $state'),
              ElevatedButton(
                onPressed: () {
                  final bloc = this.bloc ?? context.read<CounterCubit>();
                  bloc.increment();
                },
                child: const Text('Increment'),
              ),
            ],
          );
        });
  }
}
felangel commented 5 months ago

Hi @MiniSuperDev 👋 Thanks for opening an issue!

The approach you demonstrated seems totally fine to me! You can always decouple things even further so that you have "ContainerWidgets" (widgets which provide dependencies) and "PresentationWidgets" (plain-old stateless widgets which render some UI in response to some inputs).

Hope that helps! Closing for now but feel free to add any additional comments and I'm happy to continue the discussion or joins the Bloc Discord to chat with members of the community 👍

MiniSuperDev commented 5 months ago

Thanks @felangel. It sounds pretty interesting. Could you give more details about the "ContainerWidgets" and "PresentationWidgets", perhaps a dummy example, it doesn't matter if it is pseudo code.