Экран не обновляется, когда пользователь входит в систему с аутентификацией Firebase

#firebase #flutter #firebase-authentication

# #firebase #flutter #firebase-аутентификация

Вопрос:

У меня есть приложение Flutter, в котором я использую firebase_auth ^0.18.1 2 для входа пользователя в систему с помощью электронной почты / пароля. Есть два экрана:

  • Начальный экран: где пользователю предоставляется возможность входа в систему
  • Экран панели мониторинга: первый экран, который отображается при входе в систему

Однако после входа пользователя в систему приложение не обновляется для отображения панели мониторинга; продолжает отображаться начальный экран. Кто-нибудь может посоветовать?

После входа в систему появляется панель мониторинга только при выполнении горячего перезапуска.

main.dart :

 MultiProvider(
    providers: [
      // Provider 1
      // ...
      // Provider 2
      // ...
      Provider<AuthService>(
        create: (_) => AuthService(),
      ),
      // Provider 4
      // ...
      // Provider 5
      // ...
    ],
    child: App(),
),
 

auth_service.dart :

 Stream<User> get authStateChanges => FirebaseAuth.instance.authStateChanges();
 

app.dart :

 final AuthService authService =
    Provider.of<AuthService>(context, listen: false);

return MaterialApp(
  debugShowCheckedModeBanner: false,
  home: StreamBuilder<User>(
    stream: authService.authStateChanges,
    builder: (context, snapshot) {
      if (snapshot.connectionState == ConnectionState.active) {

        if (snapshot.hasData) {
        authService.firebaseUser = snapshot.data;

          return Scaffold(
            body: FutureBuilder<DocumentSnapshot>(
              future: authService.fetchExtraUserData(),
              builder: (context, snapshot) {
                  if (snapshot.connectionState == ConnectionState.active) {
                      authService.user = snapshot.data;

                      // Dashboard does not appear, however Flutter logging shows that Dashboard
                      // initState() is being called in the background.

                      if (snapshot.hasData) {
                         return Dashboard();
                      } else {
                         return Scaffold(
                            backgroundColor: Colors.white,
                            body: Center(
                               child: CircularProgressIndicator(),
                             ),
                          );
                      }
                  } else {
                      return Scaffold(
                      backgroundColor: Colors.white,
                      body: Center(
                         child: CircularProgressIndicator(),
                        ),
                    );
                  }
              },
            ),
          );
        } else {
          return Home();
        }
      } else {
        return Scaffold(
          backgroundColor: Colors.white,
          body: Center(
            child: CircularProgressIndicator(),
          ),
        );
      }
    },
  ),
);
 

Я также безуспешно пробовал следующее:

  • Удаление FutureBuilder вызовов which authService.fetchExtraUserData()
  • Перемещение StreamBuilder выше MaterialApp
     return StreamBuilder<User>(
        stream: authService.authStateChanges,
        builder: (context, snapshot) {
          return MaterialApp(
            debugShowCheckedModeBanner: false,
            home: snapshot.hasData ? Dashboard() : Home(),
          );
        },
      );
     

Также интересно видеть, что выход из системы работает правильно — пользователь возвращается на главный экран. Это показывает, что authStateChanges в данном случае работает правильно.

 logout(BuildContext context) async => await authService.signOut();
 

flutter doctor вывод:

 Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel beta, 1.23.0-18.1.pre, on Mac OS X 10.15.7 19H2 x86_64)

[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.1)
[✓] Xcode - develop for iOS and macOS (Xcode 12.3)
[✓] Chrome - develop for the web
[✓] Android Studio (version 4.1)
[✓] VS Code (version 1.52.0)
[✓] Connected device (3 available)
 

Комментарии:

1. какой экран появляется?

2. @PeterHaddad На главном экране. Вместо этого при входе в систему должен отображаться экран панели мониторинга. Я обновил вопрос, чтобы прояснить это

3. Почему вы используете FutureBuilder при отображении панели мониторинга, если вы отображаете панель мониторинга, не учитывая, будет ли будущее (в данном случае AuthService. fetchExtraUserData() ) завершена? Это кажется ненужным, если я здесь чего-то не хватает.

4. Возможно authService.user = snapshot.data; , назначение вызывает перестройку StreamBuilder. Это означало бы, что существует цикл. Лучше избегать побочных эффектов в методах сборки ( authService.firebaseUser = snapshot.data; , authService.fetchExtraUserData() , authService.user = snapshot.data; )

5. @TanayNeotia Моя ошибка snapshot.hasData была опущена из примера кода (который я обновил сейчас). Однако проблема по-прежнему сохраняется.

Ответ №1:

Что ж, у меня есть демо-версия, которая работает отлично, позвольте мне опубликовать соответствующие части:

main.dart я использовал ChangeNotifierProvider.value()

 ...
return MultiProvider(
        providers: [
          ChangeNotifierProvider.value(
            value: AuthenticationService.instance(),
          ),
        ],
        child: MaterialApp(
          theme: ThemeData(
            primarySwatch: Colors.red,
          ),
          home: _screenMaker(context),
        ));
...
 

