Code after "await" is not executing properly in flutter - flutter

I am trying to read data from a Firebase real time database and use it in a bar graph.
My code starts off by simply reading the data from the DB (specifically the item names), and then storing them in a list (itemNames). This is all done in the activateListeners() method.
From this point, I call the activateListners() method in the generateData() method, in order to start using the data in the itemNames list for the bar chart. Since the activateListeners() method is asynchronous, I use the "await" keyword to ensure that the item names are stored in the list before moving on.
After this point, I plan on creating ProductSales objects with the name of each item from the database, as well as the quantity. This will be done by getting the item names from the itemNames list.
However, before I do this, I was testing to see if the bar chart would work normally with test data.
The issue is that when I run the code, the bar chart does not display, as it seems to not be reading in the data. HOWEVER, if I remove the "await activateListners()" from the generateData() method, the bar chart displays the test data perfectly.
Why is the ber chart not displaying the data when I await for the activateListeners() method to execute first?
Any help would be much appreciated!
class _ProductBarChartState extends State<ProductBarChart> {
//Ref to DB
final DatabaseReference _dbRef = FirebaseDatabase.instance.ref();
late DataSnapshot _itemStream;
//Stores the description of each menu item in the DB
String itemName = "";
String itemID = "";
List<String> itemNames = [];
List<String> itemIDs = [];
//Reads the item names from the DB and adds them to a list
Future _activateListeners() async {
for (int i = 1; i <= 10; i++) {
itemID = "J$i";
_itemStream = await _dbRef.child("menuItem/$itemID/itemName").get();
itemName = _itemStream.value.toString();
itemNames.addAll([itemName]);
}
}
List<charts.Series<ProductSales, String>> _seriesBarData =
[]; //A list that will store all the sales data for the bar chart report
_generateData() async {
await _activateListeners();
var BarChartData = [
//Stores all ProductSales objects for the product report
ProductSales("Hake", 8),
ProductSales("Toasted", 15),
ProductSales("Chick strips", 28),
ProductSales("Kota", 40),
];
//Adding the BarChartData (seen above) to the list
_seriesBarData.add(charts.Series(
id: 'SalesPerProduct',
colorFn: (_, __) =>
charts.ColorUtil.fromDartColor(Color.fromARGB(255, 255, 157, 38)),
domainFn: (ProductSales productSales, _) => productSales.productName,
measureFn: (ProductSales productSales, _) => productSales.noOfProductSold,
data: BarChartData,
));
}
#override
void initState() {
// TODO: implement initState
super
.initState(); //This runs the original initState function that we inherited from the material app class (even though we override i)
_seriesBarData = <charts.Series<ProductSales, String>>[];
_generateData(); //Generates all the data for the chart (method specified above)
}

Call setState to update the UI end of _generateData
_generateData() async {
//...
setState((){}) ;
}

Related

How to notify a stream to update in Flutter

I am trying to implement pagination in my Flutter app but this is the first time I have done it. My idea was to create a Stream of data that updates each time the user reaches the bottom of a list. I have failed to get this working. My current code has the logic for getting the new data and adding it to the existing data, but right now it's not even returning the first range of data so the snapshots are empty. As for the pagination functionality, I tried to use a ChangeNotifier to notify the Stream to update, but I don't know if that is working. Please take a look at the code below and let me know what should be changed.
The DataProvider class:
class DataProvider extends ChangeNotifier{
DataProvider() : super();
static var changeController = ChangeNotifier();
static void reload() {
changeController.notifyListeners();
}
static int lastRange = 0;
static List data = [];
static Stream<List> paginatedUsersAndPosts() async* {
List<UserSearchResult> usersList = data.first;
List<Post> postsList = data.last;
print('Notifier');
changeController.addListener(() async {
print('Change notified, getting data');
List<int> range() {
if (lastRange == 0) {
lastRange = 10;
return [0, 10];
} else {
// Example 0, 10 => 11, 20
int newMin = lastRange + 1;
int newMax = lastRange + 10;
lastRange = newMax;
return [newMin, newMax];
}
}
List<Map<String, dynamic>> postsDocs = await Supabase.db
.from('posts')
.select()
.order('date', ascending: false)
.range(range().first, range().last);
List<Post> newPostsList =
postsDocs.map((postDoc) => Post.fromJson(postDoc)).toList();
newPostsList.forEach((post) async {
postsList.add(post);
if (usersList.where((u) => u.uid == post.uid).isNotEmpty) {
Map<String, dynamic> userDoc =
await Supabase.db.from('profiles').select().single();
ProfileInfoObject profileInfo = ProfileInfoObject.fromJson(userDoc);
print('New profile: $profileInfo');
Profile profile = Profile(profileInfo, []);
profile.posts.add(post);
List blockedUsers = userDoc['blockedUsers'] as List;
UserSearchResult user = (UserSearchResult(
profile, userDoc['uid'].toString(), blockedUsers));
usersList.add(user);
}
});
});
yield [usersList, postsList];
}
}
The main widget that uses the stream:
class FloatingTabBarView extends StatefulWidget {
const FloatingTabBarView({Key? key}) : super(key: key);
#override
State<FloatingTabBarView> createState() => _FloatingTabBarViewState();
}
class _FloatingTabBarViewState extends State<FloatingTabBarView> {
#override
void initState() {
PermissionsService.checkPermissions(context);
DataProvider.reload();
super.initState();
}
Stream<List> stream = DataProvider.paginatedUsersAndPosts();
return StreamBuilder<List>(
stream: stream,
builder: (context, AsyncSnapshot<List<dynamic>> snapshot) {
...
});
}
#override
Widget build(BuildContext context) {
return floatingTabBarPageView();
}
}
Please take a look at pull_to_refresh package. This allows to implement pull to refresh and incrementally load data. Use the RefreshController to update when the data is refreshed or loaded.
Instantiate RefreshController
Wrap the ListView with SmartRefresher
Implement the onLoading callback to fetch data incrementally
Update the RefreshController on completion or error.
The package has a good usage example, do check it out.
The issue was the way I was creating the stream. Since I made the Stream a function, there was no way for me to call that function to reload. Instead, I moved the code to a static void that I can call from any page, created a StreamController, and used controller.add(data) at the end of the void which allows me to update the stream with the new data.

