#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
вызовов whichauthService.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();
},
);
}
}