Firebase 身份验证不会在 Flutter 中保留在 iOS 或 Android 上
Posted
技术标签:
【中文标题】Firebase 身份验证不会在 Flutter 中保留在 iOS 或 Android 上【英文标题】:Firebase auth not persisting on iOS or Android in Flutter 【发布时间】:2021-08-29 19:03:04 【问题描述】:首先,我在 Stack Overflow 上就这个话题经历了 20 多个不同的问题和解决方案(其中大部分与网页版有关),我也尝试过 twitter,甚至 FlutterDev Discord 服务器也无法好像发现了这个问题。
我正在使用 firebase 对我的应用进行移动身份验证,无论我尝试什么,我似乎都无法让持久身份验证状态在 ios 或 android 上工作。
这是我的主要内容:
Future<void> main() async
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(
MultiProvider(
...
child: const MyApp(),
),
);
class MyApp extends StatelessWidget
const MyApp(Key? key) : super(key: key);
final const ColorScheme colorScheme = ColorScheme(
...
);
@override
Widget build(BuildContext context)
bool isDebug = false;
if (Constants.DEBUG_BANNER == 'true')
isDebug = true;
return MaterialApp(
theme: ThemeData(
...
),
routes:
// This is a general layout of how all my routes are in case this is the issue
Screen.route: (BuildContext context) => const Screen(),
,
home: const HomeScreen(),
debugShowCheckModeBanner: isDebug,
);
...
只是我认为与我的问题无关的代码,因此为了简洁起见,我将其隐藏起来。主要是主题和私人数据
让我们从我的 google-sign-in-button 开始,如有必要,我可以分享其他重要的信息。我们在 iOS 上使用 Facebook、Google 和 Apple。
class GoogleSignInButton extends StatefulWidget
const GoogleSignInButton(Key? key) : super(key: key);
@override
_GoogleSignInButtonState createState() => _GoogleSignInButtonState();
class _GoogleSignInButtonState extends State<GoogleSignInButton>
bool _isSigningIn = false;
@override
Widget build(BuildContext context)
return Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: _isSigningIn
? CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(MRRM.colorScheme.primary),
)
: OutlinedButton(
key: const Key('google_sign_in_button'),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.white),
shape: MaterialStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(40),
),
),
),
onPressed: () async
setState(()
_isSigningIn = true;
);
context.read<Member>().signInWithGoogle(context: context).then<void>((void user)
setState(()
_isSigningIn = false;
);
Navigator.pushReplacementNamed(context, UserInfoScreen.route);
);
,
child: Padding(
padding: const EdgeInsets.fromLTRB(0, 10, 0, 10),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Image(
image: AssetImage('assets/images/png/google_logo.png'),
height: 35.0,
),
Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
'Sign in with Google',
style: TextStyle(
fontSize: 20,
color: MRRM.colorScheme.secondary,
fontWeight: FontWeight.w600,
),
))
],
),
),
),
);
我正在使用provider pub,这是context.read<Object?>()
的来源。
这里是signInWithGoogle
函数;
Future<String> signInWithGoogle(required BuildContext context) async
final FirebaseAuth _auth = FirebaseAuth.instance;
final GoogleSignIn googleSignIn = GoogleSignIn();
final GoogleSignInAccount? googleSignInAccount =
await googleSignIn.signIn();
if (googleSignInAccount != null)
final GoogleSignInAuthentication googleSignInAuthentication =
await googleSignInAccount.authentication;
final AuthCredential credential = GoogleAuthProvider.credential(
accessToken: googleSignInAuthentication.accessToken,
idToken: googleSignInAuthentication.idToken,
);
try
final UserCredential userCredential =
await _auth.signInWithCredential(credential);
_firebaseUser = userCredential.user!;
_authType = AuthType.Google;
_uuId = _firebaseUser.uid;
notifyListeners();
on FirebaseAuthException catch (e)
if (e.code == 'account-exists-with-different-credential')
ScaffoldMessenger.of(context).showSnackBar(
customSnackBar(
content: 'The account already exists with different credentials.',
),
);
else if (e.code == 'invalid-credential')
ScaffoldMessenger.of(context).showSnackBar(
customSnackBar(
content: 'Error occurred while accessing credentials. Try again.',
),
);
catch (e)
ScaffoldMessenger.of(context).showSnackBar(
customSnackBar(
content: 'Error occurred using Google Sign-In. Try again.',
),
);
return getMemberLogin();
这包含在我的 Member 对象中,它只存储所有 Auth 数据以及来自我们内部 API 之一的特定于成员的数据,并且成员数据作为 App State 对象存储在提供程序中,它链接在 main.dart 文件中
getMemberLogin()
函数只是从身份验证中获取 UUID 并将其发送到 API 并获取内部成员数据,我希望一个简单的发布请求不是导致这种情况的原因。但如果您认为它可能会让我知道,我会尝试在混淆任何 NDA 相关数据的同时发布它。
这是处理初始路由并转到loadingScreen
的主屏幕/启动屏幕,它应该检查是否存在持久登录并转到 UserInfo 屏幕而不是 Auth 屏幕。
class HomeScreen extends StatefulWidget
const HomeScreen(Key? key) : super(key: key);
static const String route = '/home';
@override
_HomeScreenState createState() => _HomeScreenState();
class _HomeScreenState extends State<HomeScreen>
@override
Widget build(BuildContext context)
return Scaffold(
body: Column(
key: const Key('Home'),
children: <Widget>[
Expanded(
child: Image.asset('assets/images/png/Retail_Rebel_Primary.png'),
),
BlinkingTextButton(
key: const Key('blinking_text_button'),
textButton: TextButton(
child: Text(
'Tap to continue',
style: TextStyle(
color: MRRM.colorScheme.primary,
fontSize: 16.0,
),
),
onPressed: ()
Navigator.of(context).pushReplacementNamed(LoadingScreen.route);
,
),
),
Container(
height: 8.0,
),
],
),
);
最后,这是主屏幕导航到的LoadingScreen
:
class LoadingScreen extends StatelessWidget
const LoadingScreen(Key? key) : super(key: key);
static const String route = '/loadingScreen';
@override
Widget build(BuildContext context)
return StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (BuildContext context, AsyncSnapshot<User?> snapshot)
if (snapshot.connectionState == ConnectionState.waiting)
if (snapshot.hasData)
print('user is logged in');
SchedulerBinding.instance!.addPostFrameCallback((_)
Navigator.of(context).pushReplacementNamed(UserInfoScreen.route);
);
return const Text('');
else
print('no user is logged in');
SchedulerBinding.instance!.addPostFrameCallback((_)
Navigator.of(context).pushReplacementNamed(AuthScreen.route);
);
return const Text('');
return const SplashScreen();
,
);
不确定我处理路由的方式是否可能是问题,但我使用Navigator.of(context).pushReplacementNamed();
是很常见的,除非需要弹出然后我通常只使用Navigator.of(context).pop();
。我通常只将.pop()
用于模态框/alertDialogs,以及诸如 QR 扫描仪之类的东西返回到上一个屏幕。
对不起,如果信息太多,或者我忘记了很多东西。一个多星期以来,我一直在努力解决这个问题,现在有点沮丧。
感谢您的所有回复。
仅仅因为我认为看看我已经看过的内容很重要,这里列出了我看过的其他几个问题,但没有帮助。
This 我相信它的日期为 2020 年 8 月,特别是考虑到 onAuthStateChanges
已更改为流 authStateChanges()
。
我也尝试过以文档here 中描述的确切方式实现身份验证,但同样的问题。
我也尝试过使用:
FirebaseAuth.instance.authStateChanges().then((User? user)
if (user != null)
Navigator.of(context).pushReplacementNamed(UserInfoScreen.route);
else
Navigator.of(context).pushReplacementNamed(AuthScreen.route);
这没有用。我还尝试简单地检查是否有当前用户:
User user = FirebaseAuth.instance.currentUser;
if (user != null && user.uid != null)
Navigator.of(context).pushReplacementNamed(UserInfoScreen.route);
else
Navigator.of(context).pushReplacementNamed(AuthScreen.route);
仍然总是去AuthScreen
我也尝试了所有这些方法作为异步任务,看看是否可能只需要一秒钟的时间来加载,以及同样的问题。最奇怪的是使用当前方法,如果我从LoadingScreen
中取出if(snapshot.connectionState == ConnectionState.waiting)
,它将立即打印出no user is logged in
,然后是user is logged in
,然后再次no user is logged in
,然后它将导航到AuthScreen
【问题讨论】:
最后一个方法可以用ConnectionState.active代替connectionstate.waiting Welp 成功了,谢谢...... 【参考方案1】:如果您按照我在上面所做的操作并进行一次更改,它将适用于持久登录。
改变:
class LoadingScreen extends StatelessWidget
const LoadingScreen(Key? key) : super(key: key);
static const String route = '/loadingScreen';
@override
Widget build(BuildContext context)
return StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (BuildContext context, AsyncSnapshot<User?> snapshot)
if (snapshot.connectionState == ConnectionState.waiting)
if (snapshot.hasData)
print('user is logged in');
SchedulerBinding.instance!.addPostFrameCallback((_)
Navigator.of(context).pushReplacementNamed(UserInfoScreen.route);
);
return const Text('');
else
print('no user is logged in');
SchedulerBinding.instance!.addPostFrameCallback((_)
Navigator.of(context).pushReplacementNamed(AuthScreen.route);
);
return const Text('');
return const SplashScreen();
,
);
到
class LoadingScreen extends StatelessWidget
const LoadingScreen(Key? key) : super(key: key);
static const String route = '/loadingScreen';
@override
Widget build(BuildContext context)
return StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (BuildContext context, AsyncSnapshot<User?> snapshot)
if (snapshot.connectionState == ConnectionState.active)
if (snapshot.hasData)
print('user is logged in');
SchedulerBinding.instance!.addPostFrameCallback((_)
Navigator.of(context).pushReplacementNamed(UserInfoScreen.route);
);
return const Text('');
else
print('no user is logged in');
SchedulerBinding.instance!.addPostFrameCallback((_)
Navigator.of(context).pushReplacementNamed(AuthScreen.route);
);
return const Text('');
return const SplashScreen();
,
);
【讨论】:
以上是关于Firebase 身份验证不会在 Flutter 中保留在 iOS 或 Android 上的主要内容,如果未能解决你的问题,请参考以下文章
Flutter - 将 Firebase 通知推送给没有 Firebase 身份验证的特定用户
Flutter + Firebase Auth:有啥方法可以在 Web 上使用 Firebase 电话身份验证重新发送短信验证码?
Firebase (Flutter) 验证电话号码始终需要 reCAPTCHA