InitState() not finishing before Build method is called - flutter

I'm having a problem in my home_screen.dart file. I
have a method called pullUserData() that is called in initState() but before pullUserData() is completely finished, the build method in home_screen.dart begins. This results in null values (auth and friendsList) being sent to NavDrawer() and FriendsFeed() in the build method of home_screen.dart.
How can I prevent NavDrawer() and FriendsFeed() from being called in the build method before initState() is completely finished? Should I use FutureBuilder?
User_data.dart handles gets the values for auth and friendsList.
home_screen.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:mood/components/friends_feed.dart';
import 'package:mood/components/nav_drawer.dart';
import 'package:mood/services/user_data.dart';
class LandingScreen extends StatefulWidget {
static const String id = 'landing_screen';
#override
_LandingScreenState createState() => _LandingScreenState();
}
class _LandingScreenState extends State<LandingScreen> {
FirebaseAuth auth;
List<dynamic> friendsList;
#override
void initState() {
super.initState();
pullUserData();
}
Future<void> pullUserData() async {
UserData userData = UserData();
await userData.getUserData();
auth = userData.auth;
friendsList = userData.friendsList;
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text('Mood'),
centerTitle: true,
),
drawer: NavDrawer(auth),
body: FriendsFeed(friendsList),
);
}
}
user_data.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class UserData {
final FirebaseAuth _auth = FirebaseAuth.instance;
User _currentUser;
String _currentUserUID;
List<dynamic> _friendsList;
FirebaseAuth get auth => _auth;
User get currentUser => _currentUser;
String get currentUserUID => _currentUserUID;
List<dynamic> get friendsList => _friendsList;
Future<void> getUserData() async {
getCurrentUser();
getCurrentUserUID();
await getFriendsList();
}
void getCurrentUser() {
_currentUser = _auth.currentUser;
}
void getCurrentUserUID() {
_currentUserUID = _auth.currentUser.uid;
}
Future<void> getFriendsList() async {
await FirebaseFirestore.instance
.collection("Users")
.doc(_currentUserUID)
.get()
.then((value) {
_friendsList = value.data()["friends"];
});
}
}

There are couple of problems in your code but it will work.
Firstly, if you want to set value of your friendslist during build, you have to use setState like this:
setState(() {
friendsList = userData.friendsList;
});
And if you want to wait until pullUserData() finish, you are looking for something called splash screen, but in your problem, you are waiting only for body to be build so I will recommend to use progress indicator in your scaffold like this:
return Scaffold(
appBar: Bars.menuAppBar('Devices'),
drawer: DrawerMenu(),
backgroundColor: ColorThemes.backgroundColor,
body: _loading
? Center(
child: CircularProgressIndicator(
valueColor: new AlwaysStoppedAnimation<Color>(
Colors.blue), //choose your own color
))
: FriendsFeed(friendsList)
);
You can see that I used _loading variable. You will have to define it before your initState() like
bool _loading = true;
Then after you set your friendsList inside of your pullUserData() function, change _loading to false inside of setState just like this:
setState(() {
friendsList = userData.friendsList;
_loading = false;
});

Related

How to use a variable within a method elsewhere in Flutter?

I know this is a stupid question, but I'm asking as a newbie to flutter.
I created a getData() method to call Firebase's User data and display it on the app. And to call it, result.data() is saved as a variable name of resultData.
But as you know I can't use Text('user name: $resultData'). How do I solve this? It's a difficult problem for me, since I don't have any programming basics. thank you.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:shipda/screens/login/login_screen.dart';
import 'package:get/get.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final _authentication = FirebaseAuth.instance;
User? loggedUser;
final firestore = FirebaseFirestore.instance;
void getData() async {
var result = await firestore.collection('user').doc('vUj4U27JoAU6zgFDk6sSZiwadQ13').get();
final resultData = result.data();
}
#override
void initState() {
super.initState();
getCurrentUser();
getData();
}
void getCurrentUser(){
try {
final user = _authentication.currentUser;
if (user != null) {
loggedUser = user;
print(loggedUser!.email);
}
} catch (e){
print(e);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
children: [
Text('Home Screen'),
IconButton(
onPressed: () {
FirebaseAuth.instance.signOut();
Get.to(()=>LoginScreen());
},
icon: Icon(Icons.exit_to_app),
),
IconButton(
onPressed: () {
Get.to(() => LoginScreen());
},
icon: Icon(Icons.login),
),
Text('UserInfo'),
Text('user name: ')
],
),
),
);
}
}
What you are referring to is called state.
It is a complex topic and you will have to start studying it to correctly develop any web based app.
Anyway, as for your situation, you should have resultData be one of the attributes of the _HomeScreenState class.
Then change resultData in a setState method, like this:
setState(() {
resultData = result.data();
});
Then, in the Text widget, you can actually do something like:
Text("My data: " + resultData.ToString())
Instead of ToString of course, use anything you need to actually access the data.
By writing
void getData() async {
var result = await firestore.collection('user').doc('vUj4U27JoAU6zgFDk6sSZiwadQ13').get();
final resultData = result.data();
}
you make resultData only local to the function getData(). You should declare it outside. Also you need to put it in a setState to make it rebuild the screen after loading. I don't know what type it is, but if it's a String for example you could write
String? resultData;
void getData() async {
var result = await firestore.collection('user').doc('vUj4U27JoAU6zgFDk6sSZiwadQ13').get();
setState(() {
resultData = result.data();
});
}
Then you can use Text('user name: $resultData') for example

