如何在Navigator中使用ChangeNotifier

时间:2020-02-19 16:04:31

标签: flutter dart

在我的应用程序中,我有一个模型来存储在我的应用程序中登录的用户。

class AuthenticationModel extends ChangeNotifier {
  User _user;
  User get user => _user;

  void authenticate(LoginData loginData) async {
    // _user = // get user from http call
    notifyListeners();
  }

  void restoreUser() async {
    //_user = // get user from shared prefs
    notifyListeners();
  }
}

模型被注册在小部件树的顶部:

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => AuthenticationModel(),
      child: MaterialApp(
        title: 'My App',
        initialRoute: '/',
        routes: {
          '/': (context) => PrehomeScreen(),
          '/home': (context) => HomeScreen()
        },
      ),
    );
  }
}

在小部件树下的某个地方,我有一个调用Model的按钮:

          child: Consumer<AuthenticationModel>(
            builder: (context, authModel, child) {
              return MyCustomButton(
                text: 'Connect',
                onPressed: () {
                  authModel.authenticate(...)
                },
              );
            },
          ),

现在,我想在某个地方,当用户在模型中不为空时,听AuthenticationModel上的更改以触发Navigator.pushReplacmentNamed('/home')

我尝试在Prehome的构建器中进行此操作:

class PrehomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<AuthenticationModel>(
      builder: (context, authModel, child) {
        if (authModel.user != null) {
          Navigator.of(context).pushReplacementNamed("/home")
        }
        return Container(
          child: // Prehome UI
        );
      },
    );
  }
}

但是这样做时出现错误:

════════ (2) Exception caught by widgets library ═══════════════════════════════════════════════════
setState() or markNeedsBuild() called during build.
The relevant error-causing widget was:
  Consumer<AuthenticationModel> file:///Users/pierre.degand/Projects/cdc/course_du_coeur/lib/Prehome.dart:13:12
═══════════════════════════════════════════════════════════════════════════════

如何设置这样的监听器?在这样的模型更改上触发导航是一种好习惯吗?

谢谢

编辑:我找到了一种使这项工作有效的方法。我没有在PrehomeScreen构建器中使用Consumer,而是使用了以下代码:

class PrehomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {

    Provider.of<AuthenticationModel>(context).addListener(() {
      Navigator.of(context).pushReplacementNamed("/home");
    });

    return Container(
      child: // UI
    );
  }
}

它工作正常,在模型更改时执行导航。但是控制台中有一条错误消息(打印了3次):

════════ (4) Exception caught by foundation library ════════════════════════════════════════════════
Looking up a deactivated widget's ancestor is unsafe.
════════════════════════════════════════════════════════════════════════════════════════════════════

该应用程序不会崩溃,因此,现在我可以接受。 我仍然想知道这是否是一种好方法。

3 个答案:

答案 0 :(得分:0)

我更喜欢使用Stream或rxdart PublishSubject BehaviourSubject收听任何活动或管理全局应用程序数据。

我使用集团模式实现它。基本上,区块模式就像redux一样,react意味着创建一个包含所有应用程序数据的中央数据集,而您不必进行道具钻探。

您可以像这样创建Stream。

UnicodeError: encoding with 'idna' codec failed (UnicodeError: label empty or too long)

现在您可以从任何地方访问该abcBloc变量,并像这样收听连接性变量

import 'package:rxdart/rxdart.dart';

class AbcBloc {
  BehaviorSubject<bool> _connectivity;
  AbcBloc() {
    _connectivity = BehaviorSubject<bool>();
  }
  // stream
  Stream<bool> get connectivity => _connectivity.stream;
  // sink
  Function(bool) get updateConnectivity => _connectivity.sink.add;
dispose(){
    _connectivity.close();
}
}

void createAbcBloc() {
  if (abcBloc != null) {
    abcBloc.dispose();
  }
  abcBloc = AbcBloc();
}

AbcBloc abcBloc = AbcBloc();


,您可以从abcBloc.updateConnectivity(false);更新连接。 每次执行任何更改时,监听器都会被调用。

请记住,您必须调用一次listenConnectivity()才能将其激活;

答案 1 :(得分:0)

void main() {
  Provider.debugCheckInvalidValueType = null;

  return runApp(
    Provider(
      create: (_) => AuthenticationModel(),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final navigatorKey = GlobalKey<NavigatorState>();

    Provider.of<AuthenticationModel>(context).addListener(() {
      final authModel = Provider.of<AuthenticationModel>(context);

      if (authModel.user != null) {
        navigatorKey.currentState.pushReplacementNamed("/home");
      }
    });

    return MaterialApp(
      navigatorKey: navigatorKey,
      title: 'My App',
      initialRoute: '/',
      routes: {
        '/': (context) => PrehomeScreen(),
        '/home': (context) => HomeScreen()
      },
    );
  }
}

答案 2 :(得分:0)

我认为不需要ChangeNotifier

void main() async {
  final isLoggedIn = await Future.value(true); // get value from shared prefs or your model

  runApp(MyApp(isLoggedIn));
}

class MyApp extends StatelessWidget {
  MyApp(this.isLoggedIn);

  final bool isLoggedIn;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: isLoggedIn ? '/home' : '/',
      routes: {
        '/': (context) => HomeScreen(),
        '/login': (context) => LoginScreen()
      },
    );
  }
}

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      child: Text('Logout'),
      onPressed: () => Navigator.of(context).pushReplacementNamed("/login"),
    );
  }
}

class LoginScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      child: Text('Login'),
      onPressed: () => Navigator.of(context).pushReplacementNamed("/"),
    );
  }
}