Flutter list stores values using for loop, but loses these values when used outside the loop - flutter

I am trying to read data from a firebase real time database and store it in a list to use in my flutter app.
As seen in the code below, I start by creating a reference to the database. I also create some global variables, where "itemName" stores the name of the item in the database, "itemID" stores the id of each item in the database and "itemNames" is a list of all the item names in the database.
The "activate listeners" method listens to the database, and returns any values if they are changed. Each item ID starts with a J, and continues onto J1, J2, J3 etc. Hence I am using a for loop to access all the item IDs.
The issue I am having is that the itemNames are successfully being stored in the itemNames list, and can be see when I print the list within the for loop (The first print line).
However, when I try print the list value OUTSIDE the for loop, it prints an empty list for loop (second print line).
So in other words, the list is not retaining the elements added to it during the for loop.
Any help would be much appreciated!
final DatabaseReference _dbRef = FirebaseDatabase.instance.ref();
late StreamSubscription _dailySpecialStream;
//Stores the description of each menu item in the DB
String itemName = "";
String itemID = "";
List<String> itemNames = [];
//"Listens for any changes being made to the DB, and updates our app in real time"
void _activateListeners() {
for (int i = 0; i <= 10; i++) {
itemID = "J$i";
_dailySpecialStream =
_dbRef.child("menuItem/$itemID/itemName").onValue.listen((event) {
itemName = event.snapshot.value.toString();
itemNames.addAll([itemName]);
print(itemNames);
});
}
print(itemNames);
}

That is the expected behavior. Data is loaded from Firebase (and most modern cloud APIs) asynchronously, and while that is happening your main code continues to run.
You can most easily see this by placing some print statements:
print('before starting to load data');
for (int i = 0; i <= 10; i++) {
itemID = "J$i";
_dailySpecialStream =
_dbRef.child("menuItem/$itemID/itemName").onValue.listen((event) {
print('loaded data: %i');
});
}
print('after starting to load data');
If you run this, you'll see something like:
before starting to load data
after starting to load data
loaded data: 0
loaded data: 1
loaded data: 2
loaded data: 3
...
So as you can see the after print statement that is lowest in your code, actually printed before any of the data was loaded. This is probably not what you expected, but explains perfectly why the print statement you had outside the loop doesn't print the data: it hasn't been loaded yet!
The solution for this type of problem is always the same: you have to make sure the code that requires the data is inside the callback, or it is called from there, or it is otherwise synchronized.
A simple way to do the latter is by using get() instead of onValue, and then use await on the Future that is returns:
print('before starting to load data');
for (int i = 0; i <= 10; i++) {
itemID = "J$i";
_dailySpecial = await _dbRef.child("menuItem/$itemID/itemName").get();
print('loaded data %i: ${_dailySpecial.value}');
}
print('after starting to load data');
Now with this, the print statements will be in the order you expected.

Related

Flutter : How to Map values dynmically into list and iterate to its length and how can i do Update and Delete after maping values

