Alex Bibiano
Alex Bibiano

Reputation: 653

AppWidget home not changing when state changes

Using Riverpod + StateNotifier but I think with other providers there is the same issue.

I have an authentication StateNotifier class and StateNotifierProvider and have wrapped the MaterialApp widget into a Riverpod Consumer to rebuild the complete app/widget tree when user is no longer authenticated.

As soon as I navigate with pushReplacementNamed to a third page and update the state of the authenticationStateNotifierProvider, I can see the build method of the consumer wrapping the App is triggered and the state is updated (print(state)) but the home page and widget tree is not rebuilt.

Sample app with 3 screen with the issue:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/all.dart';

void main() {
  runApp(ProviderScope(child: MyApp()));
}

class MyApp extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
    final state = watch(authenticationNotifier.state);
    print(state);
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: state is Unauthenticated ? LoginScreen() : HomeScreen(),
      onGenerateRoute: (RouteSettings settings) {
        if (settings.name == '/second')
          return MaterialPageRoute(builder: (_) => SecondScreen());
        else
          return MaterialPageRoute(builder: (_) => HomeScreen());
      },
    );
  }
}

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('HomeScreen'),
      ),
      body: Column(
        children: [
          MaterialButton(
            child: Text('Logout'),
            onPressed: () => context.read(authenticationNotifier).toggle(),
          ),
          MaterialButton(
            child: Text('Second'),
            onPressed: () => Navigator.pushReplacementNamed(
              context,
              '/second',
            ),
          ),
        ],
      ),
    );
  }
}

class SecondScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('SecondScreen'),
      ),
      body: MaterialButton(
        child: Text('Logout'),
        onPressed: () => context.read(authenticationNotifier).toggle(),
      ),
    );
  }
}

class LoginScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('LoginScreen'),
      ),
      body: MaterialButton(
        child: Text('Login'),
        onPressed: () => context.read(authenticationNotifier).toggle(),
      ),
    );
  }
}

// Controller.
final authenticationNotifier =
    StateNotifierProvider((ref) => AuthenticationNotifier());

class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
  AuthenticationNotifier() : super(Unauthenticated());

  void toggle() {
    state = state is Unauthenticated ? Authenticated() : Unauthenticated();
  }
}

// State.
abstract class AuthenticationState {}

class Authenticated extends AuthenticationState {}

class Unauthenticated extends AuthenticationState {}

If you test the app you will see state management works between login and home page as expected with the code, but as soon as you navigate to the second screen form the home page and press the logout button, state is changed on the App widged but widget tree is not updated.

Upvotes: 0

Views: 618

Answers (1)

Alex Hartford
Alex Hartford

Reputation: 6000

The problem is in your onGenerateRoute you are always redirecting to the HomeScreen in your else case. The home property on MaterialApp is only called when the app first opens. So, to truly fix your problem (I saw this commit in your repo, which is a workaround that won't work if your user's session were invalidated externally), you should add something like the following:

home: state is Unauthenticated ? LoginScreen() : HomeScreen(),
onGenerateRoute: (RouteSettings settings) {
  if (settings.name == '/second')
    return MaterialPageRoute(builder: (_) => SecondScreen());
  else
    return MaterialPageRoute(builder: (_) => state is Unauthenticated ? LoginScreen() : HomeScreen());
},

I think that should work for you.

Upvotes: 1

Related Questions