Flutter:带有 StreamBuilder 的 FirebaseAuth (v1.2.0) 无法在 Flutter Web 中进行热重载

Flutter: FirebaseAuth (v1.2.0) with StreamBuilder not working on hot reload in Flutter Web

所以在过去的几周里,我一直在为网络和 Android 测试 FirebaseAuth 而且经验大多很糟糕。 我已尝试添加尽可能多的信息来为您提供足够的上下文。

我的目标

我的最终目标是做一个包来简化 Flutter 中的 FirebaseAuth 基本上,来自 FirebaseAuth 的 authStateChanges 流上的 StreamBuilder 运行s,它在登录后或当我重新加载整个页面(Flutter Web)时立即为用户提供,但在热重新加载期间没有 return 用户,尽管我知道用户已通过身份验证。当我重新加载网页时它再次工作。 这在 Android 中不存在,并且按预期工作。 非常令人沮丧,我需要任何人的帮助!

Flutter 医生

Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel stable, 2.0.2, on Microsoft Windows [Version 10.0.21296.1010], locale en-US)
[√] Android toolchain - develop for Android devices (Android SDK version 30.0.2)
[√] Chrome - develop for the web
[!] Visual Studio - develop for Windows (Visual Studio Community 2019 16.5.5)
X Visual Studio is missing necessary components. Please re-run the Visual Studio installer for the
  "Desktop development with C++" workload, and include these components:
    MSVC v142 - VS 2019 C++ x64/x86 build tools
     - If there are multiple build tool versions available, install the latest
    C++ CMake tools for Windows
    Windows 10 SDK
[√] Android Studio (version 4.0)
[√] VS Code (version 1.56.2)
[√] Connected device (3 available)

Dart 版本控制

Dart VM version: 2.8.4 (stable) (Wed Jun 3 12:26:04 2020 +0200) on "windows_x64"

重现步骤

/web/index.html

中的 FirebaseSDKVersioning
<script src="https://www.gstatic.com/firebasejs/8.6.2/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.6.2/firebase-analytics.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.6.2/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.6.2/firebase-firestore.js"></script>

(the setup is correct as signIn works)

pubspec.yaml 依赖关系

environment:
  sdk: ">=2.7.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter

  #Firebase Dependencies
  firebase_core: ^1.2.0
  firebase_auth: ^1.2.0

Flutter 代码(main.dart)

import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

FirebaseAuth fa = FirebaseAuth.instance;
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  if (!kIsWeb) {
    await Firebase.initializeApp();
  }
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Auth Demo',
      home: AuthDemo(),
    );
  }
}

class AuthDemo extends StatelessWidget {
  const AuthDemo({Key key}) : super(key: key);

  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("AuthDemo"),
      ),
      body: Column(
        children: [
          ElevatedButton(
            onPressed: () async {
              await fa.signInAnonymously();
            },
            child: Text("Anon"),
          ),
          ElevatedButton(
            onPressed: () async {
              await fa.signOut();
            },
            child: Text("SignOut"),
          ),
          SizedBox(height: 20),
          StreamBuilder(
            stream: fa.authStateChanges(),
            builder: (context, snapshot) {
              return Text(snapshot.data?.uid ?? "[NULL]");
            },
          )
        ],
      ),
    );
  }
}

基本上它 return 是页面重新加载时或刚登录后的 UID,但是当热重新加载完成时,它显示 null,即使用户实际上已登录。这正是问题所在!

请注意

我尝试使用两个插件的 v1.0.0 对其进行测试,以验证我的 flutter 版本是否不兼容,但这也不起作用。 对于以下依赖版本,这完全符合我的预期(在热重载时打印 UID):

firebase_core: "^0.7.0"
firebase_auth: "^0.20.1"

这非常令人沮丧,绝对没有错误、警告或控制台或任何地方。 SignIn 有效,但 authenticationState 不会持续存在于 Web 中的热重载,(在 android 上完美运行) 但它仅适用于这些旧版本的网络。 这是一个错误吗? 如果不是请帮助我。

谢谢!

玛纳斯海马迪

我刚刚找到了这个问题的解决方案! 基本上,FireFlutter 团队修复了一个生产级错误,进而暴露了 Dart SDK 的一个缺陷。 由于这只是一个仅用于开发的错误(仅在热重启期间出现错误),因此没有给予重视。

在我的研究中我发现最后一个支持 StreamBuilder 和 Hot Restart 的版本组合是

firebase_auth: 0.20.1; firebase_core 0.7.0

firebase_auth: 1.1.0; firebase_core: 1.0.3

这些是唯一可以正常运行的版本。每个后续版本都有新的升级暴露了这个bug。

解决方法很简单!也适用于 firebase_auth 和 firebase_core 插件的最新版本 (1.2.0)!

首先导入共享首选项

import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/foundation.dart' as Foundation;
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

class HotRestartBypassMechanism {
  ///The Standard SharedPreference instance
  static final Future<SharedPreferences> prefs =
      SharedPreferences.getInstance();

  ///Saves the Login State (true) for LoggedIn and (false) for LoggedOut
  static saveLoginState(bool isLoggedIn) async {
    //Ignore Operation if Not in DebugMode on Web
    if (!Foundation.kDebugMode && !Foundation.kIsWeb) return;

    SharedPreferences p = await prefs;
    p.setBool('is_logged_in', isLoggedIn);
  }

  ///Gets the login status from SharedPreferences
  static Future<bool> getLoginStatus() async {
    SharedPreferences p = await prefs;
    return p.getBool('is_logged_in') ?? false;
  }
}

class HotRestartByPassBuilder extends StatelessWidget {
  final Widget destinationFragment;
  final Widget loginFragment;
  const HotRestartByPassBuilder({
    Key key,
    this.destinationFragment,
    this.loginFragment,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<bool>(
      future: HotRestartBypassMechanism.getLoginStatus(),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.done) {
          if (snapshot.data) {
            return destinationFragment;
          } else {
            return loginFragment;
          }
        } else {
          return loginFragment;
        }
      },
    );
  }
}


//Usage In StreamBuilder
StreamBuilder(
  stream: FirebaseAuth.instance.authStateChanges(),
  builder: (context, snapshot) {
      if (snapshot.hasData) {
        return HomePage();
      } else {
        //Only in Debug Mode & in Web
        if (Foundation.kDebugMode && Foundation.kIsWeb) {
          //---------------------HOT RESTART BYPASS--------------------------
          return HotRestartByPassBuilder(
            destinationFragment: HomePage(),
            loginFragment: LoginPage(),
          );
          //-----------------------------------------------------------------
        } else {
          return LoginPage();
        }
      }
    },
);

//When User Logs in Use
await HotRestartBypassMechanism.saveLoginState(true);

//When User Logs out use
await HotRestartBypassMechanism.saveLoginState(false);

这确保它在 Flutter Web 热重启期间按预期工作!

Manas 的回答有效(通过手动缓存登录状态),但这个错误现在也在 flutter dev 频道中修复了。据我所知,这只会影响开发,因为它只会中断热 reload/restart,但不会刷新页面。我发现在我的本地工作流程中进行 web-first 开发非常不方便,所以我切换到本地开发。

flutter channel dev
flutter upgrade

如前所述,这似乎是一个暴露了 DarkSDK 错误的 flutterfire 修复程序。出于好奇,这里是 issue/fix:

的参考资料

FlutterFire 问题: https://github.com/FirebaseExtended/flutterfire/issues/6247

DartSDK 问题: https://github.com/dart-lang/sdk/issues/45874

DartSDK 修复: https://github.com/dart-lang/sdk/commit/460887d8149748d3feaad857f1b13721faadeffa