1

I've got two StatelessWidgets, one is a child of another. There is also a progress update function which updates the state in the external TransferState. Once updateProgress function is being called the TransferIndicator widget gets rebuilt immediately. On the other hand, its parent (TransfersListTile) build method isn't called.

It works as expected, however I can't really work out what's the mechanism that's being used here. How Flutter decides to rebuild the: _TransferIndicator given that the parameter is a string hash that's not being changed, but only used as a lookup ID to reach the map in TransferState and load the status and progress.

Documentation: https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html says:

"The build method of a stateless widget is typically only called in three situations: the first time the widget is inserted in the tree, when the widget's parent changes its configuration, and when an InheritedWidget it depends on changes."

If: notifyListeners(); function is removed, the widget doesn't get rebuilt.

It seem to be closely related to: ChangeNotifier, but couldn't find the exact info how it works.

In doc here: https://docs.flutter.dev/development/data-and-backend/state-mgmt/simple#changenotifier there is an example involving ChangeNotifier, however doesn't the receiving widget need to be wrapped around: Consumer (https://docs.flutter.dev/development/data-and-backend/state-mgmt/simple#consumer)? In my case there is no Consumer wrapping.

class TransfersListTile extends StatelessWidget {
  TransfersListTile(this.transfer, {Key? key}) : super(key: key);
  final Transfer transfer;

  @override
  Widget build(BuildContext context) {
    return ListTile(
      leading: _TransferIndicator(transfer.hash),
      title: Text(transfer.name!),
    );
  }
}

class _TransferIndicator extends StatelessWidget {
  const _TransferIndicator(this.hash, {Key? key}) : super(key: key);
  final String? hash;

  @override
  Widget build(BuildContext context) {
    final status = context.select((TransferState s) => s.map[hash]?.status) ?? TransferStatus.pending;
    final progress = context.select((TransferState s) => s.map[hash].progress.percentage);

    return CircularProgressIndicator(
      value: status == TransferStatus.completed ? 100 : (progress / 100),
    );
  }
}

function:

class TransferState with ChangeNotifier {
  updateProgress(String hash, TransferProgress progress) {
    map[hash]?.progress = progress;
    notifyListeners();
  }
}

and provider part:

runApp(
      MultiProvider(
        providers: [
          ChangeNotifierProvider(create: (context) => TransferState(),
        ],
        child: MyApp(),
      )
  );
Tom Raganowicz
  • 2,169
  • 5
  • 27
  • 41
  • Where is `BuildContext.select()` defined? This does not appear to be a standard method. – Chuck Batson Feb 18 '23 at 04:52
  • 1
    It's part of the `provider` package: https://pub.dev/packages/provider Inside `inherited_provider.dart` there is the `SelectContext` extension on `BuildContext`. Your comment made me realize that it's actually not a Google package, despite the references here: https://docs.flutter.dev/development/data-and-backend/state-mgmt/simple. I am going to shop around in the package maintainer docs since it seem to have more examples. – Tom Raganowicz Feb 18 '23 at 08:18

1 Answers1

0

More info about the select method and other convenience methods can be found on the provider (https://pub.dev/packages/provider) package site.

Excerpt:

The easiest way to read a value is by using the extension methods on [BuildContext]:

  • context.watch(), which makes the widget listen to changes on T
  • context.read(), which returns T without listening to it
  • context.select<T, R>(R cb(T value)), which allows a widget to listen to only a small part of T.
Tom Raganowicz
  • 2,169
  • 5
  • 27
  • 41