Flutter集成Appwrite Google OAuth登录session为空无法跳转求助
问题描述
我正在为采用Riverpod做状态管理的Flutter应用集成Appwrite的Google OAuth登录功能。邮箱密码登录/注册功能正常,成功后能跳转至home_view.dart;但Google登录后,Appwrite后台能正常显示用户数据,却返回null的session变量,导致无法跳转至home_view.dart。不过按下热重载后,已登录的用户就能进入home_view.dart。求帮忙解决,感谢!
相关代码
auth_api.dart
final authAPIProvider = Provider((ref) { final account = ref.watch(appwriteAccountProvider); return AuthAPI(account: account); }); abstract class IAuthAPI { FutureEither<model.User> signUp({ required String email, required String password, }); FutureEither<model.Session> login({ required String email, required String password, }); FutureEither<model.Session> loginWithGoogle(); Future<model.User?> currentUserAccount(); FutureEitherVoid logout(); } class AuthAPI implements IAuthAPI { final Account _account; AuthAPI({required Account account}) : _account = account; @override Future<model.User?> currentUserAccount() async { try { return await _account.get(); } on AppwriteException { return null; } catch (e) { return null; } } @override FutureEither<model.User> signUp({ required String email, required String password, }) async { try { final account = await _account.create( userId: ID.unique(), email: email, password: password, ); return right(account); } on AppwriteException catch (e, stackTrace) { return left( Failure(e.message ?? 'Some unexpected error occurred', stackTrace), ); } catch (e, stackTrace) { return left( Failure(e.toString(), stackTrace), ); } } @override FutureEither<model.Session> login({ required String email, required String password, }) async { try { final session = await _account.createEmailSession( email: email, password: password, ); return right(session); } on AppwriteException catch (e, stackTrace) { return left( Failure(e.message ?? 'Some unexpected error occurred', stackTrace), ); } catch (e, stackTrace) { return left( Failure(e.toString(), stackTrace), ); } } @override FutureEither<model.Session> loginWithGoogle( ) async { try { final session = await _account.createOAuth2Session( provider: 'google', success: "", failure: "", ); debugPrint(session); return right(session); } on AppwriteException catch (e, stackTrace) { return left( Failure(e.message ?? 'Some unexpected error occurred', stackTrace), ); } catch (e, stackTrace) { return left( Failure(e.toString(), stackTrace), ); } } @override FutureEitherVoid logout() async { try { await _account.deleteSession( sessionId: 'current', ); return right(null); } on AppwriteException catch (e, stackTrace) { return left( Failure(e.message ?? 'Some unexpected error occurred', stackTrace), ); } catch (e, stackTrace) { return left( Failure(e.toString(), stackTrace), ); } } }
auth_controller.dart
final authControllerProvider = StateNotifierProvider<AuthController, bool>((ref) { return AuthController( authAPI: ref.watch(authAPIProvider), userAPI: ref.watch(userAPIProvider), ); }); final currentUserDetailsProvider = FutureProvider((ref) { final currentUserId = ref.watch(currentUserAccountProvider).value!.$id; debugPrint("Utente corrente: $currentUserId"); final userDetails = ref.watch(userDetailsProvider(currentUserId)); return userDetails.value; }); final userDetailsProvider = FutureProvider.family((ref, String uid) { final authController = ref.watch(authControllerProvider.notifier); return authController.getUserData(uid); }); final currentUserAccountProvider = FutureProvider((ref) { final authController = ref.watch(authControllerProvider.notifier); return authController.currentUser(); }); class AuthController extends StateNotifier<bool> { final AuthAPI _authAPI; final UserAPI _userAPI; AuthController({ required AuthAPI authAPI, required UserAPI userAPI, }) : _authAPI = authAPI, _userAPI = userAPI, super(false); // state = isLoading Future<model.User?> currentUser() => _authAPI.currentUserAccount(); void signUp({ required String email, required String password, required BuildContext context, }) async { state = true; final res = await _authAPI.signUp( email: email, password: password, ); state = false; res.fold( (l) => showSnackBar(context, l.message), (r) async { UserModel userModel = UserModel( email: email, name: getNameFromEmail(email), followers: const [], following: const [], profilePic: '', bannerPic: '', uid: r.$id, bio: '', isTwitterBlue: false, ); final res2 = await _userAPI.saveUserData(userModel); res2.fold((l) => showSnackBar(context, l.message), (r) { showSnackBar(context, 'Accounted created! Please login.'); Navigator.push(context, LoginView.route()); }); }, ); } void login({ required String email, required String password, required BuildContext context, }) async { state = true; final res = await _authAPI.login( email: email, password: password, ); state = false; res.fold( (l) => showSnackBar(context, l.message), (r) { Navigator.push(context, HomeView.route()); }, ); } void loginWithGoogle({ required BuildContext context, }) async { state = true; final res = await _authAPI.loginWithGoogle( ); state = false; res.fold( (l) => showSnackBar(context, l.message), (r) { Navigator.push(context, HomeView.route()); }, ); } Future<UserModel> getUserData(String uid) async { final document = await _userAPI.getUserData(uid); final updatedUser = UserModel.fromMap(document.data); return updatedUser; } void logout(BuildContext context) async { final res = await _authAPI.logout(); res.fold((l) => null, (r) { Navigator.pushAndRemoveUntil( context, SignUpView.route(), (route) => false, ); }); } }
解决方案
问题根源
Appwrite的OAuth2登录是跳转式授权流程,createOAuth2Session方法不会像邮箱登录那样同步返回session——用户会被带到浏览器完成授权,授权完成后才会回到App,此时原方法的返回值是空的,所以你拿到的session为null,无法触发跳转。而热重载后,currentUserAccount()能读取到本地已保存的session,所以能进入首页。
具体修复步骤
1. 修改AuthAPI的loginWithGoogle方法
OAuth2流程不会同步返回session,所以修改方法返回类型为FutureEitherVoid,只负责触发授权跳转:
@override FutureEitherVoid loginWithGoogle() async { try { // 填写你的App回调URL,比如flutterapp://login-success await _account.createOAuth2Session( provider: 'google', success: "flutterapp://login-success", failure: "flutterapp://login-failure", ); return right(null); } on AppwriteException catch (e, stackTrace) { return left( Failure(e.message ?? 'Some unexpected error occurred', stackTrace), ); } catch (e, stackTrace) { return left( Failure(e.toString(), stackTrace), ); } }
同时更新IAuthAPI中的对应方法签名。
2. 配置Deep Link并处理回调
- 在AndroidManifest.xml、Info.plist中配置Deep Link(具体配置参考Appwrite官方文档的Flutter OAuth部分),确保授权完成后能跳转回App。
- 在main.dart中监听Deep Link,触发状态刷新:
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); void main() async { WidgetsFlutterBinding.ensureInitialized(); await initAppwrite(); // 处理初始Deep Link final initialLink = await getInitialLink(); if (initialLink != null) { handleDeepLink(initialLink); } // 监听后续Deep Link linkStream.listen((link) { handleDeepLink(link); }); runApp(const ProviderScope(child: MyApp())); } void handleDeepLink(Uri link) { if (link.path == '/login-success') { // 授权成功,强制刷新用户状态 final container = ProviderScope.containerOf(navigatorKey.currentContext!); container.refresh(currentUserAccountProvider); } else if (link.path == '/login-failure') { showSnackBar(navigatorKey.currentContext!, 'Google登录失败'); } }
3. 修改AuthController的loginWithGoogle方法
不再依赖返回的session跳转,等待Deep Link触发后自动刷新状态:
void loginWithGoogle({ required BuildContext context, }) async { state = true; final res = await _authAPI.loginWithGoogle(); state = false; res.fold( (l) => showSnackBar(context, l.message), (r) => null, // 不再直接跳转,等待状态刷新 ); }
4. 优化路由守卫
让根路由监听用户状态变化,自动跳转页面:
class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( navigatorKey: navigatorKey, home: Consumer( builder: (context, ref, child) { final userAsync = ref.watch(currentUserAccountProvider); return userAsync.when( data: (user) => user != null ? const HomeView() : const LoginView(), loading: () => const CircularProgressIndicator(), error: (_, __) => const LoginView(), ); }, ), ); } }
5. 补充用户数据创建逻辑
首次通过Google登录时,Appwrite会自动创建用户,但你的应用需要手动创建对应的用户数据。可以在currentUserDetailsProvider中添加判断:
final currentUserDetailsProvider = FutureProvider((ref) async { final user = ref.watch(currentUserAccountProvider).value; if (user == null) return null; final userDetailsAsync = ref.watch(userDetailsProvider(user.$id)); final userDetails = userDetailsAsync.value; // 如果用户数据不存在,创建默认数据 if (userDetails == null) { final authController = ref.watch(authControllerProvider.notifier); final defaultUser = UserModel( email: user.email ?? '', name: user.name ?? '', followers: const [], following: const [], profilePic: user.prefs.getString('profilePic') ?? '', bannerPic: '', uid: user.$id, bio: '', isTwitterBlue: false, ); await ref.watch(userAPIProvider).saveUserData(defaultUser); return defaultUser; } return userDetails; });
内容的提问来源于stack exchange,提问作者Leon
相关产品推荐
相关产品推荐

