In my case, I need to load some data from database at the beginning of the application. But I cannot load data, so I tested what is wrong with my code and found out it takes some time in await database and the code skip this function and execute other operations. In result , I get the data later after creating the UI, so cannot show data. Any way to fix that?
This is my code and result-
dbhelper.dart
static Database db_instance;
Future<Database> get db async{
if(db_instance == null)
db_instance = await initDB();
return db_instance;
}
initDB() async{
var dbDir = await getDatabasesPath();
var dbPath = join(dbDir, "test.db");
ByteData data = await rootBundle.load(join("assets","test.db"));
List<int> bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
await File(dbPath).writeAsBytes(bytes);
var db = await openDatabase(dbPath);
return db;
}
getTest() async{
print("Test1");
var db_connection = await db;
print("Test2");
List<Map> list = await db_connection.rawQuery('SELECT * FROM $TABLE_NAME Where id= 1');
print("Test3");
for(int i=0;i<list.length;i++){
print("Test4");
id = list[i]['id'];
name = list[i]['name'];
phone = list[i]['phone'];
}
print("Test5"+name);
}
main.dart
getContactsFromDB()async{
var dbHelper = DBHelper();
dbHelper.getTest();
print("Test6"+phone);
}
#override
void initState() {
getContactsFromDB();
super.initState();
}
Result
I/flutter (13203): Test1
I/flutter (13203): Test6 // skip Test2,3,4,5 and jump to another operation and data not collected
I/flutter (13203): Test2
I/flutter (13203): Test3
I/flutter (13203): Test4
I/flutter (13203): Test5 Zin
Just use FutureBuilder. Something like here:
Widget projectWidget() {
return FutureBuilder(
future: getContactsFromDB(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Container();
} else {
return CircularProgressIndicator();
}
}
)
}
Related
In the example below, I want to combine 2 firestore queries, but I could not get it to work.
final List<Who> pagedData =
await _query.get().then((QuerySnapshot snapshot) async {
if (snapshot.docs.isNotEmpty) {
_lastDocument = snapshot.docs.last;
} else {
_lastDocument = null;
}
return snapshot.docs.map((QueryDocumentSnapshot e) async {
final data = e.data() as Map<String, dynamic>;
Whoes w = Whoes.fromMap(e.data());
User u = await _firestore
.collection("user")
.doc(data['s'])
.get()
.then((DocumentSnapshot documentSnapshot) => User.fromMap(
documentSnapshot.data()));
return Who(w, u);
}).toList();
});
When I put await in the user part, things get confused and I couldn't edit it.
What I want to output as a result is List<Who>
What am I missing?
It gives me this error:
The return type 'List<Future<Who>>' isn't a 'Future<List<Who>>', as required by the closure's context.
I solved the problem, I leave it here for anyone who encounters this
final List<Who> pagedData =
await _query.get().then((QuerySnapshot snapshot) async {
if (snapshot.docs.isNotEmpty) {
_lastDocument = snapshot.docs.last;
} else {
_lastDocument = null;
}
Iterable<Future<Who>> futureWho =
snapshot.docs.map((QueryDocumentSnapshot e) async {
final data = e.data() as Map<String, dynamic>;
Whoes w = Whoes.fromMap(e.data());
User u = await _firestore
.collection("user")
.doc(data['s'])
.get()
.then((DocumentSnapshot documentSnapshot) => User.fromMap(
documentSnapshot.data()));
return Who(w, u);
});
Future<List<Who>> listWho = Future.wait(futureWho);
return listWho;
});
Upon creating an account the user's firestore creates a field that displays the current amount of plastics the user has. So far, I have a button that updates that amount using the user's text field input. I've heard of something called a transaction which apparently allows one to intsead add the input amount to the overall data for it to be displayed? How would I accomplish this in my case when the use inputs a new amount?
Code:
database.dart
Future<void> userSetup(String displayName) async {
int plastics = 0;
final CollectionReference users =
FirebaseFirestore.instance.collection('UserNames');
FirebaseAuth auth = FirebaseAuth.instance;
String uid = auth.currentUser.uid.toString();
users.doc(uid).set({'displayName': displayName, 'uid': uid});
//This is the field for plastics amount starting at 0.
users.doc(uid).update({'plastics': plastics});
return;
}
How I retrieve the amount data:
final firestore = FirebaseFirestore.instance;
FirebaseAuth auth = FirebaseAuth.instance;
Future<String> getPlasticNum() async {
final CollectionReference users = firestore.collection('UserNames');
final String uid = auth.currentUser.uid;
final result = await users.doc(uid).get();
return result.data()['plastics'].toString();
}
How I display it:
FutureBuilder(
future: getPlasticNum(),
builder: (_, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
return Text(snapshot.data,
style: TextStyle(color: Colors.amber[400], fontSize: 20));
},
),
Currently how the user can replace the amount, but not add to it(The problem)
OnPressed: () async {
try {
final String uid = auth.currentUser.uid;
FirebaseFirestore.instance
.collection('UserNames')
.doc(uid)
.update({
"plastics": int.parse(_plasticController.text),
});
Navigator.of(context).pop();
} catch (e) {}
},
I made a separate future to take care of adding:
final firestore = FirebaseFirestore.instance; //
FirebaseAuth auth = FirebaseAuth.instance;
Future<bool> addPlastic(String amount) async {
try {
String uid = auth.currentUser.uid;
var value = double.parse(amount);
DocumentReference documentReference =
FirebaseFirestore.instance.collection('UserNames').doc(uid);
FirebaseFirestore.instance.runTransaction((transaction) async {
DocumentSnapshot snapshot = await transaction.get(documentReference);
if (!snapshot.exists) {
documentReference.set({'plastics': value});
return true;
}
double newAmount = snapshot.data()['plastics'] + value;
transaction.update(documentReference, {'plastics': newAmount});
return true;
});
} catch (e) {
return false;
}
}
Then I just called it when the button was pressed
onPressed(){
addPlastics(_plasticController.text);
}
when I try to retrieve a data from firestore with this code.
Future<String> getUserType() async {
await (Firestore.instance
.collection('users')
.document(getUserUID().toString())
.get()
.then((DocumentSnapshot ds) {
return ds['type'];
}));
}
i get this error
NoSuchMethodError: The method '[]' was called on null.
I/flutter (15824): Receiver: null
I/flutter (15824): Tried calling: []("type")
I also tried:
return ds.data['type'];
The code to retrive the uid of user is:
Future<String> getUserUID() async {
return (await _firebaseAuth.currentUser()).uid;
}
but I don't think that this is the problem, maybe in ds there is nothing.
You need to retrieve the userID first and then use that in your document retrieval:
Future<String> getUserType() async {
String userID = (await _firebaseAuth.currentUser()).uid;
await (Firestore.instance
.collection('users')
.document(userID)
.get()
.then((DocumentSnapshot ds) {
return ds['type'];
}));
}
In your code:
Future<String> getUserUID() async {
return (await _firebaseAuth.currentUser()).uid;
}
getUserUID() returns a Future, but when you do .document(getUserUID().toString()) you are not getting the result of that Future.
Check the following:
https://dart.dev/codelabs/async-await
Your getUserUID() method returns a Future String not a regular String.So that you cannot directly get a document by providing that.This is the usual way I implement a function like this.
Future<String> getUserType() async {
getUserUID().then((currentUser) {
if (currentUser != null) {
await (Firestore.instance
.collection('users')
.document(currentUser)
.get()
.then((DocumentSnapshot ds) {
return ds['type'];
}));
}
}
}
I would like to ask whats going on with my code.
Assuming the 'Counter' field is 179 in this instance, how do I make my outside myData update before printing?
class Test {
Firestore _firestore = Firestore.instance;
var myData;
void getData() async {
DocumentSnapshot snapshot =
await _firestore.collection('Counter').document('Counter').get();
myData = await snapshot.data['Counter'];
print('inside $myData');
}
void checkMyData() {
myData = 5;
getData();
print('outside $myData');
}
}
Console:
flutter: outside 5
flutter: inside 179
You have to make getData() return a Future like this:
Future getData() async {
So you can do this:
getData().then((value) {
print('value: $value');
}).catchError((error) {
print('error: $error');
});
But you probably want to use a FutureBuilder to show the information when arrives, like this:
FutureBuilder(
future: getData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text('value: ${snapshot.data}');
} else if (snapshot.hasError){
return Text('error: ${snapshot.error}');
}
return Text('loading...');
},
)
I have two functions,
First one to get the users from Firestore. Then I use "Future.ForEach" to loop on those users.
Future<List> getData() async {
targetData.clear();
await Firestore.instance
.collection('users')
.document(currentUserId)
.collection('chats')
.getDocuments()
.then((userChats) async {
// Only two documents are coming from the db
return await Future.forEach(userChats.documents, getTargetData)
.then((onValue) {
print('forEech is done');
});
});
print('getData Returen');
// I use "targetData" to build viewList
return targetData;
}
Calling this function from the Future.forEach
Future<List> getTargetData(DocumentSnapshot targetDoc) async {
print('looping');
await Firestore.instance
.collection('users')
.document(targetDoc.documentID)
.get()
.then((targetRef) {
targetData.add(new TargetUserData(
targetRef.documentID,
targetRef.data['nickname'],
targetRef.data['photoUrl'],
targetDoc.data['timestamp'],
targetRef.data['token'],
targetDoc.data['unseen']));
});
return targetData;
}
I'm calling getData from inside the FutureBuilder
return new FutureBuilder(
future: getData(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return buildLoading();
default:
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
else
return createListView(context, snapshot);
}
},
);
When I run the app it works perfectly fine and as expected - Here is the log from the print statement:
I/flutter (18780): looping
I/flutter (18780): looping I/flutter
(18780): forEech is done
I/flutter (18780): getDataReturen
Hot Restart - First Run
I expect on hot reload that I get the same result But, what is coming is!!
I/flutter (18780): looping
I/flutter (18780): looping
I/flutter (18780): looping
I/flutter (18780): forEech is done
I/flutter (18780): getDataReturen
I/flutter (18780): forEech is done
I/flutter (18780): getDataReturen
Hot Reloads ...
Question: Why the forEach is looping more than expected?
I was able to solve it by reframing the code:
Future<List> _getData() async {
var allUsers = await Firestore.instance.collection('users').getDocuments();
List<TargetUserData> users = [];
for (var userData in allUsers.documents) {
if (currentUserId != userData.documentID) {
await getTargetData(userData.documentID).then((targetData) {
TargetUserData targetUserData = TargetUserData(
userData.documentID,
userData.data['nickname'],
userData.data['photoUrl'],
targetData[0],
userData.data['token'],
targetData[1],
targetData[2]);
users.add(targetUserData);
});
}
}
return users;
}
Future getTargetData(var targetId) async {
List targetData = [];
await Firestore.instance
.collection('users')
.document(currentUserId)
.collection('chats')
.document(targetId)
.get()
.then((advData) {
if (advData.data != null) {
targetData.add(advData.data['timestamp']);
targetData.add(advData.data['unseen']);
targetData.add(advData.data['lastMsg']);
} else {
targetData.add('0');
targetData.add('0');
targetData.add('Start Chating!');
}
}).catchError((onError) {
print('on error: $onError');
});
return targetData;
}