Flutter future double data issue - flutter

I am using Futurebuilder to show List issue is its running function 2 time or 3 times i dont know and showing data 2 times.
My function code
getCustomerList() async {
customerS = [];
print('check running');
final storage = new FlutterSecureStorage();
String uUid = await storage.read(key: "uUid");
CollectionReference _collectionRef =
FirebaseFirestore.instance.collection('Transaction');
QuerySnapshot querySnapshot =
await _collectionRef.get();
// Get data from docs and convert map to List
List allData = querySnapshot.docs.where((element) => element['CustomerID'] == widget.data['customerID'])
.map((doc) => doc.data())
.toList();
print(allData);
print('allData length ${allData.length}' );
for (int i = 0; i < allData.length; i++) {
print(allData[i]);
customerS.add(allData[i]);
}
print(customerS);
print('cus length ${customerS.length}' );
return customerS;
}
My future builder code
FutureBuilder(
future: getCustomerList(),
builder: (context, snapshot) {
print('snapshot length ${snapshot.data.length}');
print(snapshot);
print(snapshot.data);
if (snapshot.hasData)
return ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
print('List length ${snapshot.data.length}');
return Padding(
padding: const EdgeInsets.only(left: 13, right: 13),
child: Container(
decoration: BoxDecoration(
border: Border(
top: BorderSide(color: Colors.grey, width: .5)),
),
child: Padding(
padding: const EdgeInsets.all(13.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'10th May ',
style: TextStyle(fontFamily: 'PoppinsMedium'),
),
Text(
snapshot.data[index]['give'].toString(),
style: TextStyle(
fontFamily: 'PoppinsMedium',
color: Colors.green),
),
],
),
),
),
);
},
);
else
return Text('Result: ${snapshot.data.body}');
}
),
Issue is i have only 2 arrays in list but its showing for look like its running two time i am also try to clear the array but nothing work.
You can look on this picture
enter image description here
I have only 2 array which are in red circle and its showing double can see in blue circle.

Why you are using customerS I mean its saving data in this by initState and when Future call its double.
Remove it from initState and just simply return allData like this
getCustomerList() async {
customerS.clear();
print('check running');
final storage = new FlutterSecureStorage();
String uUid = await storage.read(key: "uUid");
CollectionReference _collectionRef =
FirebaseFirestore.instance.collection('Transaction');
QuerySnapshot querySnapshot = await _collectionRef.get();
// Get data from docs and convert map to List
List allData = querySnapshot.docs
.where((element) => element['CustomerID'] == widget.data['customerID'])
.map((doc) => doc.data())
.toList();
print(allData);
return allData;
}

From the docs:
The future must have been obtained earlier, e.g. during State.initState, State.didUpdateWidget, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.
This line:
FutureBuilder(
future: getCustomerList(), <- this line
...
)

Related

How to display all data in array?

