I have two pages, Page_1 which uses didChangeAppLifecycleState which looks out for when the user closes the app. While the user is on the app, a timer increases, as soon as the user closes the app, the timer stops.
Page_2 has a logout button, which uses firebase signOut() function.
If I were to use the logout function in Page_1 and sign in with another account, the timer would start new for the second user, but If I were to logout from my Page_2 and sign in with another user, then the timer would of carried from the first user instead of starting again,
What I'm asking is how can I use WidgetsBinding.instance!.removeObserver(this); on Page_2 instead of Page_1
Page_1.dart
class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
User? user = FirebaseAuth.instance.currentUser;
UserModel loggedInUser = UserModel();
final _auth = FirebaseAuth.instance;
StreamController<int> controller = StreamController();
late StreamSubscription<int> streamSubscription;
#override
void initState() {
super.initState();
WidgetsBinding.instance!.addObserver(this);
print(user!.uid);
FirebaseFirestore.instance
.collection("users")
.doc(user!.uid)
.get()
.then((value) {
loggedInUser = UserModel.fromMap(value.data());
setState(() {});
});
app_start = DateTime.now();
}
late DateTime app_start;
late DateTime app_end;
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
print(state);
// if user clicks logout end this funciton
// Checks if app is active
final isBackground = state == AppLifecycleState.paused;
if (state == AppLifecycleState.inactive ||
state == AppLifecycleState.detached) return;
if (isBackground) {
FirebaseFirestore.instance
.collection("users")
.doc(user!.uid)
.get()
.then((value) {
loggedInUser = UserModel.fromMap(value.data());
if (loggedInUser.timeActive == null) {
loggedInUser.timeActive = 1;
} else {
}
app_end = DateTime.now();
final differenceInDays = app_end.difference(app_start).inSeconds;
int? test = loggedInUser.timeActive;
int? totalOnTime = differenceInDays + loggedInUser.timeActive!.toInt();
FirebaseFirestore.instance
.collection('users')
.doc(loggedInUser.uid)
.update({"timeActive": totalOnTime});
setState(() {});
});
} else {
app_start = DateTime.now();
}
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
WidgetsBinding.instance!.removeObserver(this);
}
}
Page_2.dart
#override
void dispose() {
// TODO: implement dispose
super.dispose();
WidgetsBinding.instance!.removeObserver(this);
}
ElevatedButton(
onPressed: () {
dispose()
_auth.signOut();
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) =>
LoginScreen()));
print("clicked");
},
child: Text('Log out'),
)
I have a LoginPage() for login and a DashBoard() page which comes after logging in succesfully.
I am using a Controller() page to provide the authetication which listens for any authentication changes in firebase, it looks like this :
class Controller extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
else if (snapshot.hasData) {
return DashBoard();
}
return LoginPage();
},
));
}
}
I also have a Log out button in another page. When I try to logout, it throws me first to DashBoard() page (which means that snapshot.hasData has some value) and after 2-3 seconds it throws me then to LoginPage() but the code never goes into this section
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
It first goes into this block conditon
else if (snapshot.hasData) {
return DashBoard();
}
And then the LoginPage() section. Also when I try to Login, it never shows me the Circularwait, but throws me to DashBoard() page.
What is the best way to achieve this ?
My fireBase auth file looks like this :
class GoogleSignInProvider extends ChangeNotifier {
final googleSignIn = GoogleSignIn();
GoogleSignInAccount _user;
GoogleSignInAccount get user => _user;
Future signInWithGoogle() async {
try {
final GoogleSignInAccount googleuser = await googleSignIn.signIn();
if (googleuser == null) return;
_user = googleuser;
final GoogleSignInAuthentication googleAuth =
await googleuser.authentication;
final GoogleAuthCredential credential = GoogleAuthProvider.credential(
idToken: googleAuth.idToken, accessToken: googleAuth.accessToken);
// Fluttertoast.showToast(msg: "Account created");
await FirebaseAuth.instance.signInWithCredential(credential);
print(_user);
notifyListeners();
} catch (e) {
print(e.toString());
}
}
Future signOutGoogle() async {
await googleSignIn.disconnect();
FirebaseAuth.instance.signOut();
}
}
interact with the bloc using a streambuilder in your ui. the ui creates events which the bloc code handles and response with state output
abstract class LoginEvent extends Equatable{
const LoginEvent();
#override
List<Object>get props=>[];
}
class LoginUser{
final String email;
final String password;
const LoginUser(this.email,this.password);
String get getEmail { return this.email;}
String get getPassword{ return this.password;}
}
class AuthenticateEvent extends LoginEvent{
final LoginUser user;
const AuthenticateEvent(this.user);
#override
List<Object> get props => [user];
LoginUser get getUser{return this.user;}
}
class LoginState extends Equatable{
final LoginView _login;
const LoginState(this._login);
#override
List<Object> get props => [_login];
LoginView get getLogin {return this._login;}
}
class BlocLogin
{
Stream<LoginState> get loginStream => _loginController.stream;
final _loginController = BehaviorSubject<LoginState>();
void dispose()
{
_loginController.close();
}
authenticate(BuildContext context,LoginEvent loginEvent) async
{
if (loginEvent is AuthenticateEvent)
{
LoginView param =
new LoginView(loginEvent.getUser.getEmail, loginEvent.getUser.getPassword);
LoginView loginValue =await Provider.of<Api>(context, listen: false)
.addLogin(context, param);
if (loginValue.returnMessage == "Failed") {
DialogCaller.showIncorrectLoginDialog(context).then((value2) {});
} else {
Provider.of<Api>(context, listen: false).dataCache.login = loginValue;
LoginState loginState=new LoginState(loginValue);
_loginController.sink.add(loginState);
}
}
}
}
I have a Flutter app that uses Firestore to store user data. I need help with retrieving the values stored in the 'friends' array. The image below shows the structure of my Firestore. As you can see, the 'friends' field is an array with two values: '123456' and '789123'.
I want to store these values in my variable called friendsList and I try to do this in getFriendsList(). To test and see if the 'friends' array values were stored in the friendsList variable, I use a print statement at the end of getFriendsList() to print the value of friendsList. But when I check my Console, Instance of 'Future<dynamic>' is printed and not the values of the 'friends' field.
How can I assign the values of the 'friends' array field from Firestore into my friendsList variable?
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:mood/components/nav_drawer.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
FirebaseAuth auth = FirebaseAuth.instance;
User currentUser;
String currentUserUID;
Future<dynamic> friendsList;
class LandingScreen extends StatefulWidget {
static const String id = 'landing_screen';
#override
_LandingScreenState createState() => _LandingScreenState();
}
class _LandingScreenState extends State<LandingScreen> {
final _auth = FirebaseAuth.instance;
#override
void initState() {
super.initState();
getUserData();
}
void getUserData() {
getCurrentUser();
getCurrentUserUID();
getFriendsList();
}
void getCurrentUser() {
final currentUser = _auth.currentUser;
}
void getCurrentUserUID() {
currentUserUID = auth.currentUser.uid;
}
void getFriendsList() {
friendsList = FirebaseFirestore.instance
.collection("Users")
.doc(currentUserUID)
.get()
.then((value) {
return value.data()["friends"];
});
print(friendsList);
}
In the then callback, just assign your list to friendsList and change your friendsList to List<dynamic> type
FirebaseFirestore.instance
.collection("Users")
.doc(currentUserUID)
.get()
.then((value) {
friendsList = value.data()["friends"];
print(friendsList);
});
According to your comment for async await syntax,
final value = await FirebaseFirestore.instance
.collection("Users")
.doc(currentUserUID)
.get();
friendsList = value.data()["friends"];
I'm getting multiple documents from firestore with this function:
getNumbers() async {
QuerySnapshot snapshot = await Firestore.instance
.collection('numbers')
.where("owner", isEqualTo: "${userid}")
.getDocuments();
List<myModel> numbersList =
snapshot.documents.map((doc) => myModel.fromDocument(doc)).toList();
}
Then I use a listview.builder to show the documents.
But I would like to listen to changes, is that possible without using a streambuilder?
I thought of the solution: listen for changes and replace the document of the list with the new changed document. Is that possible?
Thank you very much!
You can initialize your Firestore stream and call setState each time you got new element, but it's less efficient than using StreamBuilder
class NumberList extends StatefulWidget {
#override
_NumberListState createState() => _NumberListState();
}
class _NumberListState extends State<NumberList> {
//somewhere
List<MyModel> numbersList = List();
#override
void initState() {
listenNumbers();
super.initState();
}
listenNumbers() {
Stream<QuerySnapshot> streamNumbers = Firestore.instance
.collection('numbers')
.where("owner", isEqualTo: "${userid}")
.snapshots();
streamNumbers.listen((snapshot) {
snapshot.documents.forEach((doc) {
MyModel obj = MyModel.fromDocument(doc);
numbersList.add(obj);
setState(() {
});
});
});
}
#override
Widget build(BuildContext context) {
return Container(child: ListView.builder(
itemCount: numbersList.length,
itemBuilder: (context, index){
return NumberWidget(
key: UniqueKey(),
number: numbersList[index]
);
},
)
);
}
}
I am using Firebase Auth with google sign in Flutter. I am able to sign in however when I close the app(kill it), I have to sign up all over again. So is there a way to persist user authentication till specifically logged out by the user?
Here is my auth class
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
class Auth {
FirebaseAuth _firebaseAuth;
FirebaseUser _user;
Auth() {
this._firebaseAuth = FirebaseAuth.instance;
}
Future<bool> isLoggedIn() async {
this._user = await _firebaseAuth.currentUser();
if (this._user == null) {
return false;
}
return true;
}
Future<bool> authenticateWithGoogle() async {
final googleSignIn = GoogleSignIn();
final GoogleSignInAccount googleUser = await googleSignIn.signIn();
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
this._user = await _firebaseAuth.signInWithGoogle(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
if (this._user == null) {
return false;
}
return true;
// do something with signed-in user
}
}
Here is my start page where the auth check is called.
import 'package:flutter/material.dart';
import 'auth.dart';
import 'login_screen.dart';
import 'chat_screen.dart';
class Splash extends StatefulWidget {
#override
_Splash createState() => _Splash();
}
class _Splash extends State<Splash> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: CircularProgressIndicator(
value: null,
),
),
);
}
#override
void initState() {
super.initState();
_handleStartScreen();
}
Future<void> _handleStartScreen() async {
Auth _auth = Auth();
if (await _auth.isLoggedIn()) {
Navigator.of(context).pushReplacementNamed("/chat");
}
Navigator.pushReplacement(context, MaterialPageRoute(builder: (BuildContext context) => LoginScreen(auth: _auth,)));
}
}
I believe your problem is routing. In my apps I use FirebaseAuth and it works just as you say you wanted to, and I don't persist any login token. However, I don't know why your approach of using a getUser is not working.
Try to adjust your code to use onAuthStateChanged. EDIT: As of 2022, with Flutter 3, I noticed it worked better with userChanges instead.
Basically, on your MaterialApp, create a StreamBuilder listening to _auth.userChanges() and choose your page depending on the Auth status.
I'll copy and paste parts of my app so you can have an idea:
[...]
final FirebaseAuth _auth = FirebaseAuth.instance;
Future<void> main() async {
FirebaseApp.configure(
name: '...',
options:
Platform.isIOS
? const FirebaseOptions(...)
: const FirebaseOptions(...),
);
[...]
runApp(new MaterialApp(
title: '...',
home: await getLandingPage(),
theme: ThemeData(...),
));
}
Future<Widget> getLandingPage() async {
return StreamBuilder<FirebaseUser>(
stream: _auth.userChanges(),
builder: (BuildContext context, snapshot) {
if (snapshot.hasData && (!snapshot.data!.isAnonymous)) {
return HomePage();
}
return AccountLoginPage();
},
);
}
Sorry, it was my mistake. Forgot to put the push login screen in else.
Future<void> _handleStartScreen() async {
Auth _auth = Auth();
if (await _auth.isLoggedIn()) {
Navigator.of(context).pushReplacementNamed("/chat");
}
else {
Navigator.pushReplacement(context, MaterialPageRoute(builder: (BuildContext context) => LoginScreen(auth: _auth,)));
}
}
void main() {
FirebaseAuth.instance.authStateChanges().listen((User user) {
if (user == null) {
runApp(MyApp(auth : false);
} else {
runApp(MyApp(auth : false);
}
});
}
class MyApp extends StatefulWidget {
final bool auth;
MyApp({this.auth});
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
return MaterialApp(
......
......
home: widget.auth ? MainScreen() : AuthScreen();
);
You can use shared_preferences to keep alive your session even when you kill the app.
Here is the documentation https://pub.dartlang.org/packages/shared_preferences.
Also I've heard that it's possible to use sqlite to persist the session.
Add this code. It should work fine.
FirebaseAuth auth = FirebaseAuth.instance;
auth.setPersistence(Persistence.SESSION);
You can use my code, You can use userChanges() instead of authStateChanges()
Notifies about changes to any user updates.
This is a superset of both [authStateChanges] and [idTokenChanges]. It provides events on all user changes, such as when credentials are linked, unlinked and when updates to the user profile are made. The purpose of this Stream is for listening to realtime updates to the user state (signed-in, signed-out, different user & token refresh) without manually having to call [reload] and then rehydrating changes to your application.
final Stream<User?> firebaseUserChanges = firebaseAuth.userChanges();
One more simple example:
Future<bool> isUserLoggedIn() async {
final User? user = FirebaseAuth.instance.currentUser;
return user != null;
}
class InitialScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<bool>(
future: isUserLoggedIn(),
builder: (_, snapshot) {
if (snapshot.hasData) {
if (snapshot.data ?? false) {
Navigator.of(context).push(MaterialPageRoute(builder: (context) => UnauthScreen()));
} else {
Navigator.of(context).push(MaterialPageRoute(builder: (context) => HomeScreen()));
}
}
return const Center(child: CircularProgressIndicator());
},
),
);
}
}
I was able to achieve it by checking the firebase instance currentUser value. if null I routed to my Signup page. If not, then I routed to my HomePage. Not sure if there is anything wrong with this implementation (its working well so far) but seems simpler than the StreamBuilder solution posted above.
home: getLandingPage(),
routes: {
(...)
}
Widget getLandingPage() {
if (_auth.currentUser == null) {
return SignupPage();
} else {
return HomePage();
}
}