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

419

Solution 1

I just Found a Solution to this problem! Basically the FireFlutter Team had fixed a production level bug and inturn that exposed a flaw of the Dart SDK. As this was only a Development only bug (bug only during Hot Restart), it was not given importance.

In my Research I have found that the last version combination that supports StreamBuilder and Hot Restart is

firebase_auth: 0.20.1; firebase_core 0.7.0

firebase_auth: 1.1.0; firebase_core: 1.0.3

These are the only versions that It works properly on. Every subsequent version has the new upgrade that has exposed the bug.

The Solution is very Simple! Works for the latest version (1.2.0) of the firebase_auth and firebase_core plugins too!

Firstly Import Sharedpreferences

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);

This ensures that it works just as expected during Hot Restart in Flutter Web!

Solution 2

Manas' answer works (by caching the login state manually), but this bug is also fixed in the flutter dev channel now. This only affects development as far as I can tell because it only breaks hot reload/restart, but not page refresh. I found it very inconvenient for web-first development in my local workflow, so I switched to dev locally.

flutter channel dev
flutter upgrade

As mentioned, this appears to have been a flutterfire fix that exposed a DarkSDK bug. For the curious, here are the references for this issue/fix:

FlutterFire issue: https://github.com/FirebaseExtended/flutterfire/issues/6247

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

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

Share:
419
Manas Hejmadi
Author by

Manas Hejmadi

My name is Manas Hejmadi, i am a student of 9th grade studying at Bengaluru, India. I specialize in Python 3.x, Java, Visual C#, Full Stack. I have made several projects in the field of Deep learning and Machine Learning. I aspire to start a company of my own!

Updated on November 23, 2022

Comments

  • Manas Hejmadi
    Manas Hejmadi over 1 year

    So over the past few weeks I have been testing out FirebaseAuth both for the web and Android and the experience has been mostly bad. I have tried to add as much information as I can to give you enough context.

    My Goal

    My EndGoal is to make a package to simplify FirebaseAuth in Flutter Basically, the StreamBuilder runs on the authStateChanges stream from FirebaseAuth, It gives a user immediately after signIn or when I reload the whole page (Flutter Web) but doesnt return a user during hot reload eventhough I know the user has been authenticated. It works again when i reload the webpage. This does not exist in Android and it works as expected. Its very frustrating, and i could use some help from anyone!

    Flutter Doctor

    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 Versioning

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

    Steps To Reproduce

    • Create Flutter App
    • Create Firebase App
    • Enable Anonymous Authentication in Firebase Console
    • Link Flutter to Firebase Android App (Usual Way)
    • link Flutter to Firebase Web App (Usual Way)
    • Add Dependencies (Shown Later)
    • Add main.dart code (Shown Later)
    • run using flutter run -d chrome

    FirebaseSDKVersioning in /web/index.html

    <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 Dependencies

    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 Code (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]");
                },
              )
            ],
          ),
        );
      }
    }
    

    Basically it returns the UID on page reload or just after signIn but when a hot reload is done, it shows null eventhough the user is loggedIn actually. This is precisely the problem!

    Please Note

    I tried to test it with v1.0.0 of both plugins to verify if my flutter version was incompatible but that didnt work too. This works exactly as I expect (print UID on hot reload) for the dependency versions of:

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

    This is very very frustrating, There is absolutely no error, warning or on the console or anywhere. The SignIn works but the authenticationState does not perist on Hot reload in the Web, (Works perfectly on android) but it works perfectly for the web only on these older versions. Is this a bug? If not please help me.

    Thank you!

    Manas Hejmadi

    • Kamran Bashir
      Kamran Bashir almost 3 years
      Found any solution? Stuck in same problem.
    • Manas Hejmadi
      Manas Hejmadi almost 3 years
      Hey! @KamranBashir Yes I found a solution! Posted my answer down below!
  • Kamran Bashir
    Kamran Bashir almost 3 years
    Works perfectly, Thanks!
  • droptic
    droptic almost 3 years
    Could you share the link to the original issue you referenced, where a bug was exposed in Dart? That would be helpful for tracking when this fix can be removed.
  • Manas Hejmadi
    Manas Hejmadi almost 3 years
    @droptic After taking a look at your answer, I assume you have found it already is that okay? but if you still need it here is the link to the issue that I created: FLutterFire #6247
  • Manas Hejmadi
    Manas Hejmadi almost 3 years
    Thank you for this! I'm sure this would help many people! This bug was really frustrating!