skip to Main Content

We can do this:

class WorldModel {
  WorldModel({required this.skyModel});

  final SkyModel skyModel;

  static final instance = Provider<WorldModel>(
    (ref) => WorldModel(
      skyModel: SkyModel(),
    ),
  );

  final countBirds = Provider<int>((ref) => 25);
}

Which would mean that we can only access our countBirds provider after we have accessed the WorldModel instance. Those (in build() method):

Widget build(BuildContext context, WidgetRef ref) {
  final worldModel = ref.watch(WorldModel.instance);
  final countBirds = ref.watch(worldModel.countBirds);
  return Text(countBirds.toString());
}

Otherwise, we can even define it like this:

late final countBirds = Provider<int>((ref) => 5);

It all works great and performs its function 100%. Even when using the .autoDispose modifier, everything works fine and is disposed of. But, the official documentation strongly recommends using providers ONLY as final ones:

Providers should always be final.

Does this mean they can be late? Why and what are the pitfalls?


About function

Why do I need this (I’m talking about definitions in the WorldModel class)? This is because countBirds may depend on some fields of the WorldModel class. I can’t do it any other way, just because I think it’s good practice for dependency injection. Here is a good example:

class WorldModel {
  WorldModel({required this.skyModel});

  final SkyModel skyModel;

  static final instance = Provider<WorldModel>(
    (ref) => WorldModel(skyModel: SkyModel()),
  );

  late final countBirds = Provider<int>((ref) => skyModel.countBirds);
}

class SkyModel {
  late int countBirds;
}

2

Answers


  1. It’s not recommend to use providers as static, while I don’t think there is technically an issue with that but still it’s a bad practices.

    As for defining the provider as final inside a class without static keyword, this will lead to a new provider being created with every instance of the class, so when you lose access to the class instance you will not be able to access the provider and this will be a bigger issue if the provider isn’t auto-disposable.

    Providers can be defined late as following

    final myProvider = Provider<WorldModel>((ref) {
      throw UnimplementedError();
    });
    
    final countBirds = Provider<int>((ref) {
      throw UnimplementedError();
    });
    

    And then you can initialize the providers before accessing in the widget tree by warping the parent widget with ProviderScope

    ProviderScope(
      overrides: [
        myProvider.overrideWithValue(skyModel),
        countBirds.overrideWithValue(skyModel.countBirds),
      ],
      child: const MyWidget(),
    )
    

    And then you can access the providers in any widget in the widget tree which is under the ProviderScope.

    Another thing you can do is to use .family modifier

    final myProvider = Provider.family<WorldModel, SkyModel>((ref, skyModel) {
      return WorldModel(skyModel: skyModel);
    });
    

    However you have to pass the same SkyModel instance to reuse the same provider instance, otherwise you will get a new provider

    Login or Signup to reply.
  2. Providers should always be final because non-final providers will lead to low performance.
    All in all, you should make unchanging variables final because it’s good modelling, not because you worry about performance. Providers should be unchanging.
    Why providers should be unchanging? Because there is no reason why you may need it changing. If you need different behaviours of the provider – you can encapsulate these behaviours into the provider. If you need providers with different types you should create several final providers with different types.
    You can make the provider not final but this may lead to runtime errors if you do not keep track of the type or behaviour of the provider.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search