Repository navigation
providers: are mounted above AuthService, so getJwt() returns null #141
Description
What I'm seeing
On current main, StandardApp mounts the widgets from providers:
above AuthService in the tree, which means a provider can't resolve the
authentication state from its own context. I ran into this while looking at
whether my app could move off v1.1.2, so I may be missing some context for
the change — apologies if this is already known or intended.
The case I noticed is flutter_gql_acsys, which builds its client like so:
// flutter_gql_acsys/lib/src/provider.dart
ACSysService(jwt: AuthService.getJwt(context))Since getJwt is a dependOnInheritedWidgetOfExactType lookup and only
walks ancestors, with AuthService below the provider it finds nothing and
the service ends up with jwt: null. The provider also no longer has a
dependency on the credentials, so it isn't rebuilt on login. There's no
error either way, which is what made it hard to spot.
The ordering
v1.1.2 (1d5f92a) — AuthService wrapped everything:
home: ToastificationWrapper(
child: SelectionArea(
child: AuthService(
child: ... _GlobalStateProvider(child: scaffold) ...main — AuthService is applied first and the providers wrap it:
if (authInfo != null) {
tmpScaffold = AuthService(authInfo: authInfo!, child: tmpScaffold);
}
...
final scaffold = providers.fold<Widget>(tmpScaffold, (w, p) => p(child: w));So the tree went from AuthService → _GlobalStateProvider → providers → Scaffold
to providers → _GlobalStateProvider → AuthService → Scaffold.
git log -S points at 7c936b7 (":art: improve our scaffolding"), though I
haven't traced whether that was the intent of the change or a side effect.
One thing I might be misreading
The comment alongside it says:
We need the AuthService to be below the providers because when you log in
and log out, the providers will restart their data acquisition.
If I'm following correctly, providers only rebuild on an auth change when
AuthService is above them — as a descendant it rebuilds just its own
subtree. So the current order may not be achieving what the comment is
after. Quite possibly I've got the direction backwards, though, and I'd
rather ask than assume.
This also looks related to #49, which covered the same shape of problem
from the app-developer side.
Impact
Read-only apps won't notice, since ACNET reads don't need a token. An app
that sets devices would get a client with no credentials and no obvious
reason why. Anything still on v1.1.2 is unaffected.
Possible fix
Folding the providers before wrapping in AuthService would restore the
v1.1.2 ordering. A small test asserting that a widget mounted through
providers: can resolve AuthService from its context would make it
obvious if it ever flips again.
Happy to try a branch against a real app if that would help.
Activity
rneswold commentedon Sep 1, 2026
Crud. I have an app doing a lot of read-only traffic and, during the shutdown, the hardware was unavailable so my app would show an error box. While testing the app, I logged out and the error box showed again because, when
AuthServicewas above the providers, it restarted the GraphQL requests which received the errors it reported again. I don't want app writers to have to filter these repeated errors or have to detect when they were rebuilt. But, clearly, this is a worse situation.If the GraphQL provider widgets are
StatefulWidgets, they shouldn't rebuild their GraphQL client handles. 🤔 Is the problem that the providers'.build()methods are rebuilding the downstream widgets and they are reissuing the requests?rneswold commentedon Sep 1, 2026
I think the providers need to be further down the tree from
AuthService. If we log out, we want to rebuild the GraphQL client without the JWT so they can't make settings. The fact that the providers call.getJwt()means they get registered to rebuild themselves whenAuthServicechanges state.I think the global state should be above everything so it doesn't get reset when any of those other widgets change state. So the new hierarchy is: