android - Firebase 身份验证不会在 Flutter 中保留在 iOS 或 Android 上
问题描述
首先,我在 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,
),
))
],
),
),
),
);
}
}
我正在使用提供者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
HomeScreen 导航到的:
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()
用于 modals/alertDialogs,以及 QR 扫描仪之类的东西返回到上一个屏幕。
抱歉,如果这是太多信息,或者我忘记了很多东西。一个多星期以来,我一直在努力解决这个问题,我有点沮丧。
感谢您的所有回复。
仅仅因为我认为看看我已经看过的内容很重要,这里列出了我看过的其他几个没有帮助的问题。
我相信这个日期是 2020 年 8 月,特别是考虑到onAuthStateChanges
它已更改为流authStateChanges()
。
我也尝试过以此处文档中描述的确切方式实现身份验证,但同样的问题。
我也尝试过使用:
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
我也尝试了所有这些方法作为异步任务,看看是否可能只需要一秒钟来加载,以及同样的问题。最奇怪的是使用当前方法,如果我从中取出if(snapshot.connectionState == ConnectionState.waiting)
它会立即LoadingScreen
打印出来,然后再打印一次,然后它会导航到no user is logged in
user is logged in
no user is logged in
AuthScreen
解决方案
如果您按照我在上面所做的操作并进行一次更改,它将适用于持久登录。
改变:
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();
},
);
}
}
推荐阅读
- java - Array中的RecyclerView可点击按钮
- sql - 如何组合查询搜索以显示某些定义的值,同时不显示特定的定义值?
- javascript - 如何删除jpeg图像中的滚动条
- python - 即使我找不到任何代码,代码也会给我语法错误
- sql-server - SQL链接服务器Python pyodbc插入错误
- python - Django电子商务中的URL安全
- html - (415) 不支持的媒体类型:相同的脚本适用于我的笔记本电脑,但不适用于 azure runbook ((415) 不支持的媒体类型)
- angular - 如何更改 mat-button-toggle 的值
- azure - Azure eventthub EventHubConsumerAsyncClient 无法在 Springboot 非 Web 应用程序中启动
- nginx - 删除包含太多文件的目录时服务器关闭