Главная страница AppWidget не меняется при изменении состояния

Использование Riverpod + StateNotifier, но я думаю, что с другими поставщиками есть та же проблема.

У меня есть класс аутентификации StateNotifier и StateNotifierProvider, и я обернул виджет MaterialApp в Riverpod Consumer, чтобы восстановить полное дерево приложений / виджетов, когда пользователь больше не аутентифицируется.

Как только я перехожу с помощью pushReplacementNamed на третью страницу и обновляю состояние authenticationStateNotifierProvider, я вижу, что срабатывает метод сборки потребителя, обертывающего приложение, и состояние обновляется (печать (состояние)), но домашняя страница и виджет дерево не перестраивается.

Пример приложения с 3 экранами с проблемой:

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 {}

Если вы протестируете приложение, вы увидите, что управление состоянием работает между входом в систему и домашней страницей, как и ожидалось с кодом, но как только вы перейдете на второй экран с домашней страницы и нажмете кнопку выхода, состояние изменится в приложении, но дерево виджетов не обновляется.


person Alex Bibiano    schedule 16.12.2020    source источник
comment
потому что вы используете statelessWidget. Вместо этого используйте statefulWidget.   -  person John Joe    schedule 16.12.2020
comment
Я использую statelessWidget, потому что использую Consumer для восстановления виджета и управления состоянием. Фактически, если я отлаживаю, метод сборки выполняется при изменении состояния аутентификации. Проблема в том, что MaterialApp не воссоздает дерево виджетов с новым домашним виджетом.   -  person Alex Bibiano    schedule 16.12.2020
comment
Какого типа состояние вашего authenticationStateNotifierProvider? (состояние Authenticated) кажется странным, не читается как enum, поэтому просто интересно, не в этом ли проблема. т.е. правильно ли написано ваше состояние?   -  person Alex Hartford    schedule 17.12.2020
comment
Я отредактировал вопрос с помощью рабочего образца кода, чтобы продемонстрировать проблему. Использование state_notifier и flutter_riverpod в pubspec. Образец приложения также на github.com/abibiano/riverpod_test   -  person Alex Bibiano    schedule 17.12.2020


Ответы (1)


Проблема в вашем onGenerateRoute, вы всегда перенаправляете на HomeScreen в другом случае. Свойство home в MaterialApp вызывается только при первом открытии приложения. Итак, чтобы по-настоящему решить вашу проблему (я видел этот коммит в вашем репо, который является обходной путь, который не сработает, если сеанс вашего пользователя был признан недействительным извне), вы должны добавить что-то вроде следующего:

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());
},

Я думаю, это должно сработать для вас.

person Alex Hartford    schedule 17.12.2020
comment
Спасибо за Ваш ответ. Использование вашего предложения и добавление на втором экране Navigator.pushReplacementNamed (context, '/',) перед изменением состояния решило мою проблему. - person Alex Bibiano; 18.12.2020