Skip to content

prefer_bloc_extensions

v0.4.0 Warning Fix Bloc / Riverpod

This rule flags BlocProvider.of<T>(context) and RepositoryProvider.of<T>(context), and offers a quick fix rewriting them to context.read<T>(). When listen: true is passed, the fix produces context.watch<T>() instead.

BlocProvider.of and the extensions do the same lookup, but the extension puts the subscription decision in the method name. With of, whether the widget rebuilds on state changes depends on a listen: argument that is easy to omit and easy to misread — and its default (false) is silent, so a widget that should rebuild simply never does.

See also: BlocProvider | context.read vs context.watch

A one-off read in a callback becomes context.read:

// Don't
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
sealed class CounterEvent {}
class Increment extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0);
}
void onTap(BuildContext context) {
BlocProvider.of<CounterBloc>(context).add(Increment()); // LINT
}
// Do
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
sealed class CounterEvent {}
class Increment extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0);
}
void onTap(BuildContext context) {
context.read<CounterBloc>().add(Increment());
}

Reading state in build needs a subscription, so the fix produces watch:

// Don't
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
}
Widget build(BuildContext context) {
final count = BlocProvider.of<CounterCubit>(context, listen: true).state; // LINT
return Text('$count');
}
// Do
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
}
Widget build(BuildContext context) {
final count = context.watch<CounterCubit>().state;
return Text('$count');
}

RepositoryProvider.of is reported on the same terms:

// Don't
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class UserRepository {}
void load(BuildContext context) {
final repo = RepositoryProvider.of<UserRepository>(context); // LINT
}
// Do
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class UserRepository {}
void load(BuildContext context) {
final repo = context.read<UserRepository>();
}

Only BlocProvider and RepositoryProvider resolving to package:flutter_bloc, package:bloc or package:provider are matched. A project’s own wrapper with an of static is not reported.

A dynamic listen: argument (listen: shouldWatch) is treated as false, since the rule can only read a boolean literal — so the fix would produce read where watch may be wanted.

This rule is in the opinionated preset, so it is on with preset: opinionated, or by name:

many_lints.yaml
rules:
prefer_bloc_extensions: true

To turn it off again:

many_lints.yaml
rules:
prefer_bloc_extensions: false

To keep the rule on but skip certain paths, use per-rule exclude.