【问题标题】:Flutter - Looking up a deactivated widget's ancestor is unsafe with Provider package, FireStore authenticationFlutter - 使用 Provider 包、FireStore 身份验证查找已停用小部件的祖先是不安全的
【发布时间】:2020-04-22 01:56:12
【问题描述】:

我在使用 Provider 包通过 SnackBar 显示消息时遇到问题。我得到的错误信息是:

VERBOSE-2:ui_dart_state.cc(157)] Unhandled Exception: Looking up a deactivated widget's ancestor is unsafe.
At this point the state of the widget's element tree is no longer stable.
To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.
#0      Element._debugCheckStateIsActiveForAncestorLookup.<anonymous closure> (package:flutter/src/widgets/framework.dart:3508:9)
#1      Element._debugCheckStateIsActiveForAncestorLookup (package:flutter/src/widgets/framework.dart:3522:6)
#2      Element.findAncestorStateOfType (package:flutter/src/widgets/framework.dart:3641:12)
#3      Scaffold.of (package:flutter/src/material/scaffold.dart:1313:42)
#4      LoginScreen.build.<anonymous closure>.<anonymous closure> (package:zvjs_app/screens/login_screen.dart:74:38)
<asynchronous suspension>
#5      GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:182<…>

Bellow 是我的代码,我认为所有的类都是需要的逻辑的一部分。我不明白为什么 Future 不“可用”或者错误在 sigIn 方法中的 user_log_in_provider.dart 中意味着什么。我还尝试通过变量 _errorMessage 从 sigIn 方法显示 errorMessage,您可以在 user_log_in_provider.dart 中看到该变量,然后检查此消息是否不为空。以这种方式代码运行,但它显示一条消息延迟。前面。第一次登录失败(错误的电子邮件格式)-> 没有显示消息。第二次登录失败(密码错误)-> 显示邮件格式错误。

main.dart

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => UserLogIn.instance()),
        ChangeNotifierProvider.value(value: Accommodations()),
      ],
      child: MaterialApp(
        title: 'ZVJS',
        theme: ThemeData(
            primarySwatch: Colors.blue,
            buttonTheme: ButtonThemeData(
              buttonColor: Colors.blue[300],
              padding: EdgeInsets.symmetric(
                vertical: 8.0,
                horizontal: 16.0,
              ),
            )),
        home: MyHomePage(),
        routes: {
          RegistrationScreen.routeName: (context) => RegistrationScreen(),
          MainScreen.routeName: (context) => MainScreen(),
          LoginScreen.routeName: (context) => MyHomePage(),
        },
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Consumer<UserLogIn>(
      builder: (context, user, _) {
        switch (user.status) {
          case Status.Uninitialized:
//            return Splash();
          case Status.Unauthenticated:
          case Status.Authenticating:
            return LoginScreen(
                emailController: _emailController,
                passwordController: _passwordController);
          case Status.Authenticated:
            return MainScreen();
          default:
            return ErrorPage();
        }
      },
    );
  }
}

login_screen.dart

class LoginScreen extends StatelessWidget {
  static const routeName = '/loginScreen';

  final _emailController;
  final _passwordController;

  LoginScreen(
      {@required TextEditingController emailController,
      @required TextEditingController passwordController})
      : this._emailController = emailController,
        this._passwordController = passwordController;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(Constants.logInPageTitle),
      ),
      body: Provider.of<UserLogIn>(context).status == Status.Authenticating
          ? SpinnerCustom(Constants.loggingIn)
          : Center(
              child: SingleChildScrollView(
                child: Column(
                  children: <Widget>[
                    Padding(
                      padding: const EdgeInsets.symmetric(horizontal: 8.0),
                      child: TextFieldCustom(
                        text: Constants.email,
                        controller: _emailController,
                        icon: Icon(Icons.email),
                        textInputType: TextInputType.emailAddress,
                      ),
                    ),
                    const SizedBox(height: 20),
                    Padding(
                      padding: const EdgeInsets.symmetric(horizontal: 8.0),
                      child: TextFieldCustom(
                        text: Constants.password,
                        controller: _passwordController,
                        icon: Icon(Icons.lock),
                        textInputType: TextInputType.visiblePassword,
                      ),
                    ),
                    const SizedBox(height: 10),
                    Builder(
                      builder: (ctx) => ButtonCustom(
                        text: Constants.logIn,
                        onPressed: () async {
                          var provider = Provider.of<UserLogIn>(ctx, listen: false);
                          String message = await provider.signIn(
                                _emailController.text,
                                _passwordController.text);
                          if (message != null) {
                            Scaffold.of(ctx).showSnackBar(SnackBar(
                              content: Text(message),
                            ));
                          }
                        },
                      ),
                    ),
                  ],
                ),
              ),
            ),
    );
  }
}