Flutter: Realtime database appends list when listens changes

I'm using flutter and firebase realtime database.I'm trying to read data from a specific node.I'm saving the data that I am collecting in the Orderlist class and then I return a Future List of Ordelist.This Future function I am trying to use on another widget.I want to display on screen every time data is updated.
Future<List<Orderlist>> order() async{
String business =await businessname();
List table = await tables();
List<Orderlist> list = [];
table.forEach((element) async{
String payment_method = '';
String payment_state ='';
var snapshot = ref.child(business).child(element.toString()).onValue.listen((event) {
event.snapshot.children.forEach((method) {
if(method.key=='payment_method') payment_method=method.value.toString();
if(method.key=='payment_state') payment_state = method.value.toString();
});
final order = Orderlist(payment_method: payment_method,payment_state: payment_state);
list.add(order);
});
});
return list;
}
The problem is that at first place the data are loaded on screen but when I am trying to update the data for some reason the list is appended whereas I just want to replace the previous data with the updated data.To be more specific if I want to listen to 2 nodes to be updated I will have a list with 2 Orderlist items.But the problem is when I update one of them the list is expanded to 3 Orderlist items.
Here is the widget where I am trying to use the Future function
first data loaded Updated da
class TempSettings extends StatefulWidget {
const TempSettings({super.key});
#override
State<TempSettings> createState() => _TempSettingsState();
}
class _TempSettingsState extends State<TempSettings> {
String? business;
List<Orderlist> list=[];
final user = FirebaseAuth.instance.currentUser;
#override
void initState() {
// TODO: implement initState
g();
super.initState();
}
void g() async{
list = await DatabaseManager(user_uid: user!.uid).order();[![[![enter image description here](https://i.stack.imgur.com/hn2NQ.png)](https://i.stack.imgur.com/hn2NQ.png)](https://i.stack.imgur.com/SJ4M1.png)](https://i.stack.imgur.com/SJ4M1.png)
}
#override
Widget build(BuildContext context) {
return Column(children: list.map((e) => ListTile(title: Text(e.payment_method!),)).toList(),);
}
}
When you listen to data in Firebase with onValue, the event you get contains a snapshot of all data. Even when only one child node was added/changed/removed, the snapshot will contain all data. So when you add the data from the snapshot to list, you are initially adding all child nodes once - but then on an update, you're adding most of them again.
The easiest way to fix this is to empty the list every time onValue fires an event by calling list.clear().

Flutter Search bar not refreshing the initial list

i'm having a little trouble in getting back the initial list in my app, after searching an item.
I have a list car and i'm using code from tutorials, if i let the text in the formfield and change tabs, when i back the filter continues and the bar is empty.
I already tried 100 forms and keeps getting the same result.
Here's parts of the code.
List<Car> car = cars;
final _pesquisaController = TextEditingController();
I have a default textformfield with this controller above and a onchange calling the method below.
void pesquisarCarro(String query) {
var carrosPesquisados = {
...car.where(
(cars) {
var nomedoCarro = cars.nomeCarro.toLowerCase();
var input = query.toLowerCase();
return nomedoCarro.contains(input);
},
),
...car.where(
(cars) {
var codigodoCarro = cars.codigo.toLowerCase();
var input = query.toLowerCase();
return codigodoCarro.contains(input);
},
)
}.toList();
setState(
() {
cars = carrosPesquisados;
},
);
}
Already tried putting the list on the initState but not work, can someone give me a light?
Like said, the search filters perfectly, the problem is when i let text in the form and change tabs in the bottomtabbar.
is your problem is initial value always empty?
#override
void initState() {
onInit();
super.initState();
WidgetsBinding.instance.addPostFrameCallback((callback) {
// set your list here
});
}

How can I save widget's data to device in Flutter?

when a file add I want to do an easy way to access that file in my app. to do that I am using ListView.builder() widget. but I can't save the data in the app
I worked on the shared_preferences package. At least I don't know how I must do
I solved my problem with this block, I am using ListviewBuilder to create widgets so I needed an array. after this, I wanted to save this data on my device. I am using shared preferences but shared preferences only allow to add string array. then, I decoded all arrays to String array then, when I start the widget in initstate. I take data then, encode the array data then, add value to my variable.
SharedPreferences? sharedPref;
List<Map<String, dynamic>> documents = [];
List<String> documentsEncode = [];
#override
void initState() {
super.initState();
getInstance();
}
Future<void> getInstance() async {
final sharedPref = await SharedPreferences.getInstance();
documentsEncode = sharedPref.getStringList(widget.collection!) ?? [];
documentsEncodeToDecode();
// main();
}
documentsEncodeToDecode() {
documents.clear();
documentsEncode.forEach((documentEncode) {
Map<String, dynamic> document = jsonDecode(documentEncode);
documents.add(document);
});
setState(() {});
}

flutter FutureBuilder in SearchDelegate

I'm trying to return a FutureBuilder from a SearchDelegate but result shown are not correct.
query seems to be correct and from network I can see all http calls done correctly, so the problem is related to the list data update itself.
In buildSuggestions (of SearchDelegate) a StatefulWidget called 'ResultList' is returned. This widget has for state:
previousQuery - last search term before rerendering
list - list of data returned from a Future
from - first element to show
pageSize - number of elements returned
I need those variables in order to implement infinite scroll so in ResultList build method first of all I check if widget.query has changed from last rendering
if (previousQuery != widget.query) {
setState(() {
from = 0;
list.clear();
previousQuery = widget.query;
});
}
I'm using a ScrollController, so in initState of ResultList when user reach the bottom of the screen i just update "from":
setState(() {
from += pageSize;
});
In FutureBuilder builder method, if snapshot has new data, I append it to list. I should update list in setState but I can't do this inside a builder.
builder: (context, snapshot) {
List<int> ids = [];
List<int> newids = [];
if (snapshot.hasData) {
ids = list.map((item) => item.id as int).toSet().toList();
newids = (snapshot.data.results as List)
.map((item) => item.id as int)
.toSet()
.toList()
.where((id) => !ids.contains(id))
.toList();
if (newids.length != 0) {
setState(() {//can't do this here
list = [
...list,
...(snapshot.data.results as List)
.where((element) => newids.contains(element.id as int))
];
});
}
}
Any hint? Thanks in advance.
when we Use Future Builder Inside of StateFull Widget we Should know On Every SetState
Future Builder call Future Function. and rebuild it self,
so your problem is going to solve if you Remove Future Builder ,
so please change your code to some thing like below...
#override
Widget build(BuildContext context) {
return newids.Empty && ids.Empty ? CircularProgressIndicator() : MyWidget();
}
and Call Your Future in Init State (and When You Want get new Items(next page))