I'm trying to display data from an array in firestore. I displayed it, but only [0] in the array is showing. I'm trying to get all the data in the array to show.
builder: (_, AsyncSnapshot<List<DocumentSnapshot>> snapshot){
if(snapshot.hasData){
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: ((_, index) {
List<Widget> tiles = [];
for (Map post in snapshot.data![index]['posts']) {
tiles.add(
Expanded(
child: Container(
margin: EdgeInsets.all(2),
padding: EdgeInsets.all(1),
decoration: BoxDecoration(border: Border.all(color:Colors.black)),
child: Center(
child: ListTile(
title: Text(post['postText'], style: TextStyle(color: Colors.white),),
subtitle: Text(post['fromUser'], style: TextStyle(color: Colors.white),),
),
),
),
)
);
}
return Expanded(
child: ListView(
children: tiles,
),
);
}),
);
}
else{
return Center(child: CircularProgressIndicator(),);
}
},
enter image description here
Edit
To answer your qn about newest to oldest:
I suggest you put a FieldValue.timestamp field in your group chat documents! Then, you can order them like this:
Future<List<DocumentSnapshot>> getDoc(groupID) async {
var firestore = FirebaseFirestore.instance;
QuerySnapshot qn = await firestore.collection('groups')
.where('groupChatId', isEqualTo: groupID)
.orderBy('timestamp', descending: true) // <- Here!
.get();
return qn.docs;
}
(All of that I copied by hand, since you hadn't provided this code as text, as I asked you to!... 😆)
If you don't have a timestamp field, there is a way to still find out when a document was created... but I don't know how. Plus, in this case, I guess you want the time a certain FIELD was created in the document...! I don't know if that's possible. In fact, for that you'll probably have to do:
List<Map> posts = snapshot.data![index]['posts'];
// Sort list according to the 'date' field in each Map in the list:
posts.sort((mapA, mapB){
return mapA['date'].compareTo(mapB['date']);
});
// Then you'll use posts in your for-loop instead of snapshot.data![index]['posts']:
for (Map post in posts) {
tiles.add( /*etc*/);
}
Btw, if you want it to update when new messages come in, you can do like this:
import 'dart:async';
// Put the below in the State of a StatefullWidget:
StreamSubscription<QuerySnapshot<Map<String, dynamic>>>? qn;
List<DocumentSnapshot>? eventDocs;
void getDocStream(groupID) async {
var firestore = FirebaseFirestore.instance;
qn = firestore.collection('groups')
.where('groupChatId', isEqualTo: groupID)
.orderBy('timestamp', descending: true)
.snapshots().listen((event) {
// Put here everything you want to happen when new things happen in the stream!
// For example:
setState(() {
eventDocs = event.docs;
});
// Now, you can use eventDocs instead of getDoc(groupID), as you did before.
// Just remember that it will be null at first!
});
}
#override
void dispose() {
if (qn != null) qn!.cancel(); // This is to prevent the stream from going on, after you've left the page or even closed the app...
super.dispose();
}
Old answer:
But you're telling it to display only post [0]!...
If there are more posts in each document, and you want to display all of them, you need to make a for-loop or something. For example:
itemBuilder: ((_, index) {
List<Widget> tiles = [];
for (Map post in snapshot.data![index]['posts']) {
tiles.add(
ListTile(
title: Text(post['postText']),
subtitle: Text(post['fromUser']),
));
}
return Expanded(
child: Column(
children: tiles,
),
);
}),
And btw... Next time you ask a qn, plz paste your code as text rather than an image! So that we can copy-paste it into our answer, rather than having to retype it from the image. It's so easy to make a mistake and then you get an error coz we didn't copy it right.
try this
title: Text(snapshot.data![index]['posts']['postText']),

Lazy Loading prevent the search or filter in my app (flutter & firestore)

I am new with flutter,
I would be grateful if you could help me with this problem:
I have a huge set of firestore data that I have to display i a listview, so I had to use Lazy Loading when I used it, the problem appeared, and I can't search or filter in all the data, only the data shown
so I need to use lazy Load or pagination and in the same time can search or filter all the firestore data
enter image description here
and
enter image description here
void initState() {
_chatScrollController = ScrollController()
..addListener(() {
if (_chatScrollController!.position.atEdge) {
if (_chatScrollController!.position.pixels == 0)
;
else {
setState(() {
loadMoreMsgs = loadMoreMsgs + a;
});
}
}
});
super.initState();
}
stream: FirebaseFirestore.instance
.collection(kProductsCollection)
.limit(loadMoreMsgs)
.snapshots(),
builder: (context, snapshots) {
return (snapshots.connectionState == ConnectionState.waiting)
? const Center(
child: CircularProgressIndicator(),
)
: ListView.builder(
controller: _chatScrollController,
itemCount: snapshots.data!.docs.length,
itemBuilder: (context, index) {
var data = snapshots.data!.docs[index].data()
as Map<String, dynamic>;
List<Product> products = [];
for (var doc in snapshots.data!.docs) {
products.add(
Product(
pId: doc.id,
pName: doc.get(kProductName),
pPrice: doc.get(kProductPrice),
pUrl: doc.get(kProductUrl),
pIngredients: doc.get(kProductIngredients),
pCompany: doc.get(kProductCompany),
pDose: doc.get(kProductDose),
pCode: doc.get(kProductCode),
pClass: doc.get(kProductClass),
pCountry: doc.get(kProductCountry),
pDescription: doc.get(kProductDescription),
pPregnancyCategory:
doc.get(kProductPregnancyCategory),
),
);
}
if (products[index]
.pName
.toString()
.toLowerCase()
.contains(name.toLowerCase())) {
return Padding(
padding: const EdgeInsets.fromLTRB(20, 5, 20, 5),
child: GestureDetector(
onTap: () {
Navigator.pushNamed(context, ProductDetalis.id,
arguments: products[index]);
},
child: CardWidget(
width: width, height: height, data: data)),
);
}

Flutter - How to get the value of a provider call function that requires 'await' within a variable?

I'm trying to make a budget app where each budget has its own spending history. Each of those spending histories would have a variable called 'budgetName' which I can compile and total the amount of spending by using sqflite code as below.
return await db.rawQuery("select sum(budgetSpent) as total from spending where budgetName ='" + budgetTitle + "'");
and this works if I try to use a .then((value) {print(value);}) when calling the sqflite function and see the value of each budget's spendings in the debug console.
But the problem is that I need the 'budgetTitle' when calling the function so it can compare with the spending's 'budgetName' to get the total spending amount.
So what I have right now is I try to get the spending amount like below:
child: BudgetCard(
budgetName: budget.budgetName,
budgetSpent: '${Provider.of<SpendingDatabaseHelper>(context, listen: false).getSpecificSpending(budget.budgetName}',
maxBudget: currency.format(int.parse(budget.maxBudget)),
svgIcon: iconListBudgetCards[budget.iconValue],
color: colorSwatch[budget.colorValue],
percentage: 0.5),
),
But it only returns Instance of 'Future<dynamic>' because it needs the 'await' before getting the value. But I couldn't find another way of doing this because it needs the 'budgetTitle' to be passed on.
Any help, ideas, or suggestions are highly appreciated! thank you in advance.
Here is the database code:
String? budgetSpendingAmount;
getSpecificSpending(budgetTitle) async {
dynamic result =
await SpendingDatabaseHelper.instance.getSpendingAmount(budgetTitle);
String a = result.toString();
debugPrint('A: $a');
if (a == '[{total: null}]') {
a = currency.format(int.parse('000'.trim()));
budgetSpendingAmount = a;
print(budgetSpendingAmount);
} else {
String? b = a.replaceAll(RegExp(r'[{\}\[\]\-]+'), '');
String c = b.substring(b.indexOf(":") + 1);
budgetSpendingAmount = currency.format(int.parse(c.trim()));
}
notifyListeners();
}
Future getSpendingAmount(String budgetTitle) async {
Database db = await instance.database;
return await db.rawQuery("select sum(budgetSpent) as total from spending where ='" + budgetTitle + "'");
}
Here is the full code of where I call the function to get the spending amount data:
Widget build(BuildContext context) {
return FutureBuilder<List<Budget>>(
future: Provider.of<BudgetDatabaseHelper>(context).getBudgets(),
/// Displaying the data from the list
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center();
}
return snapshot.data!.isEmpty
? const Flexible(
child: Center(
child: Padding(
padding: EdgeInsets.only(bottom: 80.0),
child: Text(
'You don\'t have any budget',
style: kCaption,
),
)))
: Flexible(
child: ListView.builder(
physics: const BouncingScrollPhysics(),
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
final budget = snapshot.data![index];
return Dismissible(
key: UniqueKey(),
background: const Align(
alignment: Alignment.centerRight,
child: Padding(
padding: EdgeInsets.only(bottom: 12.0, right: 24),
child: Icon(
IconlyLight.delete,
color: cRed,
size: 24,
),
),
),
direction: DismissDirection.endToStart,
onDismissed: (direction) {
snapshot.data!.removeAt(index);
Provider.of<BudgetDatabaseHelper>(context,
listen: false)
.removeMethod(budget.id!, budget.budgetName);
},
child: GestureDetector(
onTap: () => showModalBottomSheet(
backgroundColor: Colors.transparent,
context: context,
enableDrag: true,
isScrollControlled: true,
builder: (context) {
return DraggableScrollableSheet(
snap: true,
minChildSize: 0.43,
maxChildSize: 0.85,
initialChildSize: 0.43,
snapSizes: const [0.43, 0.85],
builder: (context, scrollController) {
return ClipRRect(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(32),
topRight: Radius.circular(32)),
child: Container(
color: cWhite,
child: SingleChildScrollView(
controller: scrollController,
physics: const BouncingScrollPhysics(),
child: BudgetDetails(
id: budget.id!,
budgetName: budget.budgetName,
budgetSpent: 'budgetSpent',
colorValue:
colorSwatch[budget.colorValue],
maxBudget: currency.format(
int.parse(budget.maxBudget)),
svgIcon: iconListBudgetDetails[
budget.iconValue],
),
),
),
);
},
);
},
),
child: BudgetCard(
budgetName: budget.budgetName,
budgetSpent: '${Provider.of<SpendingDatabaseHelper>(context, listen: false).getSpecificSpending(budget.budgetName}',
maxBudget: currency.format(int.parse(budget.maxBudget)),
svgIcon: iconListBudgetCards[budget.iconValue],
color: colorSwatch[budget.colorValue],
percentage: 0.5),
),
);
},
),
);
},
);
}
Use provider in a widget tree is not a good idea. Make a statefullWidget
Make a getter in your SpendingDatabaseHelper like this
String? _budgetSpendingAmount;
String get budgetSpendingAmount=> _budgetSpendingAmount;
and initialize it like this _budgetSpendingAmount = currency.format(int.parse(c.trim()));
So using this getter you can access this value anywhere in widget tree
Future<void> _getSpecificSpending(String budgetName)async{
try{
await Provider.of<SpendingDatabaseHelper>(context, listen: false).getSpecificSpending(budgetName);
} catch(e){
print('error :$e');
}
}
and in your widget tree write something like this
child: FutureBuilder(
future : _getSpecificSpending(budget.budgetName)
builder: (ctx,snapshot){
var spendDataProv=Provider.of<SpendingDatabaseHelper>(context, listen: false);
return snapshot.connectionState==ConnectionState.waiting ?
CircularProgressIndicator() :
BudgetCard(
budgetName: budget.budgetName,
budgetSpent:spendDataProv.budgetSpendingAmount ,
maxBudget: currency.format(int.parse(budget.maxBudget)),
svgIcon: iconListBudgetCards[budget.iconValue],
color: colorSwatch[budget.colorValue],
percentage: 0.5)
},
)
Some idea's
Use a FutureBuilder inside your BudgetCard widget. You can then show a CircularProgressIndicator where the spent amount is going to be when you are still waiting on the future to finish.
Or
Use a Boolean flag (which you flip at the beginning of the future method and at the end) that indicates whether the future is finished. Flag false: show progressIndicator, flag true show the spent amount.
Or
When calling Provider.of<BudgetDatabaseHelper>(context).getBudgets() you can let the method getBudgets() also fill an array with the information you need later on. So, call Provider.of<SpendingDatabaseHelper>(context, listen: false).getSpecificSpending(budget.budgetName) inside the getBudgets() method for each budgetName you have.