Get value from SharedPreferences without future builder

I need to get one stored value from shared preferences and put it into text widget. How can I do this without a future builder?
_currPage() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
int page = prefs.getInt('currPage') ?? 0;
return page;
}
class _AllTasksPageState extends State<AllTasksPage> {
#override
Widget build(BuildContext context) {
...
Text(_currPage()); //not working
...
}
}
int page = 0;
#override
void initState() {
super.initState();
readData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text('$page'),
),
);
}
void readData() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
if (prefs.getInt('currPage') == null)
setState(() => page = 0);
else
setState(() => page = prefs.getInt('currPage')!);
}
create a helper class just for shared preferences
import 'package:shared_preferences/shared_preferences.dart';
class SPHelper {
SPHelper._();
static SPHelper sp = SPHelper._();
SharedPreferences? prefs;
Future<void> initSharedPreferences() async {
prefs = await SharedPreferences.getInstance();
}
Future<void> save(String name, String value) async {
await prefs!.setString(name, value);
}
String? get(String key) {
return prefs!.getString(key);
}
Future<bool> delete(String key) async {
return await prefs!.remove(key);
}
}
in your main function add
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await SPHelper.sp.initSharedPreferences();
...
runApp(MyApp());
...
}
then to get your data just write
SPHelper.sp.get("YOUR_KEY")
and to store your data just write
SPHelper.sp.save("YOUR_KEY","YOUR_VALUE")
This is the best way to use shared preference.
I hope that's will help you in your problem.
The simplest method is using a SharedPreferences provider:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(
MultiProvider(
providers: [
Provider.value(value: await SharedPreferences.getInstance()),
],
child: MaterialApp(
home: AllTasksPage(),
),
),
);
}
class AllTasksPage extends StatelessWidget {
const AllTasksPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final page = context.read<SharedPreferences>().getInt('currPage') ?? 0;
return Scaffold(body: Text('$page'));
}
}
If you don't want to use a future builder, the other solution is if you have a variable that tells you that are you still waiting/loading data and if yes, show a waiting screen:
class _AllTasksPageState extends State<AllTasksPage> {
bool _loading = true;
String? textValue; // String textValue = "";
#override
initState() {
super.initState();
setTextValue();
}
setTextValue() {
SharedPreferences prefs = await SharedPreferences.getInstance();
int page = prefs.getInt('currPage') ?? 0;
setState(() {
textValue = "$page";
_loading = false;
});
}
// then in the build method
#override
Widget build(BuildContext context) {
return _loading ? CircularProgressIndicator() : actualScreen();
}
}

how to pass database as a constructor argument to the ......class and use that as an instance variable rather than with Provider.of<Database>

