Reputation: 357
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
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:
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
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
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