Swift转Flutter更新后Firebase Auth状态丢失问题求助
核心问题1:Swift→Flutter跨版本升级后Firebase Auth会话丢失
这是Firebase Auth跨平台实现的已知兼容性问题:原生Swift版Firebase Auth将用户会话凭证存储在iOS的Keychain/本地存储中,存储格式与Flutter firebase_auth 插件的存储格式不兼容。当用户从旧版Swift App升级到Flutter版时,Flutter无法读取原生存储的会话凭证,导致App重启后无法自动恢复登录状态,只能通过卸载重装让Flutter重新生成并存储自己格式的会话。
解决方法:迁移原生Auth会话到Flutter
通过原生MethodChannel桥接,读取旧版Swift App的Firebase Auth会话凭证,在Flutter中手动完成登录,将会话迁移到Flutter的存储体系中:
Swift原生端代码(添加到Flutter项目的iOS Runner中):
import FirebaseAuth // 在FlutterMethodChannel的处理方法中添加: case "getFirebaseRefreshToken": if let user = Auth.auth().currentUser { user.getIDTokenForcingRefresh(true) { token, error in if let token = token { result.success(token) } else { result.success(nil) } } } else { result.success(nil) }Flutter端代码(在App启动时调用):
final MethodChannel _channel = MethodChannel('your_channel_name'); Future<void> migrateLegacyAuthSession() async { try { final String? idToken = await _channel.invokeMethod('getFirebaseRefreshToken'); if (idToken != null && FirebaseAuth.instance.currentUser == null) { // 使用ID Token登录,迁移会话 await FirebaseAuth.instance.signInWithCustomToken(idToken); // 登录成功后,Flutter会自动存储兼容的会话凭证 } } catch (e) { // 迁移失败不影响流程,用户后续手动登录即可 } }在
main()方法中,初始化Firebase后立即调用migrateLegacyAuthSession()。
核心问题2:isLoggedIn()方法的异步逻辑缺陷
当前的isLoggedIn()方法同步读取currentUser,但authStateChanges()是异步流,可能导致初始状态判断错误(比如Flutter还未完成Auth初始化,currentUser为null,但实际会话存在)。
修复后的isLoggedIn()方法
改成异步方法,确保获取到准确的初始登录状态:
static Future<bool> isLoggedIn() async { if (_isLoggedIn == null) { // 先同步读取当前用户 _isLoggedIn = _firebaseAuth.currentUser?.uid != null; // 监听后续状态变化 _firebaseAuth.authStateChanges().listen((User? user) { _isLoggedIn = user?.uid != null; }); // 如果同步读取为未登录,等待Auth流的初始值确认 if (_isLoggedIn == false) { final User? initialUser = await _firebaseAuth.authStateChanges().first; _isLoggedIn = initialUser?.uid != null; } } return _isLoggedIn ?? false; }
核心问题3:signOutUserIfUserDeinstalledAndReinstalledApp()误判升级用户
当前逻辑通过LocalStorage的isFirstStart标记判断首次启动,但旧版Swift App没有这个标记,升级用户打开Flutter版时会被误判为“首次安装”,从而被强制登出。
修复后的判断逻辑
利用iOS Keychain的特性(卸载才会清除,升级保留),区分“首次安装”和“版本升级”:
Swift原生端代码(读取Keychain标记):
import Security func hasPreviousInstallMarker() -> Bool { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: "app_previous_install_marker", kSecReturnData as String: kCFBooleanTrue!, kSecMatchLimit as String: kSecMatchLimitOne ] var data: AnyObject? let status = SecItemCopyMatching(query as CFDictionary, &data) return status == errSecSuccess } // 在MethodChannel中添加方法: case "hasPreviousInstall": result.success(hasPreviousInstallMarker())Flutter端修改后的方法:
static Future<void> signOutUserIfUserDeinstalledAndReinstalledApp() async { final MethodChannel _channel = MethodChannel('your_channel_name'); bool? hasLaunchedBefore = await LocalStorageService.getData(key: LocalStorageKeys.hasLaunchedBefore); if (hasLaunchedBefore == null) { // 检查Keychain是否存在旧安装标记 bool wasPreviouslyInstalled = await _channel.invokeMethod('hasPreviousInstall') ?? false; if (!wasPreviouslyInstalled) { // 仅首次安装时执行登出清数据 await LocalStorageService.clear(); await signOut(deleteMessaingToken: false); } // 设置标记,后续启动不再触发 await LocalStorageService.setBool(key: LocalStorageKeys.hasLaunchedBefore, value: true); } }同时,在旧版Swift App中添加逻辑:用户首次登录成功后,在Keychain中存入
app_previous_install_marker标记,确保升级用户能被识别。
内容的提问来源于stack exchange,提问作者Chris