I am trying to build json expected output as below through dart programing for my application, I have mapped data to list successfully. But when I trying to delete / add the list, the elements in the list are not getting updated accordingly instead they are hgetting reapeted same data.
Here is my code implimentation
in this set state i am getting required values like Phone, name, email e.t.c
for (int i = 0; i <= selectedContacts.length - 1; i++) {
SimplifiedContact? contact = selectedContacts.elementAt(i);
CreateContestModel(//<-- from here i am getting required values.
contact.phone,
contact.name,
contact.email,
);
}
in below code i am, mapping data and building json
class CreateContestModel {
static List models = [];
String phone = '';
String name = '';
String email;
CreateContestModel(this.phone, this.name, this.email) {
var invitemap = {
'name': name,
'phone': phone,
'email': email,
};
models.add(invitemap);
print(models);
}
}
Output
{
"invitations":[
{
"name":"Acevedo Castro",
"phone":982-475-2009,
"email":"floresjoyner#digifad.com"
},
{
"name":"Acevedo Castro",
"phone":982-475-2009,
"email":"floresjoyner#digifad.com"
},
{
"name":"Abby Webster",
"phone":888-561-2141,
"email":"howardnoel#perkle.com"
},
{
"name":"Abby Webster",
"phone":888-561-2141,
"email":"howardnoel#perkle.com"
},
{
"name":"Abby Webster",
"phone":888-561-2141,
"email":"howardnoel#perkle.com"
}
]
}
As you see above items are not getting updated, but they are getting added more.
Expected Output
{
"invitations":[
{
"name":"Acevedo Castro",
"phone":"982-475-2009",
"email":"floresjoyner#digifad.com"
},
{
"name":"Abby Webster",
"phone":"888-561-2141",
"email":"howardnoel#perkle.com"
}
]
}
That is some seriously flawed program flow. Your object creation as a side effect at the same time fills a static list. And it seems you call your object creation every build. So you would insert into your list whenver the user flips it's phone or drags his browser window.
I'm not entirely sure what you want, but you need state management in your application. There are different ways to do it, you can pick the one you like best from the Flutter documentation on state management.
This is a huge flow issue, You should never do data manipulation in build() function as in flutter build function is called multiple times so the manipulation code will also get called multiple times.
So, the proper way to manipulate data is before the data is being used so make sure that the data is only manipulated in initstate(). In Your case you are also doing something which is not required. You are trying to add data to a list via a constructor to a static list so it will always add the data whenever you call it, This is not a proper way.
class CreateContestModel {
late String phone = '';
late String name = '';
late String email;
CreateContestModel.fromMap(Map<String, String> json) {
phone = json['phone'] ?? '';
phone = json['name'] ?? '';
phone = json['email'] ?? '';
}
}
This is how you should create your class. And always create functions for manipulation if possible.
List<CreateContestModel> createContestModelList =
testData['invitations']!.map(
(data) {
return CreateContestModel.fromMap(data);
},
).toList();
Then use this code to construct your list in initstate() and manipulate this having a static variable in your stateful widget. Make Sure you do not construct the data on build() function.
Need More Help?? here's the Gist link
As per my question i find some work around, i.e., by clear up stack on selecting or updtaing existing contacts because as explained below
in this for loop i am itrating same values on each time, when i hit on submit button
for (int i = 0; i <= selectedContacts.length - 1; i++) { //<--- for every time same elements will be itrated
SimplifiedContact? contact = selectedContacts.elementAt(i);
CreateContestModel(//<-- from here i am getting required values.
contact.phone,
contact.name,
contact.email,
);
}
so the list of sets are not updating as per the displayed list as mentioned in question, so i done some work around in below code while selecting / updating contacts, because my model is static list
void onContactBtnPress(BuildContext context) async { CreateContestModel.models.clear(); //<-- clearing up stack
}

Flutter - different between the real value and the displayed value

