I am using shared_preferences to store a bool value locally but I think I am doing something wrong.
So first of all, here is my initState:
#override
initState(){
super.initState();
checkIfUserHasData();
getBoolValuesSF();
}
on checkIfUserHasData, Im calling another function at the end (addBoolToSF)
Future<void> checkIfUserHasData ()async {
var collection = FirebaseFirestore.instance.
collection('users').doc(userID).collection('personalInfo');
var querySnapshots = await collection.get();
for (var snapshot in querySnapshots.docs) {
documentID = snapshot.id;
}
await FirebaseFirestore.instance
.collection('users')
.doc(userID)
.collection('personalInfo').doc(documentID)
.get().then((value) {
if (!mounted) return;
setState(() {
gender = value.get('gender');
profileImageUrl = value.get('url');
print(profileImageUrl);
print(gender);
});
});
if (gender != null){
if (!mounted) return;
setState((){
isUserNew = false;
});
if(gender == "Male"){
setState(() => genderIsMale = true);
addBoolToSF();
}else{
setState(() => genderIsMale = false);
addBoolToSF();
}
}else {
return;
}
}
Then addBoolToSF:
addBoolToSF() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool('genderType', genderIsMale);
}
Lastely getBoolValuesSF:
getBoolValuesSF() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
bool _genderType = ((prefs.getBool('genderType') ?? true)) ;
genderType = _genderType;
});
}
When the genderType value is obtained I then decide which image to be the background image on the screen:
CachedNetworkImage(
placeholder: (context, url) =>
CircularProgressIndicator(),
imageUrl: genderType ? // : //
With all of that said, here is what is happening when the gender is changed on the firebase firestore:
The first time I navigate or refresh the screen nothing is changed and I get this error:
type 'Null' is not a subtype of type 'bool'
The second time I refresh or navigate to the screen, I do get the correct image on place but I get the same error message again
type 'Null' is not a subtype of type 'bool'
I have tried several ways to solve this issue but i dont seem to get it right.
Edit: I have noticed that when I removed the last part for CachedNetworkImage, I get no error so I think the problem might be on this part
In case like that when you need to wait for a future to build some UI, the go to way is to use a FutureBuilder
You use it like this
FutureBuilder<bool>(
future: getBoolValuesSF,
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
// build your UI here based on snapshot value
},
)
checkIfUserHasData() and getBoolValuesSF() both are future method. you can create another async method and put it inside initState.
#override
initState(){
super.initState();
newMthod();
}
newMthod() async{
await checkIfUserHasData();
await getBoolValuesSF();
}
Related
I am simply trying to set an ID in this function:
_getLastWorkoutId() async {
try {
var snapshot = await usersRef
.doc(currentUser!.uid)
.collection('workouts')
.orderBy('workoutDate', descending: true)
.limit(1)
.snapshots()
.first;
//The execution moves to build method from here------and then returns
for (var element in snapshot.docs) {
workoutId = element.id;
setState(() {
_isWorkoutIdSet = true;
});
}
//return snapshot;
} catch (e) {
print(e.toString());
}
//return null;
}
I call it in the initState:
#override
void initState() {
WidgetsBinding.instance!.addObserver(this);
super.initState();
//var snapshot = _getLastWorkoutId();
_getLastWorkoutId();
}
The problem is, the for loop executes after the build function is called. I don't want that to happen.
You can use FutureBuilder like this:
Future<bool> _value;
#override
void initState() {
WidgetsBinding.instance!.addObserver(this);
super.initState();
_value = _getLastWorkoutId();
}
And in your build method you have:
FutureBuilder<bool>(
future: _value,
builder: (
BuildContext context,
AsyncSnapshot<bool> snapshot,
) {
if (snapshot.hasData) {
if (snapshot.data){
//update view
}else{
//update view
}
}
}
The method can be like this:
_getLastWorkoutId() async {
try {
var snapshot = await usersRef
.doc(currentUser!.uid)
.collection('workouts')
.orderBy('workoutDate', descending: true)
.limit(1)
.snapshots()
.first;
for (var element in snapshot.docs) {
workoutId = element.id;
return true;
}
} catch (e) {
print(e.toString());
}
}
Here you can find more about FutureBuilder.
I believe this should solve the issue:
First, on build method:
return FutureBuilder(
future: _getLastWorkoutId(),
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) return CircularProgressIndicator();
return Container(); // here goes whatever it is you had before.
}
);
then on _getLastWorkoutId():
Future<void> _getLastWorkoutId() async {
...
}
That way the function returns a future of void instead of void, allowing FutureBuilder to do its thing.
You should declare a variable to save state. For example
var isLoading = true;
At the end of try block in func _getLastWorkoutId reset isLoading to false.
Then call setState or update state by better way used state management likes provider, bloc, get.
In build widget add check isLoading like this
return isLoading ? YourLoadingIndicatorWidget() : DisplayDataWidget();
i am trying to get data to a screen by shared prefrences so i set data here on my login screen:
var displayName=jsondata["username"];
SharedPreferences prefs=await SharedPreferences.getInstance();
prefs.setString('displayName', displayName);
and getting this data on my drawer:
String displayName="0";
initState() {
getData();
super.initState();
}
getData() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
displayName=prefs.getString('displayName')?? "0";
}
**it gets the data when i click on drawer button but when go back to main screen and re-enter in my drawer it gets value "0" **here is the demo of issue
what is wrong?
and here is the code of the other screen which I move in between:
late String displayName;
getData() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
displayName=prefs.getString('displayName')?? "0";
print(displayName);
}
initState() {
getData();
super.initState();
}
first of all always use a method after super.initState(); inside initState.
after that assuming that variable displayName is not updated anywhere else try to log when ever displayName changes in your code.
I can see that another screen is opening and then he value changes. so please show us more code of what you write.
Please refer to below code
String displayName="0";
initState() {
getData();
super.initState();
}
getData() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
if(displayName.isEmpty || displayName == "0"){
displayName=prefs.getString('displayName')?? "0";
await prefs.reload();
}
}
to Keep listening to code even you moved between pages,use StreamBuilder
StreamBuilder<DocumentSnapshot>(
stream:getData(),
builder: ( context, snapshot) {
if(snapshot.connectionState==ConnectionState.waiting){
return CircularProgressIndicator();
}else {
if(snapshot.hasData){
// .......
}
}
}
)
I have 3 page I check transitions with bottomNavigationBar first page is Soclose in this page im gettting information from the database and print it on the screen.
I'm getting information from the database smoothly but when i switch screens my console gives warning messages. An error appears in the console, but the application is working properly. When changing screens and returning to the old page(Soclose page), an error page appears and disappears within milliseconds.
I cant find similar questions and i tried to make suggestions in the warnings but either I couldn't do it or the solutions don't work.
Related soclose dart file:
class _Closesevents extends State<Soclose> {
List<Event> eventList;
int eventListLen;
#override
void initState() {
try{
final Future<Database> dbFuture = DbHelper.initializeDatabase();
dbFuture.then((database) {
Future<List<Event>> eventListFuture = DbHelper().getEventList();
eventListFuture.then((eventList) {
setState(() {
this.eventList = eventList;
this.eventListLen = eventList.length;
});
});
});}
catch (e,s)
{
print("[ERROR] $e");
print("[ERROR TREE]\n$s");
}
super.initState();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container(
child: new ListView.builder(
itemCount: eventListLen,
itemBuilder: (BuildContext context, int index) =>
buildTripCard(context, index)),
);
}
Widget buildTripCard(BuildContext context, int index)
...
Databasehelper file
import ...
class DbHelper {
static DbHelper _databaseHelper; // Singleton DatabaseHelper
static Database _database;
static final String _tablename = EventConstants.TABLE_NAME;
static final String _columnId = EventConstants.COLUMN_ID;
static final String _columnTitle = EventConstants.COLUMN_TITLE;
static final String _columnDate = EventConstants.COLUMN_DATE;
static final String _columnStartTime = EventConstants.COLUMN_STARTTIME;
static final String _columnFinishTime = EventConstants.COLUMUN_FINISHTIME;
static final String _columnDesc = EventConstants.COLUMN_DESCRIPTION;
static final String _columnIsActive = EventConstants.COLUMN_ISACTIVE;
DbHelper._createInstance(); // Named constructor to create instance of DatabaseHelper
factory DbHelper() {
if (_databaseHelper == null) {
_databaseHelper = DbHelper._createInstance(); // This is executed only once, singleton object
}
return _databaseHelper;
}
Future<Database> get database async {
if (_database == null) {
_database = await initializeDatabase();
}
return _database;
}
static Future<Database> initializeDatabase() async {
Directory directory = await getApplicationDocumentsDirectory();
String path = directory.path + 'takvimapp.db';
// Open/create the database at a given path
var notesDatabase = await openDatabase(path, version: 1, onCreate: _createDb);
return notesDatabase;
}
static void _createDb(Database db, int newVersion) async {
await db.execute('CREATE TABLE $_tablename ( $_columnId INTEGER PRIMARY KEY NOT NULL,$_columnTitle TEXT ,$_columnDate TEXT,$_columnStartTime TEXT,$_columnFinishTime TEXT,$_columnDesc TEXT,$_columnIsActive INTEGER);');
}
// Get all events --map
Future<List<Map<String, dynamic>>> getEventMapList() async {
Database db = await this.database;
var result = await db.query(_tablename, orderBy: '$_columnTitle ASC');
return result;
}
// Insert Operation: Insert a Event object to database
Future<int> insertEvent(Event event) async {
Database db = await this.database;
var result = await db.insert(_tablename, event.toMap());
return result;
}
// Update Operation: Update a Event object and save it to database
Future<int> updateEvent(Event event) async {
var db = await this.database;
var result = await db.update(_tablename, event.toMap(), where: '$_columnId = ?', whereArgs: [event.id]);
return result;
}
// Delete Operation: Delete a Event object from database
Future<int> deleteEvent(int id) async {
var db = await this.database;
int result = await db.rawDelete('DELETE FROM $_tablename WHERE $_columnId = $id');
return result;
}
// Get number of Event objects in database
Future<int> getCount() async {
Database db = await this.database;
List<Map<String, dynamic>> x = await db.rawQuery('SELECT COUNT (*) from $_tablename');
int result = Sqflite.firstIntValue(x);
return result;
}
// Convert map to list
Future<List<Event>> getEventList() async {
var eventMapList = await getEventMapList(); // Get 'Map List' from database
int count = eventMapList.length; // Count the number of map entries in db table
List<Event> eventList = List<Event>();
// For loop to create a 'Event List' from a 'Event List'
for (int i = 0; i < count; i++) {
eventList.add(Event.fromMap(eventMapList[i]));
}
return eventList;
}
static Future closeDb() => _database.close();
}
The error warning is constantly written to the console in an infinite loop.
To get rid of the warning, I need to close the app and restart the emulator.
Warning message:
E/flutter (30455): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: setState() >called after dispose(): _CountDownItemState#2bbc3(lifecycle state: defunct, not mounted)
E/flutter (30455): This error happens if you call setState() on a State object for a widget that no >longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its >build). This error can occur when code calls setState() from a timer or an animation callback.
E/flutter (30455): The preferred solution is to cancel the timer or stop listening to the animation >in the dispose() callback.
Another solution is to check the "mounted" property of this object >before calling setState() to ensure the object is still in the tree.
E/flutter (30455): This error might indicate a memory leak if setState() is being called because >another object is retaining a reference to this State object after it has been removed from the >tree. To avoid memory leaks, consider breaking the reference to this object during dispose().
Solution:
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: _db.getEventList(),
builder: (context, snapshot) {
if (snapshot.data == null) {
return Container(
child: Text("Loading....."),
);
} else {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(snapshot.data[index].title),
);
});
}
}),
);
}
The issue is with your initState function override. It's good practice to also call the super of initState, super.initState, before all other logic. Your Futures may be completing too quickly, and calling setState before the state is even initialized. Simply move super.initState(); as the first statement in the override. Ex.
#override
void initState() {
super.initState();//Always call this first
try{
final Future<Database> dbFuture = DbHelper.initializeDatabase();
dbFuture.then((database) {
Future<List<Event>> eventListFuture = DbHelper().getEventList();
eventListFuture.then((eventList) {
setState(() {
this.eventList = eventList;
this.eventListLen = eventList.length;
});
});
});}
catch (e,s)
{
print("[ERROR] $e");
print("[ERROR TREE]\n$s");
}
}
Edit: However, this can still lead to errors as setState could still be called before the widget is mounted. This is why the FutureBuilder widget exists. Wrap the widget that needs this Future data in your build method, pass the Future to the future parameter of the FutureBuilder and access the data with the AsyncSnapshot that the builder provides. See more about FutureBuilder.
Future<Map> returnUserMap() async {
final FirebaseUser currentUser = await _auth.currentUser();
Map userMap = {
"UserName": currentUser.displayName,
"UserEmail": currentUser.email,
"UserUrl": currentUser.photoUrl
};
print("1");
print(userMap);
return userMap;
}
return value type is Instance of 'Future>'.
I want to get a UserName, how can I do it?
Your function returnUserMap() returns a Future<Map>. I suspect that the error you describe is not in the code snippet you copied.
Whenever the task to be performed may take some time, you will receive a future. You can wait for futures in an async function with await.
It is therefore recommended to use a so-called FutureBuilder in your build() function:
FutureBuilder<FirebaseUser>(
future: _auth.currentUser(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
final FirebaseUser user = snapshot.data;
if (user.displayName == null || user.displayName.isEmpty())
return text(currentUser.email); // display email if name isn't set
return text(currentUser.displayName);
}
if (snapshot.hasError) {
return text(snapshot.error);
}
return text('loading...');
},
),
If you want to have the displayName outside your build() function, the following code should do the job when you are inside of an async function:
final FirebaseUser user = await _auth.currentUser();
final String displayName = user.displayName;
print('the displayName of the current user is: $displayName');
And this code when you are in a normal function:
_auth.currentUser().then((FirebaseUser user) {
String displayName = user.displayName;
print('displayName: $displayName');
}).catchError((error) {
print('error: ' + error.toString());
});
I think it's worth watching the following video for further understanding:
Async/Await - Flutter in Focus
I have a MainScreen (stateful) with the following method:
#override
void initState() {
super.initState();
WidgetsBinding.instance
.addPostFrameCallback((_) async {
loadFirebaseUser(context);
});
}
My 'loadFirebaseUser' method is in another file called Constants.dart which can be used from any screen.
The function is as follows:
Future<bool> loadFirebaseUser(BuildContext context) async {
Auth _auth = Auth();
FirebaseUser cUser = await _auth.currentUser();
if (cUser.isEmailVerified) {
DocumentSnapshot snapshot = await Firestore.instance
.collection('Profile')
.document(cUser.uid)
.get();
if (snapshot.data != null) {
User user = Provider.of<UserData>(context).getUser();
user = User.fromMap(snapshot);
Provider.of<UserData>(context).setUser(user);
return true;
} else {
return false;
}
}
return false;
}
I am getting the follwing error when this code is executed:
Unhandled Exception: Looking up a deactivated widget's ancestor is unsafe.
Needless to say that as result of this the user is not getting loaded. The error points to the following line:
User user = Provider.of<UserData>(context).getUser();
I want the 'loadFirebaseUSer' function to execute automatically and not on any button click, so this is the only place I know to place this code. Is there a way to achieve this differently? Thanks
Did you tried to get context this way?
Future.delayed(Duration.zero, () {
var myState = Provider.of<MyState>(context);
.......
});