Bloc: [Discussion] Does 'context.watch' overlap with `BlocBuilder`?

Created on 8 Nov 2020  路  3Comments  路  Source: felangel/bloc

I have just upgraded all of my dependencies and noticed quite a few deprecation warnings in places where context.bloc was called, saying it is now replaced with context.read and context.watch. I changed all of my calls to use read but now I get an exception at runtime saying I should use watch instead (something that I know from provider - are you simply exposing provider extensions or has it been reimplemented in flutter_bloc?). Fair enough, I need to go over my code and decide what to do on a case-by-case basis. In order to do that to the best of my ability, I though a bit about the difference between the two calls as I know them from provider, and it occurred to me that:

final stateA = context.watch<BlocA>();
return Text('$stateA');

is actually pretty similar to:

return BlocBuilder<BlocA, StateA>(
  builder: (_, state) {
    return Text('$state');
  },
);

but more succinct. I suppose it works like provider's StreamProvider does (do you use StreamProvider under the hood for this feature?), i.e. it causes build to be called whenever the state changes, is that right?

Using BlocBuilder does introduce a dedicated Widget and it gets its own BuildContext, but it rarely makes a difference, at least in my code. I can always use an intermediate Builder if I need this, or extract my widget to a separate class.

Question: when would I prefer BlocBuilder over context.watch, or vice versa?

Right now I think I would personally always use the latter due to it being less boilerplate and arguably easier to understand, especially for someone working with provider as well, making the codebase more consistent (the line between both packages is getting blurry over time, due to nested, this change, etc., which I think is good, I think of flutter_bloc as an extension of provider anyway). The only exception that comes to mind is when BlocBuilder.buildWhen is necessary (which I don't use at all in my app). However, I think I could replace even this use case most of the time (maybe always?) with context.select, right?

If we go further, we might not even need BlocProvider at some point, as we can use StreamProvider (Bloc is-a Stream). The only reason difference for me is the BlocProvider.dispose callback.

Is BlocBuilder going to disappear?

Or

context.watch actually changes when the bloc instance it 'represents' changes. But what is the use for this? I very likely don't need to update the BLoC instance as the whole point of using it is that it keeps the state for me.

I'm a bit confused ;d

question

Most helpful comment

Hi @wujek-srujek 馃憢
Thanks for opening an issue!

The context.read, context.watch, and context.select extensions are exposed through provider and behave the exact same way (ie you should not use context.read within build and context.watch outside of build, etc...).

context.watch will trigger a rebuild whenever the underlying state of the bloc changes so the two are functionally equivalent:

@override
Widget build(BuildContext context) {
  final state = context.watch<MyBloc>().state;
}
@override
Widget build(BuildContext context) {
  return BlocBuilder<MyBloc, MyState>(
    builder: (context, state) {...}
  );
}

Question: when would I prefer BlocBuilder over context.watch, or vice versa?

I would prefer BlocBuilder over context.watch in cases where you don't want to decompose your widget tree into really small widgets. For example:

@override
Widget build(BuildContext context) {
  final state = context.watch<MyBloc>().state;
  return Scaffold(
    body: Text('$state'),
  );
}

This will result in the entire widget being rebuilt whenever state changes (even though the Scaffold does not need to be rebuilt).

In contrast the following will only result in the Text widget being rebuilt:

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: BlocBuilder<MyBloc, MyState>(
      builder: (context, state) => Text('$state'),
    ),
  );
}

You can still scope your rebuilds via context.watch with a Builder widget:

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Builder(
      builder: (context) {
        final state = context.watch<MyBloc>().state;
        return Text('$state');
      },
    ),
  );
}

However, I would still prefer to use BlocBuilder unless you need to access the state of more than one bloc.

I think I could replace even this use case most of the time (maybe always?) with context.select, right?

In many cases you can replace buildWhen with context.select; however, it is not always the case. For example, if you still need access to the entire state but only want to restrict rebuilds, context.select will not help.

If we go further, we might not even need BlocProvider

I would still use BlocProvider because it is specifically implemented to handle managing the lifecycle of the bloc. The fact that a Bloc is a Stream is, to some extent, an implementation detail.

Is BlocBuilder going to disappear?

No, I have no intention of removing BlocBuilder because it is safer in many ways and also aligns more closely with existing Flutter widgets/patterns (StreamBuilder, etc...). I consider the extensions as useful tools for advanced users who are very familiar with bloc and provider and have no intention of removing the existing bloc widgets.

Or context.watch actually changes when the bloc instance it 'represents' changes

No, context.watch changes when the bloc state changes.

Hope that helps clarify things and sorry for any confusion/inconvenience!

UPDATE I've added information about the new extensions in v6.1.0 to the migration guide 馃憤

All 3 comments

Hi @wujek-srujek 馃憢
Thanks for opening an issue!

The context.read, context.watch, and context.select extensions are exposed through provider and behave the exact same way (ie you should not use context.read within build and context.watch outside of build, etc...).

context.watch will trigger a rebuild whenever the underlying state of the bloc changes so the two are functionally equivalent:

@override
Widget build(BuildContext context) {
  final state = context.watch<MyBloc>().state;
}
@override
Widget build(BuildContext context) {
  return BlocBuilder<MyBloc, MyState>(
    builder: (context, state) {...}
  );
}

Question: when would I prefer BlocBuilder over context.watch, or vice versa?

I would prefer BlocBuilder over context.watch in cases where you don't want to decompose your widget tree into really small widgets. For example:

@override
Widget build(BuildContext context) {
  final state = context.watch<MyBloc>().state;
  return Scaffold(
    body: Text('$state'),
  );
}

This will result in the entire widget being rebuilt whenever state changes (even though the Scaffold does not need to be rebuilt).

In contrast the following will only result in the Text widget being rebuilt:

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: BlocBuilder<MyBloc, MyState>(
      builder: (context, state) => Text('$state'),
    ),
  );
}

You can still scope your rebuilds via context.watch with a Builder widget:

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Builder(
      builder: (context) {
        final state = context.watch<MyBloc>().state;
        return Text('$state');
      },
    ),
  );
}

However, I would still prefer to use BlocBuilder unless you need to access the state of more than one bloc.

I think I could replace even this use case most of the time (maybe always?) with context.select, right?

In many cases you can replace buildWhen with context.select; however, it is not always the case. For example, if you still need access to the entire state but only want to restrict rebuilds, context.select will not help.

If we go further, we might not even need BlocProvider

I would still use BlocProvider because it is specifically implemented to handle managing the lifecycle of the bloc. The fact that a Bloc is a Stream is, to some extent, an implementation detail.

Is BlocBuilder going to disappear?

No, I have no intention of removing BlocBuilder because it is safer in many ways and also aligns more closely with existing Flutter widgets/patterns (StreamBuilder, etc...). I consider the extensions as useful tools for advanced users who are very familiar with bloc and provider and have no intention of removing the existing bloc widgets.

Or context.watch actually changes when the bloc instance it 'represents' changes

No, context.watch changes when the bloc state changes.

Hope that helps clarify things and sorry for any confusion/inconvenience!

UPDATE I've added information about the new extensions in v6.1.0 to the migration guide 馃憤

Closing for now but feel free to comment with additional questions and I鈥檓 happy to continue the conversation 馃憤

Thanks @felangel for the answer, I think it is definitive. Don't worry, I'll be back if I have doubts ;d

Was this page helpful?
0 / 5 - 0 ratings