2

我有一个具有工作用户注册的应用程序,并且使用用户提供程序可以在注册期间设置用户并在以下屏幕上检索它而不会出现问题。我遇到的问题是尝试正确实现在应用程序启动时设置存储的当前登录用户并进入仪表板的能力,而不是在不抛出以下断言的情况下完成注册过程。

我可以毫无问题地获取用户,SharedPreferences但是我不确定如何在我的提供程序中正确设置此用户,以便在仪表板屏幕加载时可以访问它。下面是我的main()函数和 MyApp 类。

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  runApp(MultiProvider(
    providers: [
      ChangeNotifierProvider(create: (context) => AuthProvider()),
      ChangeNotifierProvider(create: (context) => UserProvider()),
    ],
    child: MyApp(),
  ));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    Future<User> getUserData() => UserPreferences().getUser();

    return MaterialApp(
        title: 'My App',
        theme: ThemeData(
          primarySwatch: Colors.red,
          accentColor: Colors.black,
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ),
        home: FutureBuilder(
            future: getUserData().then((value) => {
                  Provider.of<UserProvider>(context, listen: false)
                      .setUser(value)
                }),
            builder: (context, snapshot) {
              switch (snapshot.connectionState) {
                case ConnectionState.none:
                case ConnectionState.waiting:
                  return CircularProgressIndicator();
                default:
                  if (snapshot.hasError)
                    return Text('Error: ${snapshot.error}');
                  else if (snapshot.data.token == null)
                    return Login();
                  else if (snapshot.data.token != null) {
                    return Dashboard();
                  } else {
                    UserPreferences().removeUser();
                    return Login();
                  }
              }
            }),
        routes: {
          '/dashboard': (context) => Dashboard(),
          '/login': (context) => Login(),
          '/register': (context) => Register(),
          '/username': (context) => Username(),
        });
  }
}

在 FutureBuilder 中,我尝试在getUserData()调用上链接一个 then 来设置用户,但这当然会返回以下断言:

════════ Exception caught by foundation library ════════════════════════════════
The following assertion was thrown while dispatching notifications for UserProvider:
setState() or markNeedsBuild() called during build.

This _InheritedProviderScope<UserProvider> widget cannot be marked as needing to build because the framework is already in the process of building widgets.  A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.

从我读过的内容来看,这是因为notifyListeners()在用户提供程序中有一个调用,它将调用markNeedsBuild(). 也就是说,我已经尝试在网上移动一些东西和一些建议,但无法弄清楚实现这一点的最佳实践和最佳方法是什么。

用户提供者:

class UserProvider with ChangeNotifier {
  User _user = new User();

  User get user => _user;

  void setUser(User user) {
    _user = user;
    notifyListeners();
  }
}

如果有人已经登录而没有遇到上述断言,我如何在应用程序加载时正确设置用户?谢谢!

4

2 回答 2

1

共享偏好可按如下方式使用:

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() {
  runApp(MaterialApp(
    home: Scaffold(
      body: Center(
      child: RaisedButton(
        onPressed: _incrementCounter,
        child: Text('Increment Counter'),
        ),
      ),
    ),
  ));
}

_incrementCounter() async {
  SharedPreferences prefs = await SharedPreferences.getInstance();
  int counter = (prefs.getInt('counter') ?? 0) + 1;
  print('Pressed $counter times.');
  await prefs.setInt('counter', counter);
}
于 2020-12-24T08:48:03.727 回答
0

我可以从我的应用程序中为您提供一个示例:

home: initScreen == 3 || auth.onBoarding == true
                      ? OnboardingScreen()
                      : auth.isAuth <------ checks if auth is available
                          ? TabsScreen()
                          : FutureBuilder(
                              future: auth.tryAutoLogin(), <--- autologin
                              builder: (ctx, authResultSnapshot) =>
                                  authResultSnapshot.connectionState ==
                                          ConnectionState.waiting
                                      ? SplashScreen()
                                      : AuthScreen()),

上面的代码在我的 main.dart 中。我标记的线条很重要。您可以检查用户是否具有身份验证状态以强制他直接进入您的应用主屏幕。如果没有可用的 authState,您仍然可以通过自动登录功能强制他进入您的主屏幕。这就是我如何让人们在他们只登录一次时进入应用程序。

这是我的身份验证文件中的自动登录功能(我使用的是 firebase):

Future<bool> tryAutoLogin() async {

    final prefs = await SharedPreferences.getInstance();
    if (!prefs.containsKey('userData')) {
      return false;
    }
    final extractedUserData =
        json.decode(prefs.getString('userData')) as Map<String, Object>;
    final expiryDate = DateTime.parse(extractedUserData['expiryDate']);
    final emVerified = extractedUserData['emailVerified'];

     print('emVerified: $emVerified');

    if (emVerified == false || emVerified == null) {
      print('@@@ user email is not verified, logging out user');
      logout();
    }

    if (expiryDate.isBefore(DateTime.now())) {
      refreshSession();
    }

    notifyListeners();
    return true;
  }

我正在检查来自的用户数据SharedPreferences是否有效,我也在检查电子邮件是否经过验证,因为我只希望用户在最后检查会话时确认了他们的电子邮件地址(如果你不需要它,你可以删除这部分)来自用户,如果达到过期日期,我会刷新会话。

按照本主题使用刷新令牌刷新您的 IDToken:https ://firebase.google.com/docs/reference/rest/auth#section-refresh-token

我在刷新令牌时遇到了很多麻烦,因为我使用的是 firebase 库,而不是使用 http 调用来完成所有事情。我基本上从中学到了很多,并且有一个关于登录和刷新用户会话的自己的主题。如果您想了解更多信息,请查看我的一个旧问题:

使用refreshToken颤动firebase自动刷新用户会话

最后一个链接似乎是实现刷新会话的最简单方法。使用官方的flutterfire库,这里是文档中的示例如何刷新它:

https://firebase.flutter.dev/docs/auth/usage/#reauthenticating-a-user

PS:如果函数内部某些条件不成立,例如会话过期,数据错误,例如编写注销函数以强制用户重新登录。另外不要忘记将新值(刷新和令牌后的到期日期)添加到您Shared Preferences的值,因为您一直从中获取值。

于 2020-12-24T08:42:53.247 回答