Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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


 }
}
like image 213
Jazil Zaim Avatar asked Oct 25 '20 05:10

Jazil Zaim


People also ask

What is flutteragency?

FlutterAgency.com is our portal Platform dedicated to Flutter Technology and Flutter Developers. The portal is full of cool resources from Flutter like Flutter Widget Guide , Flutter Projects , Code libs and etc.

What are the best resources for learning flutter?

The portal is full of cool resources from Flutter like Flutter Widget Guide , Flutter Projects , Code libs and etc. FlutterAgency.com is one of the most popular online portal dedicated to Flutter Technology and daily thousands of unique visitors come to this portal to enhance their knowledge on Flutter.

How to listen to user’s authentication state change in Firebase?

Firebase returns a Stream of FirebaseUser with its onAuthStateChanged function. There are many ways to listen to the user’s authentication state change. Take a look at below Code Snippet. We return a StreamBuilder to my App’s home page, and the StreamBuilder returns specific pages based on the auth status of the user. ? MainPage()


2 Answers

The problem is you have explicitly made your login page as your homepage. When the app opens, it will automatically read through the main.dart file and see 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 for decidedly older versions.

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

Step1: 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 id anywhere in 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 container of the theme color of your app. You can even set it to be a splash screen. (Please note. This containeris 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 steam builder that listens to the steeam of auth changes we made. If there is any stuff, the user is logged in. If not, he is logged out. Fairly simple. There is like a 1 second lag though in between determining a user's logged in state. Soo, i am returning a black container in that time. 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) {
like image 127
Simeon Avatar answered Oct 06 '22 02:10

Simeon


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!');
         }
});
like image 37
Roman Chervotkin Avatar answered Oct 06 '22 02:10

Roman Chervotkin