35

I am using Provider. I have got two classes: class TenderApiData {} it's stand alone class (not widget). How I can write accesstoken to AppState?

class AppState extends ChangeNotifier // putted to ChangeNotifierProvider
{ 
  String _accesstoken; // need to fill not from widget but from stand alone class
  String _customer; // Fill from widget 
  List<String> _regions; // Fill from widget 
  List<String> _industry; // Fill from widget 
  ...
}

I need way to read\write accesstoken from stand alone classes.

Or I have issue with architecture of my app?

Here is full source code.

2
  • how did you finally achieve it? i do have a similar requirement
    – anoop4real
    Commented Oct 6, 2020 at 21:37
  • 1
    Consider using riverpod instead of Provider.
    – kris
    Commented Jan 5, 2022 at 6:46

4 Answers 4

20

You cannot and should not access providers outside of the widget tree.

Even if you could theoretically use globals/singletons or an alternative like get_it, don't do that.

You will instead want to use a widget to do the bridge between your provider, and your model.

This is usually achieved through the didChangeDependencies life-cycle, like so:

class MyState extends State<T> {
  MyModel model = MyModel();

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    model.valueThatComesFromAProvider = Provider.of<MyDependency>(context);
  }
}

provider comes with a widget built-in widgets that help with common scenarios, that are:

  • ProxyProvider
  • ChangeNotifierProxyProvider

A typical example would be:

ChangeNotifierProxyProvider<TenderApiData, AppState>(
  initialBuilder: () => AppState(),
  builder: (_, tender, model) => model
    ..accessToken = tender.accessToken,
  child: ...,
);
4
  • 37
    In the first two lines you state, "You should not access providers outside of the widget tree" even if theoretically possible. Instead of a blatant "don't do that", can you say why?
    – chinloyal
    Commented Aug 11, 2020 at 20:18
  • How do you reset state of app during testing without accessing those classes?
    –  vrnvorona
    Commented Nov 29, 2021 at 9:19
  • 1
    Contrary to this advice (maybe it's a later development?) the description of the read() function in provider.dart actively encourages passing it to objects that need access to the provider but have no context.
    – Gábor
    Commented Jul 1, 2022 at 22:32
  • 1
    Does this mean that we should not use Providers with MVC architecture? If the provider should be in the Model and listened from the Controller, but we can't access to context from the Controller since it doesn't have a context
    – JAgüero
    Commented Jan 31, 2023 at 6:44
10

TL;DR

Swap provider for get_it. The later does DI globally without scoping it to a BuildContext. (It actually has its own optional scoping mechanism using string namedInstance's.)

The rest...

I ran into a similar problem and I believe it comes down to the fact that Provider enforces a certain type of (meta?) architecture, namely one where Widgets are at the top of what you might call the "agency pyramid".

In other words, in this style, widgets are knowledgable about Business Logic (hence the name BLoC architecture), they run the show, not unlike the ViewController paradigm popularised by iOS and also maybe MVVM setups.

In this architectural style, when a widget creates a child widget, it also creates the model for the widget. Here context could be important, for example, if you had multiple instances of the same child widget being displayed simultaneously, each would need its own instance of the underlying model. Within the widget or its descendents, your DI system would need the Context to select the proper one. See BuildContext::findAncestorWidgetOfExactType to get an idea why/how.

This architectural style is the one seemingly encouraged by plain vanilla Flutter, with its paradigms of app-as-a-widget ("turtles all the way down"), non-visual widgets, layout-as-widgets and InheritedWidget for DI (which provider uses I believe)

BUT

Modern app frameworks libs (e.g. redux, mobx) encourage the opposite kind of meta-architecture: widgets at the bottom of the pyramid.

Here widgets are "dumb", just UI signal generators and receivers. The business logic is encapsulated in a "Store" or via "Actions" which interact with a store. The widgets just react to the relevant fields on the store being updated and send Action signals when the user interacts with them.

Which should you use?

In my experience, at least on mobile where the screen realestate is less, scoping a model to a branch in the render tree is seldom required. If it suddenly becomes important then there are plenty of other ways to handle it (indexed array, id lookup map, namedInstances in get_it) than to require linking it to the semantics of UI rendering.

Currently, having spent too much time in iOS ViewControllers, I'm a fan of new systems which enforce better SoC. And personally find Flutter's everything-is-a-widget pardigm to appear a bit messy at times if left untended. But ultimately it's a personal preference.

3

you can use navigator key

final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

and put this key in MaterialApp and wrap it with your provider (TenderApiData)

  ChangeNotifierProvider<TenderApiData>(
              create: (_) => TenderApiData(),
              child: Consumer<TenderApiData>(builder: (context, tenderApiData , child) {
                return  MaterialApp(
          navigatorKey: navigatorKey,
    
                        title: 'title',
                      home: SplashScreen());
              }),
            );

and listen to this provider from anywhere with this navigator key

navigatorKey.currentContext?.read<TenderApiData>();
0

There is another solution that does not use GetIt or GlobalKey. It uses the Singleton design pattern.

For instance:


class BackgroundTaskProvider extends ChangeNotifier {

  static final BackgroundTaskProvider _backgroundTaskProvider = BackgroundTaskProvider._internal();

  factory BackgroundTaskProvider() {
    return _backgroundTaskProvider;
  }

  BackgroundTaskProvider._internal();

  List<Task> backgroundTasks = [];
  
  addBackgroundTask(Task task) {
    backgroundTasks.add(task);
    notifyListeners();
  }

}

Than you could do something like:

BackgroundTaskProvider().addBackgroundTask(Task()));

from everywhere in your code. Be aware use with caution make sure to understand the singleton pattern and its pro's and cons.

Motivation

I agree with the answer from Rémi Rousselet that Singleton and GetIt should not be used generally. However, I found my self wanting to add something to the provider across an asynchronous gap. Something like:

Task task = await getSomeTask();
BackgroundTaskProvider provider = Provider.of<MyDependency>(context);
if(mounted) provider.addToBackgroundTasks(task);

If the state is not mounted anymore, the task end up not being added to the state of the BackGroundTask provider.

Not the answer you're looking for? Browse other questions tagged or ask your own question.