In WinJS the only way to get the count of items in a ListView object is with the method getCount().
But this method is asynchronous.
This make it very difficult to be used in a for loop for example when there is a need to loop through the items of the list.
var listView = document.getElementById("listView").winControl;
listView.itemDataSource.getCount().done(
function (numItems) {
for (var i = 0; i < numItems; i++) {
//do your stuff here
}
});
If I put this in any part of my code I can't return the value I read in the loop from any function because the getCount() return a promise, making my function also return a promise and so on...
So my question is why? Isn't the number of items in a list already known when the method is called?
Have you tried joining promises? If your concern is to iterate all of the items in a ListView by selecting each item by index and then performing some work on them, you can use WinJS.Promise.join to create a single promise that contains the results of all the operations.
For example:
var listView = document.getElementById("listView").winControl;
listView.itemDataSource.getCount().then(
function (numItems) {
var joinedPromises = [];
for (var i = 0; i < numItems; i++) {
joinedPromises.push(listView.itemDataSource.itemsFromIndex(i, 0, 0));
}
return WinJS.Promises.join(joinedPromises);
}).done(
function (results) {
// Operate on each item in the ListView's data source.
},
function (err) {
// Handle any errors from the joined promises.
});
The ListView's data contract allows for asynchronous data sources, and we include a base class VirtualizedDataSource that you can use for fancy scenarios like that. If you are using a WinJS.Binding.List as your data source that API is in fact synchronous and you should be able to say:
listView.itemDataSource.list.length
However, if you're writing generic code that deals with ListView's and doesn't know what kind of data source it will
Related
I have been getting this problem now a few times when I'm coding and I think I just don't understand the way SwiftUI execute the order of the code.
I have a method in my context model that gets data from Firebase that I call in .onAppear. But the method doesn't execute the last line in the method after running the whole for loop.
And when I set breakpoints on different places it seems that the code first is just run through without making the for loop and then it returns to the method again and then does one run of the for loop and then it jumps to some other strange place and then back to the method again...
I guess I just don't get it?
Has it something to do with main/background thread? Can you help me?
Here is my code.
Part of my UI-view that calls the method getTeachersAndCoursesInSchool
VStack {
//Title
Text("Settings")
.font(.title)
Spacer()
NavigationView {
VStack {
NavigationLink {
ManageCourses()
.onAppear {
model.getTeachersAndCoursesInSchool()
}
} label: {
ZStack {
// ...
}
}
}
}
}
Here is the for-loop of my method:
//Get a reference to the teacher list of the school
let teachersInSchool = schoolColl.document("TeacherList")
//Get teacherlist document data
teachersInSchool.getDocument { docSnapshot, docError in
if docError == nil && docSnapshot != nil {
//Create temporary modelArr to append teachermodel to
var tempTeacherAndCoursesInSchoolArr = [TeacherModel]()
//Loop through all FB teachers collections in local array and get their teacherData
for name in teachersInSchoolArr {
//Get reference to each teachers data document and get the document data
schoolColl.document("Teachers").collection(name).document("Teacher data").getDocument {
teacherDataSnapshot, teacherDataError in
//check for error in getting snapshot
if teacherDataError == nil {
//Load teacher data from FB
//check for snapshot is not nil
if let teacherDataSnapshot = teacherDataSnapshot {
do {
//Set local variable to teacher data
let teacherData: TeacherModel = try teacherDataSnapshot.data(as: TeacherModel.self)
//Append teacher to total contentmodel array of teacherdata
tempTeacherAndCoursesInSchoolArr.append(teacherData)
} catch {
//Handle error
}
}
} else {
//TODO: Error in loading data, handle error
}
}
}
//Assign all teacher and their courses to contentmodel data
self.teacherAndCoursesInSchool = tempTeacherAndCoursesInSchoolArr
} else {
//TODO: handle error in fetching teacher Data
}
}
The method assigns data correctly to the tempTeacherAndCoursesInSchoolArr but the method doesn't assign the tempTeacherAndCoursesInSchoolArr to self.teacherAndCoursesInSchool in the last line. Why doesn't it do that?
Most of Firebase's API calls are asynchronous: when you ask Firestore to fetch a document for you, it needs to communicate with the backend, and - even on a fast connection - that will take some time.
To deal with this, you can use two approaches: callbacks and async/await. Both work fine, but you might find that async/await is easier to read. If you're interested in the details, check out my blog post Calling asynchronous Firebase APIs from Swift - Callbacks, Combine, and async/await | Peter Friese.
In your code snippet, you use a completion handler for handling the documents that getDocuments returns once the asynchronous call returns:
schoolColl.document("Teachers").collection(name).document("Teacher data").getDocument { teacherDataSnapshot, teacherDataError in
// ...
}
However, the code for assigning tempTeacherAndCoursesInSchoolArr to self.teacherAndCoursesInSchool is outside of the completion handler, so it will be called before the completion handler is even called.
You can fix this in a couple of ways:
Use Swift's async/await for fetching the data, and then use a Task group (see Paul's excellent article about how they work) to fetch all the teachers' data in parallel, and aggregate them once all the data has been received.
You might also want to consider using a collection group query - it seems like your data is structure in a way that should make this possible.
Generally, iterating over the elements of a collection and performing Firestore queries for each of the elements is considered a bad practice as is drags down the performance of your app, since it will perform N+1 network requests when instead it could just send one single network request (using a collection group query).
This question already has answers here:
How does foreach work when looping through function results?
(4 answers)
Closed 2 years ago.
Sorry if I misexpain in title,
What I want to ask is easy to say via code below;
var userList = context.Where(w=> w.age > 20);
foreach(var item in userList.ToList())
{
...
}
userList.ToList() retrieves 100 rows
I wonder if 'userList.ToList()' fires 100 times, (each iteration and go to database to retrieve records)
or just once and iterate the original collection.
It seems weird but now super important for me since my colleague claim it knock to db 100 times and I believe it does once..
It will call database one time, you even don't need ToList there. The easiest way to check it - just use database profiler and see queries which will hit it. foreach is translated by compiler into something like that:
IEnumerator<EntityType> enumerator = queryable.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
EntityType current = enumerator.Current;
}
}
finally
{
if (enumerator != null)
{
enumerator.Dispose();
}
}
So you are working with the same Enumerator and it does not makes sense to call db multiple times.
Also if your colleague statement about userList.ToList() being fired 100 times the next should have written to console multiple times:
IEnumerable<int> GetEn()
{
Console.WriteLine(nameof(GetEn));
foreach (var element in new[] {10,20,30})
{
yield return element;
}
}
var userList = GetEn().Where(w => w > 20);
foreach (var item in userList.ToList())
{
}
I need to update a collection in values like this :
{
"email" : "x#gmail.com",
"fullName" : "Mehr",
"locations" : ["sss","dsds","adsdsd"]
}
Locations needs to be an array. in firebase how can I do that ... and also it should check duplicated.
I did like this :
const locations=[]
locations.push(id)
firebase.database().ref(`/users/ + ${userId}`).push({ locations })
Since you need to check for duplicates, you'll need to first read the value of the array, and then update it. In the Firebase Realtime Database that combination can is done through a transaction. You can run the transaction on the locations node itself here:
var locationsRef = firebase.database().ref(`/users/${userId}/locations`);
var newLocation = "xyz";
locationsRef.transaction(function(locations) {
if (locations) {
if (locations.indexOf(newLocation) === -1) {
locations.push(newLocation);
}
}
return locations;
});
As you can see, this loads the locations, ensures the new location is present once, and then writes it back to the database.
Note that Firebase recommends using arrays for set-like data structures such as this. Consider using the more direct mapping of a mathematical set to JavaScript:
"locations" : {
"sss": true,
"dsds": true,
"adsdsd": true
}
One advantage of this structure is that adding a new value is an idempotent operation. Say that we have a location "sss". We add that to the location with:
locations["sss"] = true;
Now there are two options:
"sss" was not yet in the node, in which case this operation adds it.
"sss" was already in the node, in which case this operation does nothing.
For more on this, see best practices for arrays in Firebase.
you can simply push the items in a loop:
if(locations.length > 0) {
var ref = firebase.database().ref(`/users/ + ${userId}`).child('locations');
for(i=0; i < locations.length; i++) {
ref.push(locations[i]);
}
}
this also creates unique keys for the items, instead of a numerical index (which tends to change).
You can use update rather than push method. It would much easier for you. Try it like below
var locationsObj={};
if(locations.length > 0) {
for(i=0; i < locations.length; i++) {
var key= firebase.database().ref(`/users/ + ${userId}`).child('locations').push().key;
locationsObj[`/users/ + ${userId}` +'/locations/' + key] =locations[i];
}
firebase.database().ref().update(locationsObj).then(function(){// which return the promise.
console.log("successfully updated");
})
}
Note : update method is used to update multiple paths at a same time. which will be helpful in this case, but if you use push in the loop then you have to wait for the all the push to return the promises. In the update method it will take care of the all promises and returns at once. Either you get success or error.
In Meteor, I am publishing a collection from a non-Mongo source (IMAP specifically).
Meteor.publish("search_results", function(user, password, str) {
var self = this;
res_msg = [];
Imap.connect({... });
Imap.search(str, resultcb);
for (var i = 0; i < res_msg.length; i++) {
self.set("s_results", Meteor.uuid(), {uid: res_msg[i].uid, date: res_msg[i].date, headers:res_msg[i].headers});
}
self.flush();
self.complete();
self.flush();
console.log("total messages : ", res_msg.length);
});
This works fine. However, on the second pass though, new items are appended to the collection. There does not appear to be a way to remove records from a non-Mongo collection.
It seems from the documentation that if I use this.unset, it will change attributes, not remove the record(s).
I can't use collection.remove({}) either client or server side.
I found a really ugly way to do this, so I'm leaving the question open in the hopes that there is a better alternative.
Basically, if you unset all the attributes, the document goes away. The question is how to iterate over the collection within the publish method to find all documents so attributes can be unset. Creating a collection doesn't seem to work, let alone .find();
I stored the list of ids in a separate array. Ugly, I know. I hope you can do better.
for (i = 0; i < uuids.length; i++) {
self.unset("s_results", uuids[i], {});
}
uuids = [];
Imap.search(str, resultcb);
for (var i = 0; i < res_msg.length; i++) {
var u = Meteor.uuid();
self.set("s_results", u, {uid: res_msg[i].uid, date: res_msg[i].date, headers:res_msg[i].headers});
uuids.push(u);
}
In as3, I am adding event listener and then attaching the anonymous function to it:
myBox.addEventListener(MouseEvent.ROLL_OVER,
function(e:MouseEvent):void
{
Alert.show(count, 'Alert Box');
);
Now this whole piece of code is looped n times. Now, I have n myBoxes and whenever I roll my mouse over the box, it should alert the name. But, what I see is that the last value of count is used by each box.
How can I pass parameter or value to the anonymous function? ( as roll over , I believe, expects only one variable)
You need to create a new scope by executing a function:
for (var i:int = 0; i<10; i++)
{
(function(count:int):void
{
myBox.addEventListener(MouseEvent.ROLL_OVER,
function(e:MouseEvent):void { Alert.show(count, 'Alert Box'); });
})(i);
}
Rather than relying on an index, wouldn't it be simpler (and better) to get the currentTarget of the event and get the value of one of its members?
myBox.addEventListener(MouseEvent.ROLL_OVER,
function(e:MouseEvent):void
{
Alert.show(UIComponent(e.currentTarget).name, 'Alert Box');
);
If you absolutely have to reference the index, you could get that by
UIComponent(e.currentTarget).parent.getChildIndex(e.currentTarget)
And, now that I think of it, you don't even have to make this an anonymous function at all if you use the event model.