felangel / bloc

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

feat: Add a builder parameter on BlocProvider. #4245

Closed lsaudon closed 3 weeks ago

lsaudon commented 3 weeks ago

Description

Add a builder parameter on BlocProvider.

Desired Solution

BlocProvider<CounterBloc>(
  create: (context) => CounterBloc(),
  builder: (context, child) {
    final state = context.watch<CounterBloc>();
    return Text('$state');
  }
)

Additional Context

Equivalent of https://github.com/rrousselGit/provider/blob/64745784fcef9e3ebc6e6dea0d5f82d1ac20c797/packages/provider/lib/src/inherited_provider.dart#L111

felangel commented 3 weeks ago

Hi @lsaudon 👋 That’s undesired because it makes it impossible to unit test the widget since you’re providing and consuming the bloc in the same component. Let me know what you think? Thanks!

lsaudon commented 3 weeks ago

Hi @felangel, When I run tests, I mock the repository rather than the bloc. I don't want any dependency on the bloc in my tests. The repository was placed in the provider when the application was launched, so I can easily mock it.

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

  final MyRepository myRepository;

  @override
  Widget build(BuildContext context) {
    return RepositoryProvider.value(
      value: myRepository,
      child: const MyPage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => MyBloc(context.read()),
      builder: (context, child) => const EditTodoPage(),
    );
  }
}
felangel commented 3 weeks ago

Hi @felangel, When I run tests, I mock the repository rather than the bloc. I don't want any dependency on the bloc in my tests. The repository was placed in the provider when the application was launched, so I can easily mock it.

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

  final MyRepository myRepository;

  @override
  Widget build(BuildContext context) {
    return RepositoryProvider.value(
      value: myRepository,
      child: const MyPage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => MyBloc(context.read()),
      builder: (context, child) => const EditTodoPage(),
    );
  }
}

In that case your widget test is both testing your bloc's logic and your presentation logic (in the widget). Since it's already possible to write your own generic version of this widget, I'd prefer not to add something like this to the library since it encourages tightly coupling the widget to the bloc.

import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class NewBlocProvider<T extends StateStreamableSource<Object?>>
    extends StatelessWidget {
  const NewBlocProvider({
    required this.create,
    required this.builder,
    super.key,
  });

  final T Function(BuildContext context) create;
  final Widget Function(BuildContext context) builder;

  @override
  Widget build(BuildContext context) {
    return BlocProvider<T>(
      create: create,
      child: Builder(builder: builder),
    );
  }
}

Hope that helps! đź‘Ť