Flutter FutureBuilder Snapshot is null but Future Does return data

While working with Flutter for a new application client for Kanboard, I encountered the following problem. I have a FutureBuilder that should return a select dropdown menu with items but, for some reason, the Snapshot data is null, although the Future method does resolves and has data on return.
Full page.dart code here: https://pastebin.com/J48nxsdZ
The block having the problem is the following:
Widget _columnSelect() {
return FutureBuilder(
future: columnProvider.getColumns(task.projectId),
builder: (BuildContext context, AsyncSnapshot snapshot) {
List<DropdownMenuItem<String>> columnList = [];
if (snapshot.hasData) {
columnList.add(DropdownMenuItem<String>(
child: Text('Select Column'), value: 0.toString()));
_columns = snapshot.data;
} else {
columnList.add(DropdownMenuItem<String>(
child: Text('Loading..'), value: 0.toString()));
}
_columns.forEach((column) {
columnList.add(DropdownMenuItem<String>(
child: Container(
child: Text(
column.title,
),
),
value: column.id.toString()));
});
return Container(
// margin: EdgeInsets.only(left: 40.0),
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: DropdownButtonFormField(
icon: Padding(
padding: const EdgeInsets.only(right: 12),
child: Icon(Icons.view_column, color: Colors.blue),
),
items: columnList,
value: _columnId,
decoration: InputDecoration(helperText: 'Optional'),
onChanged: (newValue) {
_columnId = newValue;
},
),
);
},
);
}
This is a duplicate of a widget in the same form for a user dropdown select. The original widget (in the same page) is the following:
Widget _ownerSelect() {
return FutureBuilder(
future: userProvider.getUsers(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
List<DropdownMenuItem<String>> usernameList = [];
if (snapshot.hasData) {
usernameList.add(DropdownMenuItem<String>(
child: Text('Select Owner'), value: 0.toString()));
_users = snapshot.data;
} else {
usernameList.add(DropdownMenuItem<String>(
child: Text('Loading..'), value: 0.toString()));
}
_users.forEach((user) {
usernameList.add(DropdownMenuItem<String>(
child: Container(
child: Text(
user.name,
),
),
value: user.id.toString()));
});
return Container(
// margin: EdgeInsets.only(left: 40.0),
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: DropdownButtonFormField(
icon: Padding(
padding: const EdgeInsets.only(right: 12),
child: Icon(Icons.person, color: Colors.blue),
),
items: usernameList,
value: _ownerId,
decoration: InputDecoration(helperText: 'Optional'),
onChanged: (newValue) {
_ownerId = newValue;
},
),
);
},
);
}
For some reason, the "_columnSelect" AsyncSnapshot is null always, even when the Future method is working fine:
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:kanboard/src/models/column_model.dart';
import 'package:kanboard/src/preferences/user_preferences.dart';
class ColumnProvider {
final _prefs = new UserPreferences();
Future<List<ColumnModel>> getColumns(projectId) async {
final Map<String, dynamic> parameters = {
"jsonrpc": "2.0",
"method": "getColumns",
"id": 887036325,
"params": {"project_id": projectId}
};
final credentials = "${_prefs.username}:${_prefs.password}";
Codec<String, String> stringToBase64 = utf8.fuse(base64);
String encoded = stringToBase64.encode(credentials);
final resp = await http.post(
Uri.parse(_prefs.endpoint),
headers: <String, String>{"Authorization": "Basic $encoded"},
body: json.encode(parameters),
);
final decodedData = json.decode(utf8.decode(resp.bodyBytes));
final List<ColumnModel> columns = [];
final List<dynamic> results = decodedData['result'];
if (decodedData == null) return [];
results.forEach((column) {
final columnTemp = ColumnModel.fromJson(column);
columns.add(columnTemp);
});
print(columns);
return columns;
}
}
The output of "print(columns)" returns:
I/flutter ( 9486): [Instance of 'ColumnModel', Instance of 'ColumnModel', Instance of 'ColumnModel', Instance of 'ColumnModel']
I don't know what I'm missing here. The form has 2 users dropdown select (with the original FutureBuilder Widget) which works just fine. The Column widget with the Future Builder is the one with the "null" problem in snapshot.data.
Thank you in advance for your time and support with this!
I just found where the problem was:
In the form page(new Task page), The columnProvider.getColumns(task.projectId)) wasn't executing because the "task.projectId" parameter is a String, but the API needs an int.
I was confused because the method were being called by the previous page (A project Page with all the tasks) and the getColumn's argument was indeed an integer: int.parse(projectId).
The Kanboard API doesn't return an error code if the ID parameter is other than INT with this specific call "getColumns" (for some reason).
Of course, Flutter (or Dart) is waiting for a response from http.post that would never arrive. When comparing the two calls from the two pages, I noticed the difference.
So, in conclusion, I specified the int data type argument in the getColumn definition in order to avoid any confusion:
Future<List<ColumnModel>> getColumns(int projectId) async {
Best Regards!

Flutter: Retrieve associated object from Future in FutureBuilder widget

I am fetching the user 'event manager id' data coming from a future of the object 'event'. I would like now to fetch a user using that id to display his name next the event. However, my FutureBuilder widget only takes into account one future (Event) and I am not able to retrieve that user's name based on that event since my fetchUser method will only return Future objects.
Any help is greatly appreciated.
Here's the FutureBuilder widget:
body: new FutureBuilder(
future: events,
builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
List<Event> availableEvents = snapshot.data;
if (!snapshot.hasData) return CircularProgressIndicator();
return new ListView.builder(
scrollDirection: Axis.vertical,
padding: new EdgeInsets.all(6.0),
itemCount: availableEvents.length,
itemBuilder: (BuildContext context, int index) {
user = fetchUserbyId( // Here, user is of type Future<user> and I cannot retrieve info such as the name of that user
(availableEvents[index].managerId).toString());
return new Container(
margin: new EdgeInsets.only(bottom: 6.0),
padding: new EdgeInsets.all(6.0),
color: Colors.white,
child: Column(
children: <Widget>[
new Text('${availableEvents[index].name}',
style: TextStyle(
fontWeight: FontWeight.bold,
height: _height,
fontSize: 18)),
new Text('${availableEvents[index].description}',
style: TextStyle(height: _height)),
new Text('${availableEvents[index].address}',
style: TextStyle(height: _height)),
new Text('${availableEvents[index].datetime}',
style: TextStyle(height: _height)),
//new Text('${availableEvents[index].managerId}', style: TextStyle(height: _height)),
new FlatButton(
onPressed: null,
// Simply call joinEvent for event 'availableEvents[index]'
color: Colors.redAccent,
textColor: Colors.white,
disabledColor: Colors.red,
disabledTextColor: Colors.white,
padding: EdgeInsets.all(8.0),
splashColor: Colors.redAccent,
child: Text('Join!'),
)
],
));
},
);
}));
Here is the fetchUserByID method:
Future<User> fetchUserbyId(String id) async {
final response =
await http.get('https://url-here.com' + id);
//print("response : " + response.body);
if (response.statusCode == 200) {
// If the call to the server was successful, parse the JSON.
return User.fromJson(json.decode(response.body));
} else {
// If that call was not successful, throw an error.
throw Exception('Failed to load post');
}
}
If what I'm understanding is you have two asynchronous calls, where the second one needs the results of the first call to execute. The best way to go around this is to create a helper method, i.e. getData(). In this method you make your call to events and then use that to fetchUserbyId. This would result in your FutureBuilder looking something like this:
FutureBuilder(
future: getData()
builder: ... // get the results the same why you got your results from events in the given example.
);
Then in you getData() method it would look something like this:
Future<User> getData() async {
var availableEvents= await events; // not sure what your events data/method is
return fetchUserbyId((availableEvents[index].managerId).toString());
}
I think I answered your question, but if I missed it please comment.
Note: On a completely unrelated topic, you don't need the new keyword in Flutter anymore to instantiate objects. Hope that speeds up your development process!