Не уверен, как правильно реализовать ChangeNotifierProvider для запоминания вошедшего в систему пользователя во Flutter

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

Я могу получить пользователя из 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 я попытался связать then с вызовом getUserData(), чтобы установить пользователя, однако это, конечно, возвращает следующее утверждение:

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

Как я могу правильно настроить пользователя, когда приложение загружается, если кто-то уже вошел в систему, не выполняя приведенное выше утверждение? Спасибо!


comment
Чтобы уточнить, у меня проблема, когда я вызываю setUser с моим UserProvider в FutureBuilder. Я просто не уверен, как правильно настроить этого пользователя на лету, чтобы я мог получить его на экране, который загружается с User user = Provider.of ‹UserProvider› (context) .user;   -  person MMDev    schedule 24.12.2020


Ответы (2)


Общее предпочтение можно использовать следующим образом:

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);
}
person ishak Akdaş    schedule 24.12.2020
comment
Извините, если я не понял, часть общих настроек у меня работает нормально. Проблема заключается в настройке пользователя в провайдере, поскольку это вызывает утверждение, упомянутое выше. - person MMDev; 24.12.2020

Я могу привести пример из своего приложения:

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-вызовов. В основном я многому научился, и у меня была собственная тема, касающаяся входа в систему и обновления пользовательских сеансов. Если вы хотите узнать об этом больше, ответьте на один из моих старых вопросов:

flutter firebase автоматически обновляет сеанс пользователя с помощью refreshToken

Я бы сказал, что последняя ссылка - самый простой способ реализовать обновление сеанса. Работая с официальными библиотеками flutterfire, вот пример из документации, как его обновить:

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

PS: Если внутри функции некоторые условия не верны, например, сеанс истек, данные неверны, например. напишите функцию выхода, чтобы пользователи снова входили в систему. Также не забудьте добавить новые значения (дату истечения срока действия после обновления и токены) в свой Shared Preferences, поскольку вы все время получаете значения из него.

person Marcel Dz    schedule 24.12.2020
comment
Я не использую firebase для своей реализации. Как объявлен ваш провайдер аутентификации, который вы используете в файле main.dart? Моя проблема заключается в том, что я использую свой UserProvider для установки пользовательских данных, которые я получил из моих общих настроек. Затем я получаю опубликованное мной утверждение. - person MMDev; 24.12.2020
comment
В первую очередь необходимо починить будущего строителя. Будущее, которое вы там определили, должно быть вне вашей сборки, это действительно важно. вы должны вызвать его в initstate перед сборкой виджета. Вы можете просто создать для него функцию. Вызов функции в сборке заставляет состояние вашего приложения перестраиваться более одного раза. согласно поставщикам и вашей ошибке, он все время получает новое состояние - person Marcel Dz; 25.12.2020