user_log_in_provider.dart

enum Status { Uninitialized, Authenticated, Authenticating, Unauthenticated }

class UserLogIn with ChangeNotifier {
  FirebaseAuth _auth;
  FirebaseUser _user;
  Status _status = Status.Uninitialized;
  String _errorMessage;

  UserLogIn.instance() : _auth = FirebaseAuth.instance {
    _auth.onAuthStateChanged.listen(_onAuthStateChanged);
  }

  Status get status => _status;

  FirebaseUser get user => _user;

  String get errorMessage => _errorMessage;

  Future<String> signIn(String email, String password) async {
    try {
      _status = Status.Authenticating;
      notifyListeners();
      await _auth.signInWithEmailAndPassword(email: email, password: password);
      return null;
    } catch (e) {
      _errorMessage = e.message;
      print(_errorMessage);
      _status = Status.Unauthenticated;
      notifyListeners();
      return e.message;
    }
  }

  Future<void> _onAuthStateChanged(FirebaseUser firebaseUser) async {
    if (firebaseUser == null) {
      _status = Status.Unauthenticated;
    } else {
      _user = firebaseUser;
      _status = Status.Authenticated;
    }
    notifyListeners();
  }
}

【问题讨论】:

  • 你找到解决方案了吗?
  • @IdrisStack 不,还是什么都没有。
  • 你找到解决办法了吗?

标签: flutter dart flutter-provider


【解决方案1】:

问题:

由于此代码,您会收到错误:

 Scaffold.of(ctx).showSnackBar(SnackBar(
  content: Text(message),
 ));

Scaffold.of(context) 正在尝试在不再位于其上方的小部件树中查找脚手架。

问题是这样产生的:

  1. 登录调用被异步触发:String message = await provider.signIn(...);
  2. 在等待调用时,按钮小部件的父级可能已更改,或者按钮本身可能已从树中删除。
  3. 然后,当调用 Scaffold.of(ctx).showSnackbar(...) 时,它现在正尝试在不存在的小部件树中查找脚手架。

解决办法:

有几个解决方案。其中之一是使用一个全局脚手架来包装你的每条路线。然后可以使用该脚手架键来显示小吃店。

这是如何做到的:

为您的 MaterialApp 构建器添加一个脚手架。 确保使用全局密钥。

final globalScaffoldKey = GlobalKey<ScaffoldState>();

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(

      ...

      child: MaterialApp(
       builder: (context, child) {
        return Scaffold(
          key: globalScaffoldKey,
          body: child,
        );
      },
...

然后您可以使用该键通过全局函数显示小吃吧:

 void showSnackbar(String message) {
    var currentScaffold = globalScaffoldKey.currentState;
    currentScaffold.hideCurrentSnackBar(); // If there is a snackbar visible, hide it before the new one is shown.
    currentScaffold.showSnackBar(SnackBar(content: Text(message)));
  }

用法如下所示,您可以从代码中的任何位置安全地调用它:

showSnackbar('My Snackbar Message')

【讨论】:

  • ScaffoldState.showSnackBar 自 v1.23.0-14.0.pre 起已弃用
  • 这个答案还能用吗? ScaffoldState 已弃用
猜你喜欢
  • 2020-08-08
  • 2021-07-28
  • 2021-06-03
  • 2021-04-29
  • 2020-06-19
  • 2021-11-15
  • 2020-12-26
  • 2022-06-26
  • 1970-01-01
相关资源
最近更新 更多