f-miyu / Plugin.CloudFirestore

MIT License
121 stars 44 forks source link

System.ArgumentException: 'Handle must be valid. Parameter name: instance' #63

Closed CursedMun closed 3 years ago

CursedMun commented 3 years ago

So the problem is when I execute that part of the code with await before the queries it woks properly but at the last document it throws that error System.ArgumentException: 'Handle must be valid. Parameter name: instance' and closes

var query = CrossCloudFirestore.Current.Instance.Collection("FichaLinhas").WhereEqualsTo("numeroFicha", this.dataManager.Documents.Last().Numero).GetAsync(); this.dataManager.Documents.Last().ListOfDocLines = query.Result.ToObjects().ToList(); query = CrossCloudFirestore.Current.Instance.Collection("FichaServicos").WhereEqualsTo("numeroFicha", this.dataManager.Documents.Last().Numero).GetAsync(); this.dataManager.Documents.Last().ListOfDocServices = query.Result.ToObjects().ToList();

If I execute the await before the queries it just freezes at the query and doesn't go further If I execute without that part of the code it works

Whole Code ->

 await Task.Run(new Action(() =>
  {
    MainThread.BeginInvokeOnMainThread(() =>
    {
      progress.Show();
    });
    try
    {
      if (dataManager.ThirdParties.Count <= 0)
      {
        var query = CrossCloudFirestore.Current
        .Instance
        .Collection(GlobalVars.GeneralConfig.ImportFactock ? "TerceirosFactock" : "Terceiros")
        .GetAsync();
        dataManager.ThirdParties = query.Result.ToObjects<ThirdParty>().ToList();
      }

      CrossCloudFirestore.Current
 .Instance
 .Collection("Ficha")
 .WhereEqualsTo("UtilizadorAtribuido", GlobalVars.USER_INFO.Name)
 .WhereEqualsTo("FichaFechada", false)
 .OrderBy("id", false)
 .AddSnapshotListener(async (snapshot, error) =>
 {
   if (snapshot != null)
   {
     foreach (var documentChange in snapshot.DocumentChanges)
     {
       switch (documentChange.Type)
       {
         case DocumentChangeType.Added:
           //Document added
           this.dataManager.Documents.Add(documentChange.Document.ToObject<Document>());
           //Small assigment things to get all showing properly cuz of mssql DB
           this.dataManager.Documents.Last().Id = documentChange.Document.Id;
           //Adding DocLines to Document to have it all in memory and dont need to get it again.
           var query = await CrossCloudFirestore.Current.Instance.Collection("FichaLinhas").WhereEqualsTo("numeroFicha", this.dataManager.Documents.Last().Numero).GetAsync();
           this.dataManager.Documents.Last().ListOfDocLines = query.Result.ToObjects<DocumentLines>().ToList();
           //Adding DocService to Document ...
           query = await CrossCloudFirestore.Current.Instance.Collection("FichaServicos").WhereEqualsTo("numeroFicha", this.dataManager.Documents.Last().Numero).GetAsync();
           this.dataManager.Documents.Last().ListOfDocServices = query.Result.ToObjects<DocumentService>().ToList();
           //Notifying Recycleviewer of changes
           dataAdapter.NotifyDataSetChanged();

           break;

         case DocumentChangeType.Modified:
           break;

         case DocumentChangeType.Removed:
           // Document Removed
           var docToRemove = documentChange.Document.Id;
           if (dataManager.Documents.Exists(x => x.Id == docToRemove))
           {
             dataManager.Documents.Remove(dataManager.Documents.Where(x => x.Id == docToRemove).First());
             dataAdapter.NotifyDataSetChanged();
           }
           break;
       }
     }
   }
   MainThread.BeginInvokeOnMainThread(() =>
   {
     progress.Dismiss();
   });
 });
f-miyu commented 3 years ago

It seems the same as #56.

Probably, the error will go away with ToList() at foreach.

foreach (var documentChange in snapshot.DocumentChanges.ToList())
{
    ...
}

However, you should not use Result because the main thread may be locked and use await instead.

var fichaLinhasSnapshot = await CrossCloudFirestore.Current.Instance
    .Collection("FichaLinhas")
    .WhereEqualsTo("numeroFicha", document.Numero)
    .GetAsync();

this.dataManager.Documents.Last().ListOfDocLines = fichaLinhasSnapshot.ToObjects<DocumentLines>().ToList();

var fichaServicosSnapshot = await CrossCloudFirestore.Current.Instance
    .Collection("FichaServicos")
    .WhereEqualsTo("numeroFicha", document.Numero)
    .GetAsync();

this.dataManager.Documents.Last().ListOfDocServices = fichaServicosSnapshot.ToObjects<DocumentService>().ToList();

Otherwise, how about trying the following because I think there is no need to get FichaLinhas or FichaServicos sequentially and call NotifyDataSetChanged() in every loop.

CrossCloudFirestore.Current
    .Instance
    .Collection("Ficha")
    .WhereEqualsTo("UtilizadorAtribuido", GlobalVars.USER_INFO.Name)
    .WhereEqualsTo("FichaFechada", false)
    .OrderBy("id", false)
    .AddSnapshotListener(async (snapshot, error) =>
    {
        var tasks = new List<Task>();
        foreach (var documentChange in snapshot.DocumentChanges)
        {
            switch (documentChange.Type)
            {
                case DocumentChangeType.Added:
                    var document = documentChange.Document.ToObject<Document>();

                    this.dataManager.Documents.Add(document);

                    document.Id = documentChange.Document.Id;

                    tasks.Add(Task.Run(async () =>
                    {
                        var fichaLinhasSnapshot = await CrossCloudFirestore.Current.Instance
                            .Collection("FichaLinhas")
                            .WhereEqualsTo("numeroFicha", document.Numero)
                            .GetAsync();

                        document.ListOfDocLines = fichaLinhasSnapshot.ToObjects<ListOfDocLines>().ToList();

                        var fichaServicosSnapshot = await CrossCloudFirestore.Current.Instance
                            .Collection("FichaServicos")
                            .WhereEqualsTo("numeroFicha", document.Numero)
                            .GetAsync();

                        document.ListOfDocServices = fichaServicosSnapshot.ToObjects<DocumentService>().ToList();
                    }));

                    break;

                case DocumentChangeType.Modified:
                    break;

                case DocumentChangeType.Removed:
                    // Document Removed
                    var docToRemove = documentChange.Document.Id;
                    if (dataManager.Documents.Exists(x => x.Id == docToRemove))
                    {
                        dataManager.Documents.Remove(dataManager.Documents.Where(x => x.Id == docToRemove).First());
                    }
                    break;
            }
        }

        await Task.WhenAll(tasks);

        dataAdapter.NotifyDataSetChanged();
    });
mrobraven commented 3 years ago

Was having a similar issue with

foreach (IDocumentSnapshot row in postArray.Documents)
{

}

Can confirm changing to:

foreach (IDocumentSnapshot row in postArray.Documents.ToList())
{

}

Fixed the issue, looks to be that when numerous calls from numerous pages in the application are fighting to access firebase through the same libraries, the application is disposing of the .Documents IEnumerable before the loop has a chance to finish. Seems like using .ToList() and creating another reference to the data that isn't within the libraries fixes the issue. (Correct me if I am wrong)

Edit:

The original error has gone away, but another error now appears sometimes and other times the application is fine. System.ObjectDisposedException

Cannot access a disposed object.
Object name: 'Firebase.Firestore.QueryDocumentSnapshot'

See issue #84