Why for loop is not working in ionic ionViewWillEnter method - firebase

I am calling a method "allComments()" in ionViewWillEnter() method, but it is not not binding name and profile every time I am entering in view. Not able to find any valid reason. There's no error in the code
allComments() {
this.comments=[];
let arr=[];
this.com=[];
console.log(this.challengeId)
this.https.get('https://dareost.firebaseio.com/comments/'
+this.challengeId+'.json').map(res=>res.json()).subscribe(x=>{
this.com=x
for(let key in this.com) {
this.comments.push(this.com[key])
}
this.length = this.comments.length;
for(let i=0;i<this.comments.length;i++){
let a=this.comments[i]
console.log(a)
for(let key in a){
arr.push(a[key])
this.arr.push(a[key])
}
}
for(let i=0;i<this.arr.length;i++){
for(let j=0;j<this.users.length;j++){
console.log(this.arr[i].commentedBy)
if(this.arr[i].commentedBy == this.users[j].uid){
console.log(this.users[j].name1)
this.arr[i].name=this.users[j].name1
console.log(this.arr[i])
this.arr[i].profile=this.users[j].profileUrl
}
}
}
It is sometimes not printing this.arr[i].name on console and sometime it gets print.

ionViewWillEnter() gets for a less time and heavy loops and heavy processes cannot get process during this time. It totally depends on the device's RAM. A device having good processing speed will not load heavy data while the device with slow processing speed can process heavy data.

Related

I don't understand the order that code executes when calling onAppear

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).

Why does Future.timeout not work in dart?

I want to run a computation that might take too long. If it doesn't finish in a specific time, abort the computation and return a different value. For this problem I found Future.timeout, it would almost do the same thing that I want, except it doesn't work for this code.
Future<String> timeoutTest() async
{
return await longComputation().timeout(
Duration(milliseconds: 10),
onTimeout: () => "Took too long"
);
}
Future<String> longComputation() async
{
int startTime = DateTime.now().millisecondsSinceEpoch;
Rational n = Rational.one;
for(int i = 1; i < 2000; i++)
{
n *= Rational.fromInt(i);
}
String result = n.toDecimalString();
print("Time took: ${DateTime.now().millisecondsSinceEpoch - startTime} ms");
return result;
}
When I call print(await timeoutTest()) I either expect a string of digits that took maximum 10ms to calculate OR the "Took too long" string if it took more than 10ms. But I get the string of digits, and the message in the console: "Time took: 877 ms". So the timeout didn't work.
If I fake the computation with Future.delay, the timeout works as expected. It returns a different value, because the longComputation took at least 100ms. (I still get the message in the console: "Time took: 103ms", but this is not the main problem.)
Future<String> longComputation() async
{
int startTime = DateTime.now().millisecondsSinceEpoch;
String result = await Future.delayed(
Duration(milliseconds: 100),
() => "Fake computation result"
);
print("Time took: ${DateTime.now().millisecondsSinceEpoch - startTime} ms");
return result;
}
I'm assuming I messed up something in the longComputation, but what? There were no un-awaited Futures.
This behavior can be confusing but it is important to remember that your Dart code are only executed in a single thread (unless you are using isolates).
The problem is that the logic behind .timeout needs to run in the same single thread and Dart can't just stop the execution of your own code. So if you are running a CPU intensive calculation without any pauses you are stopping the Dart VM from running any other events on the event queue.
What the implementation of .timeout actually does, is creating an internal Timer which are going to be triggered in the future unless you get a result before the timeout value. This Timer event are going on top on the event queue like any other event in the Dart VM.
But in your first example, we are actually never going to execute any other event on the event queue before you are giving the result. So from the Future's point of view, you are returning a result before the deadline.
This is going to look like .timeout is kind of pointless but what it really are for is when you are making some IO operations like API requests where the Dart VM are actually waiting for some answer.
If you are going to use it for heavy calculations, you can either spawn an Isolate so your main isolate instance can wait on the other isolate. Alternative, you can insert some pauses in your calculation which makes space for the Dart VM to execute other events on the event queue. An example could be inserting await Future<void>(() => null); which are going to spawn a new event on top of the event queue. When we wait for all events on the queue to be executed before our own empty calculation.
It would then also make sense to add some logic so your own code can see if the timeout value has been reached so you can stop the calculation if that is the case.
You can't actually cancel Futures. At best you could have your .timeout callback set a flag that longComputation periodically checks to become a no-op as soon as possible.
Also see Future.timeout documention is misleading.

Xamarin Offline Sync with AzureMobileServices: Initial offline load incredibly slow

I'm successfully using Azure Mobile Services and Xamarin Forms to perform CRUD operations on an SQL DB hosted with Azure. The offline sync portion stores the data in an SQLite db on the phone. There's been a few bumps along the way to get it working as smoothly as we have it now, but this remains to be the last hurdle.
Problem
When the device has no connection (tested using Airplane mode on a variety of physical and emulated devices) - the first time it goes to access any of the offline data, it takes a very long time to return anything. This is the case if the data exists in the SQLite DB or not.
There is no exception thrown, or anything that I can see printed to the logs that indicates what the delay might be.
To give an idea, a PullAsync() on 20 rows might take 5 seconds while online, and that data is stored to the SQLite DB. After putting the device into offline mode, that same operation may take up to 60 seconds. These numbers are quite arbitrary, but the delay is noticeably much too long.
To add to this, this long load only occurs the very first time any Offline Sync method is called. After that, every method is near instant, as I would expect it to be - but why not the first time?
Expected Result
I would expect that because the data is stored on the device already, and no internet connection can be detected, it should return the data almost instantly.
Code
Sync Class
The GetPolicies() method is where the delay would occur.
This is a sample of one of the components. Every other component is the same format, but different data.
IMobileServiceSyncTable<policy_procedure> policyTable = SyncController.policyTable;
public async Task<List<policy_procedure>> GetPolicies(string companyId)
{
//SemaphoreSlim
await SyncController.dbOperation.WaitAsync();
try
{
await SyncController.Initialize();
await policyTable.PullAsync("policy_procedure", policyTable.Where(p => p.fk_company_id == companyId).Where(p=> p.signature!=null || p.signature!=""));
return await policyTable.ToListAsync();
}
catch (Exception ex)
{
//For some reason, when this method is called and the device is offline, it will fall into this catch block.
//I assume this is standard for offline sync, as it's trying to do a pull with no connection, causing it to fail.
//Through using breakpoints, the delay occurs even before it reaches this catch statement.
Console.WriteLine(ex);
return await policyTable.ToListAsync();
}
finally
{
SyncController.dbOperation.Release();
}
}
Sync Controller
public static SemaphoreSlim dbOperation = new SemaphoreSlim(1, 1);
public static MobileServiceClient client;
public static MobileServiceSQLiteStore store;
public static async Task Initialize()
{
try
{
//This line is not standard for Offline Sync.
//The plugin returns true or false for the devices current connectivity.
//It's my attempt to see if there is a connection, to eliminate the load time.
//This does immediately take it back to the try statement in GetPolicies
if (!CrossConnectivity.Current.IsConnected)
return;
if (client ? .SyncContext ? .IsInitialized ? ? false)
return;
client = new MobileServiceClient(AppSettings.azureUrl);
var path = "local.db"; //Normally uses company ID,
path = Path.Combine(MobileServiceClient.DefaultDatabasePath, path);
store = new MobileServiceSQLiteStore(path);
/************************/
#
region Table Definitions in local SQLite DB
//Define all the tables in the sqlite db
..
store.DefineTable < policy_procedure > ();
..#endregion
await client.SyncContext.InitializeAsync(store);
/************/
#
region Offline Sync Tables
..
policyTable = client.GetSyncTable < policy_procedure > ();
..#endregion
}
catch (Exception ex)
{
Console.WriteLine(ex)
}
}
What I've Tried
Well, I'm not too sure what's even causing this, so most of my attempts have been around forcing an exception before this wait time occurs, so that it can fall out of the GetPolicies try-catch, as the wait time appears to be on the PullAsync.
My most recent attempt at this is commented in the code above (SyncController), where I use James Montemagno's Connectivity Plugin to detect the phones network connectivity. (I've tested this separately, and this works correctly without delay.)
The short story is that you don't want to call PullAsync in your GetPolicies method if your device is offline. For example, you could do
try
{
await SyncController.Initialize();
if (CrossConnectivity.Current.IsConnected)
{
await policyTable.PullAsync("policy_procedure", policyTable.Where(p => p.fk_company_id == companyId).Where(p=> p.signature!=null || p.signature!=""));
}
return await policyTable.ToListAsync();
}
but you will also want to handle the case where this is the first time the app runs and so you don't have any records yet.

Meteor method got triggered multiple times

I have a fairly simple Meteor application.
I tried to send newsletter to about 3000 users in my list and things went wrong. A random set of users got multiple emails (between 41 to 1).
I shut the server down as soon I noticed this behavior. around 1300 emails were sent to 210 users. I am trying to figure out what happened and why.
Here is the code flow:
SendNow (client clode) --> SendNow (server method) --> populateQue (server function) --> processQue(server function) --> sendEmails (server method)
Client side code :
'click .sendNow': function(){
/* code that forms data object */
Meteor.call('sendNow',data);
}
Server code : server/method.js
Meteor.methods({
'sendNow' : function(data){
if(userWithPermission()){
var done = populateQue(data);
if(done)
processQue();
return {'method':'sendNow','status':'ok'}
},
'sendEmails': function(data){
this.unblock();
var result = Mandrill.messages('send', data);// using external library
SentEmails.insert(data);//Save sent emails in a collection
}
});
Function on server : server/utils.js
populateQue = function(data) {
/* code to get all users in to array */
MessageQue.remove();//Remove all documents from the Que
for (var i=0; i<users.length; i++) {
MessageQue.insert({userId: users[i]._id});
}
return true;
}
processQue = function(){
var messageQue = MessageQue.find({}).fetch();
for(i=0; i < messageQue.length; i++){
Meteor.call('sendEmails', data);
MessageQue.remove({_id: messageQue[i]._id});//Remove sent emails from the Que
}
}
My first hunch was MessageQue got messed up as I am removing items while processQue is using it but i was wrong. I am unable to simulate this behavior again after few tests
Test 1 : replaced Mandrill.message('send',data) with Meteor._sleepForMs(1000); - Only one email/person was seen in SentEmails collection.
Test 2 : Put Mandrill in Test mode (had to use different API key) and re ran the code with couple of log statements. - Only one email/person was seen in SentEMails and also in Mandrill's interface.
It's definitely not external library. its somewhere in my code or in the way I understood meteor to work.
Only one thing I noticed is an error occurred while accessing SentEmails collection through another view code. I have a view that displays SentEmails on the client with date as filter.
Here is the error :
Exception from sub sentEmailDocs id 9LTq6mMD4xNcre4YX Error:
Exception while polling query
{
"collectionName":"sent_emails",
"selector":{"date":{"$gt":"2015-07-09T05:00:00.000Z","$lt":"2015-07-11T05:00:00.000Z"}},
"options":{"transform":null,"sort":{"date":-1}}
}:
Runner error: Overflow sort stage buffered data usage of 33565660 bytes exceeds internal limit of 33554432 bytes
Is this the smoking gun? Would this have caused the random behavior?
I have put couple checks to prevent this from happening but I am puzzled on what might have caused and why? I will be happy to provide more information. Thanks in advance to who ever is willing to spend few mins on this.
Shot in the dark here, but the remove method takes an object, otherwise it doesn't do anything. MessageQue.remove() probably didn't clear the queue. You need MessageQue.remove({}). Test the theory by doing an if (MessageQue.find().count() > 0)... after the remove.
If you're set on having a separate collection for the queue, and I'm not saying that's a bad thing, I'd set the _id to be the userId. That way you can't possibly send someone the same message twice.

Implementing exception handling in a function which returns a Stream

I'm implementing a function which returns a Stream. I'm not sure how to implement the error handling, what is best practice?
For functions which return a Future, it's best practice never to throw a synchronous error. Is this also true for functions which return a Stream?
Here's an example of what I'm thinking:
Stream<int> count() {
var controller = new StreamController<int>();
int i = 0;
try {
doSomethingThatMightThrow();
new Timer.repeating(new Duration(seconds: 1), () => controller.add(i++));
} on Exception catch (e) {
controller.addError(e);
controller.close();
}
return controller.stream;
}
In general it is true for Streams as well. The main idea is, that users should only need to handle errors in one way. Your example moves all errors to the stream.
There are circumstances where immediate errors are better (for instance you could make the error is due to a programming error and should never be handled anyways, or if you want to guarantee that a Stream never produces errors), but sending the error through a stream is almost always a good thing.
Small nit: a Stream should usually (there are exceptions) not produce any data until somebody has started listening. In your example you are starting a Timer even though you don't even know if there will ever be a listener. I'm guessing the example is reduced and not representative of your real code, but it is something to look out for. The solution would be to use the StreamController's callbacks for pause and subscription changes.
I've updated the example to take on-board Florian's comments.
In my real use case, I don't ever want to buffer the results, so I'm throwing an UnsupportedError if the stream is paused.
I've made it a terminating stream, rather than an infinite one.
If the user of this function adds a listener asynchronously after a few seconds, then they will lose the first couple of results. They shouldn't do this. I guess that's something to document clearly. Though perhaps, I could also throw an error if the subscribe state changes after the first data has been received, but before a close has been received.
Stream<int> count(int max) {
var controller = new StreamController<int>(
onPauseStateChange: () => throw new UnsupportedError('count() Stream pausing not supported.'));
int i = 0;
try {
doSomethingThatMightThrow();
new Timer.repeating(new Duration(seconds: 1), () {
if (!controller.hasSubscribers)
return;
controller.add(i++);
if (i >= max)
controller.close();
});
} on Exception catch (e) {
controller.addError(e);
controller.close();
}
return controller.stream;
}

Resources