I want to write the data to the fire store database.
I wrote the code in this way in subscriptions class:
import 'package:flutter/material.dart';
import 'package:flutterapptest/services/database.dart';
import 'package:provider/provider.dart';
class Subscriptions extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Subscribed'),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () => _addTrack(context),
),
);
}
Future<void> _addTrack(BuildContext context) async {
final database = Provider.of<Database>(context, listen: false);
await database.addTrack({
'name': 'Track',
'time': 20,
});
}
}
in the database class ......
import 'dart:async';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:meta/meta.dart';
abstract class Database {
Future<void> addTrack(Map<String, dynamic> trackData);
}
class FirestoreDatabase implements Database {
FirestoreDatabase({#required this.uid}) : assert(uid != null);
final String uid;
Future<void> addTrack(Map<String, dynamic> trackData) async{
final path = '/users/$uid/track/track_abc';
final documentReference = Firestore.instance.document(path);
await documentReference.setData(trackData);
}
}
for this i am getting the error:
could not find the correct Provider<Database> above the subscriptions widget
For this a friend suggested me to do:
If you push the Subscriptions widget inside a route, it won't have access to Provider.of<Database>.
The quickest solution is to pass Database as a constructor argument to the Subscriptions class, and use that as an instance variable rather than with Provider.of<Database>
Can anyone please help me what should i do now? I am new to flutter.
This is how I would do it. I know this includes much more then just the addTrack function but I guess you also need a global state somewhen and a changeNotifier, otherwise it makes no sense to use the provider.
First create the service:
import 'package:cloud_firestore/cloud_firestore.dart';
final CollectionReference firestoreUsers =
Firestore.instance.collection('users');
class UserService {
static Future<Stream<Map<String, dynamic>>> streamTrack(String userId) async {
Stream<DocumentSnapshot> query =
await firestoreUsers.document('pathToListen...').snapshots();
return query.map((doc) => Map.from(doc.data));
}
static addTrack(String userId, Map<String, dynamic> trackData) async {
await firestoreUsers
.document(userId)
.collection('track')
.document('track_abc')
.setData(trackData);
}
}
Then the provider which holds the global state and uses the changeNotifier. These are just placeholders. Put there whatever you want to listen in the database.
import 'dart:async';
import 'package:flutter/material.dart';
import '../models_services/user_services.dart';
class UserProvider with ChangeNotifier {
// your global state, if you want to listen for data from database
StreamSubscription<Map<String, dynamic>> trackStream;
Map<String, dynamic> _streamedTrack;
Map<String, dynamic> get streamedTrack => _streamedTrack;
Future streamCurrentTrack(uid) async {
if(trackStream != null) {
trackStream.cancel();
}
Stream<Map<String, dynamic>> res = await UserService.streamTrack(uid);
trackStream = res.listen((track) {
_streamedTrack = track;
notifyListeners();
});
}
Future<void> addTrack(Map<String, dynamic> trackData) async {
// not sure what this is doing, i guess uid is null here...
FirestoreDatabase({#required this.uid}) : assert(uid != null);
final String uid;
await UserService.addTrack(uid, trackData);
}
}
Use the provider with changeNotifier in your code:
import 'package:flutter/material.dart';
import 'package:flutterapptest/provider/user_provider.dart';
import 'package:provider/provider.dart';
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => UserProvider()),
...
],
child: MaterialApp(
home: Scaffold(body: Subscriptions()
)));
}
}
class Subscriptions extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Subscribed'),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () => _addTrack(context),
),
);
}
Future<void> _addTrack(BuildContext context) async {
final database = Provider.of<UserProvider>(context, listen: false);
await database.addTrack({
'name': 'Track',
'time': 20,
});
}
}

How to get data from Firestore when homepage loads?

I am trying to make a todo list app with Flutter. When the homepage loads, the data does not appear on program
I tried QuerySnapshot, '.then', sleep and more but unfortunately none of them worked
I tried to debug and find where is the problem. It looks like while the program loads, the data from Firestore is not ready yet that is why it does not appear on the homepage.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'dart:io';
class home extends StatefulWidget {
static const String id = 'home';
#override
_homeState createState() => _homeState();
}
class _homeState extends State<home> {
String userUid;
FirebaseUser loggedInUser;
final _fireStore = Firestore.instance;
final _auth = FirebaseAuth.instance;
List<Container> messageWidgets = [];
void getCurrentUser() async {
final user = await _auth.currentUser();
if (user != null) {
loggedInUser = user;
userUid = loggedInUser.uid;
}
}
getTasks() {
print('///////////////Get tasks///////////////');
return _fireStore
.document('Userss')
.collection('$userUid/Tasks')
.getDocuments();
}
bool boool = false;
var tasks;
#override
void initState() {
getCurrentUser();
getTasks().then((QuerySnapshot docs) {
if (docs.documents.isNotEmpty) {
boool = true;
tasks = docs.documents[0].data;
}
});
super.initState();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Center(
child: Column(
children: <Widget>[
boool
? Column(
children: <Widget>[
Text(tasks['Task']),
Text(tasks['Category']),
],
)
: Container(
child: Text('No DATA'),
),
],
)));
}
I expect the data appears when homepage loads but it does not appear
My quick guess is that you need to use setState(), since the data is loaded from Firestore asynchronously.
So something like:
getTasks() {
print('///////////////Get tasks///////////////');
_fireStore
.document('Userss')
.collection('$userUid/Tasks')
.getDocuments().then((QuerySnapshot docs) {
if (docs.documents.isNotEmpty) {
boool = true;
setState(() {
tasks = docs.documents[0].data;
});
}
});
}

How to use shared preferences to keep user logged in flutter?