I am facing very strange problem.
I want to implement the option to remove rows from DataTable, and therefore I implemented the following method:
onRemoveRow() {
setState(
() {
lastRowIndex -= selectedGeneLists.length;
geneLists.removeWhere((element) => selectedGeneLists.contains(element));
for (int i = 0; i < geneLists.length; i++) {
GenesListObjIndexed genesListObjIndexed = geneLists[i];
genesListObjIndexed.index = i;
}
selectedGeneLists = [];
},
);
}
This function should modify the list that store the table's data, and the expectation is that when I delete the items from the list the items will be deleted from the table.
But you can see here the following problem (the selected line isn't been removed):
The strange this is when I debug and check the value of the list it's look great and as expected, so what can be the problem?
Use the key property to uniquely identify each row and delete the row with the key value.

get value for specific question/item in a Google Form using Google App Script in an on submit event

I have figured out how to run a Google App Script project/function on a form submit using the information at https://developers.google.com/apps-script/guides/triggers/events#form-submit_4.
Once I have e I can call e.response to get a FormResponse object and then call getItemResponses() to get an array of all of the responses.
Without iterating through the array and checking each one, is there a way to find the ItemResponse for a specific question?
I see getResponseForItem(item) but it looks like I have to somehow create an Item first?
Can I some how use e.source to get the Form object and then find the Item by question, without iterating through all of them, so I could get the Item object I can use with getResponseForItem(item)?
This is the code I use to pull the current set of answers into a object, so the most current response for the question Your Name becomes form.yourName which I found to be the easiest way to find responses by question:
function objectifyForm() {
//Makes the form info into an object
var myform = FormApp.getActiveForm();
var formResponses = myform.getResponses()
var currentResponse = formResponses[formResponses.length-1];
var responseArray = currentResponse.getItemResponses()
var form = {};
form.user = currentResponse.getRespondentEmail(); //requires collect email addresses to be turned on or is undefined.
form.timestamp = currentResponse.getTimestamp();
form.formName = myform.getTitle();
for (var i = 0; i < responseArray.length; i++){
var response = responseArray[i].getResponse();
var item = responseArray[i].getItem().getTitle();
var item = camelize(item);
form[item] = response;
}
return form;
}
function camelize(str) {
str = str.replace(/[\.,-\/#!$%\^&\*;:{}=\-_`~()#\+\?><\[\]\+]/g, '')
return str.replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, function(match, index) {
if (+match === 0) return ""; // or if (/\s+/.test(match)) for white spaces
return index == 0 ? match.toLowerCase() : match.toUpperCase();
});
}
//Use with installable trigger
function onSubmittedForm() {
var form = objectifyForm();
Logger.log(form);
//Put Code here
}
A couple of important things.
If you change the question on the form, you will need to update your
code
Non required questions may or may not have answers, so check if answer exists before you use it
I only use installable triggers, so I know it works with those. Not sure about with simple triggers
You can see the form object by opening the logs, which is useful for finding the object names

Persisting a Modified Database in Chrome APP using chrome.storage API

I am using SQL.js for SQLite in my chrome app , I am loading external db file to perform query , now i want to save my changes to local storage to make it persistent , it is already define here-
https://github.com/kripken/sql.js/wiki/Persisting-a-Modified-Database
i am using the same way as defined in the article-
function toBinString (arr) {
var uarr = new Uint8Array(arr);
var strings = [], chunksize = 0xffff;
// There is a maximum stack size. We cannot call String.fromCharCode with as many arguments as we want
for (var i=0; i*chunksize < uarr.length; i++){
strings.push(String.fromCharCode.apply(null, uarr.subarray(i*chunksize, (i+1)*chunksize)));
}
return strings.join('');
}
function toBinArray (str) {
var l = str.length,
arr = new Uint8Array(l);
for (var i=0; i<l; i++) arr[i] = str.charCodeAt(i);
return arr;
}
save data to storage -
var data =toBinString(db.export());
chrome.storage.local.set({"localDB":data});
and to get data from storage-
chrome.storage.local.get('localDB', function(res) {
var data = toBinArray(res.localDB);
//sample example usage
db = new SQL.Database(data);
var result = db.exec("SELECT * FROM user");
});
Now when i make a query , i am getting this error -
Error: file is encrypted or is not a database
is there any differnces for storing values in chrome.storage and localStorage ? because its working fine using localStorage, find the working example here-
http://kripken.github.io/sql.js/examples/persistent.html
as document sugggested here -
https://developer.chrome.com/apps/storage
we don't need to use stringify and parse in chrome.storage API unlike localStorage, we can directly saves object and array.
when i try to save result return from db.export without any conversion, i am getting this error-
Cannot serialize value to JSON
So please help me guys what will be the approach to save db export in chrome's storage, is there anything i am doing wrong?

JSTree creating duplicate nodes when loading data with create_node

I'm having an issue when I'm trying to load my initial data for JSTree; I have 2 top level nodes attached to the root node but when I load them it looks like the last node added is being duplicated within JSTree. At first it looked as if it was my fault for not specifically declaring a new object each time but I've fixed that. I'm using .net MVC so the initial data is coming from the model that is passed to my view (that is the data passed into the data parameter of the method).
this.loadInitialData = function (data) {
var tree = self.getTree();
for (var i = 0; i < data.length; i++) {
var node = new Object();
node.id = data[i].Id;
node.parent = data[i].Parent;
node.text = data[i].Text;
node.state = {
opened: data[i].State.Opened,
disabled: data[i].State.Disabled,
selected: data[i].State.Selected
};
node.li_attr = { "node-type": data[i].NodeType };
node.children = [];
for (var j = 0; j < data[i].Children.length; j++) {
var childNode = new Object();
childNode.id = data[i].Children[j].Id;
childNode.parent = data[i].Children[j].Parent;
childNode.text = data[i].Children[j].Text;
childNode.li_attr = { "node-type": data[i].Children[j].NodeType };
childNode.children = data[i].Children[j].HasChildren;
node.children.push(childNode);
}
tree.create_node("#", node, "last");
}
}
My initial code was declaring node like the following:
var node = {
id: data[i].Id
}
I figured that was the cause of what I'm seeing but fixing it has not changed anything. Here is what is happening when I run the application; on the first pass of the method everything looks like it is working just fine.
But after the loop is run for the second (and last) time here is the final result.
It looks like the node objects are just a copy of each other, but when I run the code through the debugger I see the object being initialized each time. Does anyone have an idea what would cause this behavior in JSTree? Should I be using a different method to create my initial nodes besides create_node?
Thanks in advance.
I found the issue; I didn't realize but I was setting my id property to the same id for both node groups. After I fixed it everything started working as expected.