Jazil Zaim
Jazil Zaim

Reputation: 357

Struggling with authStateChanges in Flutter

Whenever the user closes the app, they have to re-log back in. I looked and tried to implement authStateChanges. But yet my app is still forcing users to log back in after they close the app. In the App class, you can see that I tried to do exactly the authStateChange but nothing seems to be happening, unfortunately.

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(App());
}

// Firebase Auth Instance
FirebaseAuth auth = FirebaseAuth.instance;

class MyApp extends StatelessWidget {
  final Future<FirebaseApp> _initialization = Firebase.initializeApp();

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Tanbo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: LoginPage(),
    );
  }
}

// This is the main root screen of the Tanbo app
class App extends StatelessWidget {
  final Future<FirebaseApp> _initialization = Firebase.initializeApp();

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      // Initialize FlutterFire
      future: _initialization,

      builder: (context, snapshot) {
        final user =
            FirebaseAuth.instance.authStateChanges().listen((User user) {
          if (user == null) {
            print('User signed out');
          } else {
            print('User signed in');
          }
        });

        // Check for errors
        if (snapshot.hasError) {
          return ErrorHandler();
        }

        // Once complete, show your application
        if (snapshot.connectionState == ConnectionState.done) {
          // If the user isn't logged in, we will be sent to sign up page.
          if (user != null) {
            return MyApp();
          } else {
            // If the user is logged in, TabHandler will be shown.
            return TabHandler();
          }
        }

        // Otherwise, show something whilst waiting for initialization to complete
        return LoadingHandler();
      },
    );
  }
}

Upvotes: 20

Views: 21661

Answers (3)

Simeon
Simeon

Reputation: 781

The problem is that you have explicitly made your login page as your homepage. When the app first opens, it will automatically read through the main.dart file and therefore use login page as the assigned Homepage.

This is how you fix it. And also make an authentication system where you can get the logged in user's ID at any point in the app.

What you need:

  1. Provider dependency of whichever version. Preferably the latest.
  2. A smile - Stop frowning. You're about to fix your problem.
  3. Firebase Auth dependency. At whichever version. I'm going to give you a fix for the newest version (and decidedly for older versions).

Step 0: Add the needed dependencies and run flutter pub get. This one is a no brainer.

Step 1: Make an auth_services class: Code is below

For older versions of firebase auth:

import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';

class AuthService {
  final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
  final GoogleSignIn _googleSignIn = GoogleSignIn();

  Stream<String> get onAuthStateChanged =>
      _firebaseAuth.onAuthStateChanged.map(
            (FirebaseUser user) => user?.uid,
      );

  // GET UID
  Future<String> getCurrentUID() async {
    return (await _firebaseAuth.currentUser()).uid;
  }
}

For newer versions of firebase auth dependency:

class AuthService {
  final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
  final GoogleSignIn _googleSignIn = GoogleSignIn();

  Stream<User> get onAuthStateChanged => _firebaseAuth.authStateChanges();

  // GET UID
  Future<String> getCurrentUID() async {
    return _firebaseAuth.currentUser.uid;
  }
}

Explanation : I have made this class to handle all auth functions. I am making a function called onAuthStateChanged that returns a stream of type User (ids for older versions of firebase auth) and I am going to listen to this stream to find out whether there is a user logged in or not.

Step 2: Create the auth provider that is going to wrap the entire app and make it possible to get our user's ID anywhere inside the app.

Create a file called auth_provider.dart. The code is below:

import 'package:flutter/material.dart';
import 'auth_service.dart';

class Provider extends InheritedWidget {
  final AuthService auth;
  Provider({
    Key key,
    Widget child,
    this.auth,
  }) : super(key: key, child: child);

  @override
  bool updateShouldNotify(InheritedWidget oldWiddget) {
    return true;
  }

  static Provider of(BuildContext context) =>
      (context.dependOnInheritedWidgetOfExactType<Provider>());
}

The next step is to wrap the entire app in this provider widget and set the home controller as the homepage.

Step 3: On the main.dart file, or anywhere really, make a class called HomeController which will handle the logged in state, and set the home Controller as the assigned homepage.

NB: I have set a container of color black to be shown as the app is loading to determine if a user is logged in or not. It is a fairly fast process but if you want to, you can set it to be a container with the theme color of your app. You can even set it to be a splash screen. (Please note: This container is shown for about 1 second at most, from my experience).

Code is below: Import all the necessary things

void main() {
 //WidgetsFlutterBinding.ensureInitialized();
 // await Firebase.initializeApp();
//only add these if you're on the latest firebase
  runApp(MyApp());
}

 class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Provider(
      auth: AuthService(),
      child: MaterialApp(
            title: 'Dreamora',
            theme: ThemeData(
              // fontFamily: "Montserrat",
              brightness: Brightness.light,
              inputDecorationTheme: InputDecorationTheme(
                contentPadding:
                    EdgeInsets.only(top: 10, bottom: 10, left: 10, right: 10),
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.circular(5.0),
                ),
              ),
              primarySwatch: Colors.purple,
              visualDensity: VisualDensity.adaptivePlatformDensity,
            ),
            home: HomeController(),
          );
        },
      ),
    ),}
//(I think i have messed up the brackets here, but, you get the 
//gist)

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

  @override
  Widget build(BuildContext context) {
    final AuthService auth = Provider.of(context).auth;

    return StreamBuilder(
      stream: auth.onAuthStateChanged,
      builder: (context, AsyncSnapshot<String> snapshot) {
        if (snapshot.connectionState == ConnectionState.active) {
          final bool signedIn = snapshot.hasData;
          return signedIn ? DashBoard() : FirstView();
        }
        return Container(
          color: Colors.black,
        );
      },
    );
  }
}

Explanation: The home controller is just a stream builder that listens to the stream of auth changes that are made at any point. If it returns true, it means that the user is logged in. If not, then it is logged out. Fairly simple. There is like a 1 second response time when determining a user's logged in state. So, I chose to return a black container in the meanwhile. The user will see the screen turn black for about a second after opening the app and then boom!! Homepage!

IMPORTANT: You should wrap your entire app with provider. This is important. Do not forget it.

HOW TO GET THE USER ID AT ANY POINT IN THE APP

Provider.of(context).auth.getCurrentUID()

There you go. Enjoy!

[EDIT]As L. Gangemi put it, The new version of Firebase Auth returns a stream of users. So edit the home controller code to be

builder: (context, AsyncSnapshot<User> snapshot) {

Upvotes: 42

Roman Chervotkin
Roman Chervotkin

Reputation: 56

I believe it does not work because of this issue - https://github.com/FirebaseExtended/flutterfire/issues/3356

Solved by downgrading firebase-app.js and firebase-auth.js versions to 7.20.0

and replacing authStateChanges() method with userChanges()

 FirebaseAuth.instance
     .userChanges()
     .listen((User user) {
         if (user == null) {
             print('User is currently signed out!');
         } else {
             print('User is signed in!');
         }
});

Upvotes: 1

Richie
Richie

Reputation: 586

Use

FirebaseAuth.instance
  .authStateChanges()
  .listen((User user) {
    if (user == null) {
      print('User is currently signed out!');
    } else {
      print('User is signed in!');
    }
  });

Upvotes: -2

Related Questions