I want to keep the user logged in after the user successfully logsin in flutter.
I am using a REST API to retrieve the user name and password of the user. But I want to save those details so that the user can stay logged in. My current situation is i can successfully log the user in but when i restart the app i have to login again so i need to save the details of the user in a shared preference so that the user can stay logged for the entire session until logout.But i am unable to do that so please help me with it.
Thanks in advance
This is the code i have for my login page.
I have removed the UI contents which should be inside the listview as those are not that relevant.
Login.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:restaurant_app/globalVar.dart';
import 'package:restaurant_app/homescreen.dart';
import 'package:restaurant_app/models/auth.dart';
import 'package:restaurant_app/signup.dart';
import 'package:http/http.dart' as http;
import 'package:restaurant_app/utils/authutils.dart';
import 'package:shared_preferences/shared_preferences.dart';
class SignIn extends StatefulWidget {
SignIn({ Key key, this.post }): super(key: key);
#override
_SignInState createState() => _SignInState();
}
class _SignInState extends State<SignIn> with SingleTickerProviderStateMixin
{
TabController controller;
TextEditingController _email = new TextEditingController();
TextEditingController _password = new TextEditingController();
bool loading;
final GlobalKey < ScaffoldState > _scaffoldKey = new GlobalKey<ScaffoldState>
();
#override
void initState() {
// TODO: implement initState
super.initState();
_fetchSessionAndNavigate();
controller = new TabController(length: 2, vsync: this);
loading = false;
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
controller.dispose();
setState(() {
loading = false;
});
_email.dispose();
_password.dispose();
}
final GlobalKey < FormState > _formKey = GlobalKey<FormState>();
bool _autoValidate = false;
_login(username, password) async {
setState(() {
loading = true;
});
var body = json.encode({
"username": username,
"password": password,
});
Map < String, String > headers = {
'Content-type': 'application/json',
'Accept': 'application/json',
};
await http
.post("${GlobalVar.Ip}/wp-json/jwt-auth/v1/token",
body: body, headers: headers)
.then((response) {
var body = json.decode(response.body);
//var response1;
if (response.statusCode == 200) {
// TODO: you need to store body['token'] to use in some authentication
loading = false;
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (BuildContext ctx) => HomePage()));
} else {
// TODO: alert message
final snackBar = SnackBar(
content: Text(body['message'].toString().trim()),
);
_scaffoldKey.currentState.showSnackBar(snackBar);
}
setState(() {
loading = false;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
resizeToAvoidBottomPadding: false,
body: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('images/art.png'),
fit: BoxFit.fill,
colorFilter: ColorFilter.mode(
Colors.white12.withOpacity(0.2), BlendMode.dstATop),
),
),
child: ListView();
}
You can navigate to the Login page if the user details are saved in the storage else to the Home page with the below code
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences prefs = await SharedPreferences.getInstance();
var email = prefs.getString('email');
print(email);
runApp(MaterialApp(home: email == null ? Login() : Home()));
}
Save the required user details after the successful login
class Login extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
onPressed: () async {
//after the login REST api call && response code ==200
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('email', 'useremail#gmail.com');
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (BuildContext ctx) => Home()));
},
child: Text('Login'),
),
),
);
}
}
clear the details on logout
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: Center(
child: RaisedButton(
onPressed: () async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.remove('email');
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (BuildContext ctx) => Login()));
},
child: Text('Logout'),
),
),
);
}
}
Hope it helps!
Make sure WidgetFlutterBinding.ensureInitialized() is the first line of main()
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter/material.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences prefs = await SharedPreferences.getInstance();
bool login = prefs.getBool("login");
print("login:" + login.toString());
runApp(MaterialApp(home: login == null ? LoginPage(title: 'My App') : HomePage()));
}
class LoginPage extends StatelessWidget { ...
The above answers using SharedPreferences works (make sure you have WidgetsFlutterBinding.ensureInitiazed(); as your first line of main), but it will give you a null on re-start, ie, if you remove the app from recent and re-open it again, it will not re-direct you to the Home or Profile Page. I solved this issue by giving write external storage permission to your app because the shared preferences need to write the data somewhere in your device or emulator.
Just add the write and read external storage permissions in your Android Manifest file and you can use permission_handler plugin for flutter from pub.dev to get the required permissions from user at runtime when the app is opened for the first time and then Shared Preferences won't give you null.
Use user sessions instead. Check out Consession. The package adds user session support in Flutter and is easy to use.
// Store value to session
await Consession().set("token", myJWTToken);
// Retrieve item from session
dynamic token = await Consession().get("token");
Concession is now deprecated in favour of flutter_session. We can now use flutter_session to manage user sessions seamlessly.
//Write values to the session:
await FlutterSession().set("token", myJWTToken);
//Read values from the session:
dynamic token = await FlutterSession().get("token");