Это мой виджет _screenMaker, в main.dart котором отображается состояние в реальном времени, поскольку я работаю Consumers с пакетом поставщика

 Widget _screenMaker(BuildContext context) {
  return Consumer<AuthenticationService>(
    builder: (ctx, AuthenticationService auth, _) {
      switch (auth.status) {
        case Status.Uninitialized:
          return Splash();
        case Status.Unauthenticated:
        case Status.Authenticating:
          return LoginPage();
        case Status.NoVerification:
          return Verification();
        case Status.Authenticated:
          return UserInfoPage(
            user: auth.user,
          );
      }
    },
  );
}
 

Давайте посмотрим на мой AuthentificationService класс, я собираюсь опубликовать полный контент, но волшебство для управления состоянием происходит в начале после моих получателей:

 import 'package:flutter/widgets.dart';
import 'package:firebase_auth/firebase_auth.dart';

enum Status { Uninitialized, Authenticated, Authenticating, Unauthenticated , NoVerification}

class AuthenticationService with ChangeNotifier {
  FirebaseAuth _auth = FirebaseAuth.instance;
  User _user;
  Status _status = Status.Uninitialized;

  Status get status => _status;
  User get user => _user;


  // Magic happens here
  AuthenticationService.instance() : _auth = FirebaseAuth.instance {
    _auth.authStateChanges().listen((firebaseUser) {
      print('--- authstate listening');
      if (firebaseUser == null) {
        _status = Status.Unauthenticated;
      } else if(firebaseUser != null amp;amp; firebaseUser.emailVerified == false) {
        _status = Status.NoVerification;
      } else {
        _user = firebaseUser;
        _status = Status.Authenticated;
      }
      notifyListeners();
    });
  }


  Future<bool> signIn(String email, String password) async {
    try {
      _status = Status.Authenticating;
      notifyListeners();
      await _auth.signInWithEmailAndPassword(email: 'admin@admin.de', password: 'admin123');

      if(_auth.currentUser.emailVerified == null){
        _status = Status.Unauthenticated;
        print('--- user email verified is null');
        notifyListeners();
      }
   
      if(_auth.currentUser.emailVerified != true) {
        _status = Status.Authenticated;
        return true;
      } else {
        print('--- user needs confirmation');
        try {
        _status = Status.NoVerification;
        await user.sendEmailVerification();
        signOut();
        return true;
     } catch (e) {
        print('--- An error occured while trying to send email verification');
        print(e.message);
     }
      }
    } catch (e) {
      print('--- $e');
      signOut();
      notifyListeners();
      return false;
    }
  }



  // Signout function
  Future signOut() async {
    _auth.signOut();
    _status = Status.Unauthenticated;
    notifyListeners();
    return Future.delayed(Duration.zero);
  }

}
 

Это все, что вам нужно. Узнайте больше о потребителях, это действительно мощная статья для понимания:
https://medium.com/flutter-community/managing-flutter-state-using-provider-e26c78060c26

Ответ №2:

Я думаю, что проблема связана с false параметрами, переданными в provider.of<T>() вызов.

Попробуйте final AuthService authService = Provider.of<AuthService>(context);

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

Комментарии:

1. Спасибо, но, к сожалению, по-прежнему безуспешно. Экран по-прежнему не обновляется для отображения панели мониторинга.

Ответ №3:

 final AuthService authService =
Provider.of<AuthService>(context, listen: false);
 

В этой строке аргумент listen: false указывает виджету не перестраиваться при изменении состояния (пользователь входит в систему).

Попробуйте удалиться listen: false от провайдера.

Также вместо создания поставщика как

 Provider<AuthService>(
    create: (_) => AuthService(),
  )
 

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

Используется ChangeNotifierProvider для прослушивания изменений и перестройки дерева виджетов следующим образом

 ChangeNotifierProvider(
    create: (context) => AuthService(),
)
 

Комментарии:

1. К сожалению, удаление listen: false не сработало. Я обновил вопрос, чтобы показать, как AuthService создается.

2. Спасибо за помощь, но все равно не повезло после использования ChangeNotifierProvider .

Ответ №4:

Хорошо, попробуйте это,

Сначала измените свой класс api firebase следующим образом

  Stream<User> get user {
    return auth.onAuthStateChanged
        .map((FirebaseUser user) => _userFromFirebase(user));
  }


  User _userFromFirebase(FirebaseUser user) {
    return user != null ? User(id: user.uid) : null;
  } 
 

после того, как в вашем файле main.dart измените следующим образом

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

 class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StreamProvider<User>.value(
      value: AuthService().user,
      child: MaterialApp(
        theme: ThemeData(scaffoldBackgroundColor: Colors.blueAccent),
        debugShowCheckedModeBanner: false,
        home: LoginWrapper(),
        //home: McqLoader(),
      ),
    );
  }
}
 

создайте класс-оболочку для входа следующим образом

 class LoginWrapper extends StatelessWidget {

  @override
  Widget build(BuildContext context) {

    
    final user = Provider.of<User>(context);
    if (user == null) {
      return Login();
    } else {
      return DashboardPage(id: user.id,);
    }
  }
}
 

Ответ №5:

 class LandingPage extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
   final auth = Provider.of<AuthBase>(context);
   return StreamBuilder<Object>(
     stream: auth.onAuthChange,
     builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.active) {
            User user = snapshot.data;
            if (user == null) {
               return SignIn();
            }else {
               return HomePage();
            }
         }
            return CupertinoActivityIndicator();
      },
    );
  }
}