Background Firestore's AddSnapshotListener in iOS

I'm making an App with Xamarin.Forms for Android and for iOS. The App needs to extract a list of communities from Firestore and, if the user enables it, it could subscribe for alerts in each community.

In Android I did a Sticky Service that is being started when the App starts, and then, in OnStartCommand function, the Service subscribe itself for every community using the AddSnapshotListener(this); function (below is the sample code). Everything works fine but now I need to do the same but in iOS.

I did a research and I tried to learn the background system in iOS. In the Microsoft's documentation I found that iOS has a background mode called "Fetch" and, for that I understand, it's a function in AppDelegate that is being called every X Seconds/Minutes by iOS. But, instead of Android where I subscribe the app to a Class, I cannot imaginate how can I use AddSnapshotListener in a Function that is called from the system.

So, how can I Listen for changes in a Firestore's collection in background on iOS?

One way could be using the Background Fetch and call .GetCollection("community1") and check for new documents, but the problem is that the user can adds more than one community, and then could be a big internet data issue (also I think that there is a maximum of 30 seconds to call back from the Fetch Function, otherwise iOS could terminate the App).

Here is the code that I'm using in Android (I did simplified the code):

DroidCommonFunctions.cs

public void UpdateCommunityAlertListener(List<LocalCommunity> Communities)
{
    if (MainActivity.ThisAct == null || Communities == null)
        return;
    try
    {
        MainActivity.ThisAct.StopService(new Intent(MainActivity.ThisAct, typeof(BackgroundTask.CommunityAlertsListener)));
        if (Communities.Count == 0)
            return;
        Intent serviceIntent = new Intent(MainActivity.ThisAct, typeof(BackgroundTask.CommunityAlertsListener));
        serviceIntent.PutExtra("CommunityLength", Communities.Count);
        for (int i = 0; i < Communities.Count; i++)
        {
            serviceIntent.PutExtra("CommunityId" + i, Communities[i].Id);
            serviceIntent.PutExtra("CommunityName" + i, Communities[i].Alias);
        }
        MainActivity.ThisAct.StartService(serviceIntent);
    } catch (Exception ex)
    {
        System.Diagnostics.Debug.WriteLine("ACAL: " + ex.Message);
    }
}

DroidBackgroundTask.cs

[Service]
public class CommunityAlertsListener : Service, IEventListener
{
    public class CommunityRegistration
    {
        public string Name { get; set; } = "";
        public IListenerRegistration Registration { get; set; }
    }

    public Dictionary<string, CommunityRegistration> CommunityDic = new Dictionary<string, CommunityRegistration>();

    public override void OnCreate()
    {
        base.OnCreate();
        StartServiceWithNotification(); //Create Android Notification
    }

    [return: GeneratedEnum]
    public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
    {
        if(FirestoreService.Instance == null)
        {
            StopSelf();
            return StartCommandResult.RedeliverIntent;
        }

        int comLength = intent.GetIntExtra("CommunityLength", 0);
        for(int i = 0; i < comLength; i++)
        {
            string comId = intent.GetStringExtra("CommunityId" + i);
            string comName = intent.GetStringExtra("CommunityName" + i);
            if (string.IsNullOrWhiteSpace(comId) || string.IsNullOrWhiteSpace(comName) || CommunityDic.ContainsKey(comId))
                continue;
            IListenerRegistration comReg = FirestoreService.Instance.Collection("communities").Document(comId).Collection("security_events").AddSnapshotListener(this);
            CommunityDic.Add(comId, new CommunityRegistration()
            {
                Name = comName,
                Registration = comReg
            });
        }

        if (comLength == 0 || CommunityDic.Count == 0)
            StopSelf();
        else
            System.Diagnostics.Debug.WriteLine("CommunityAlertsListener STARTED!");

        return StartCommandResult.RedeliverIntent;
    }

    public void OnEvent(Java.Lang.Object value, FirebaseFirestoreException error)
    {
        if (error != null)
        {
            System.Diagnostics.Debug.WriteLine("Listen failed: " + error.Message);
            return;
        }

        try
        {
            QuerySnapshot snapshot = (QuerySnapshot)value;
            if (snapshot.Query == null || snapshot.Query.GetType() != typeof(CollectionReference))
                return;
            NotifySnapshot(snapshot); //Create Android Notification
        }
        catch
        {
            System.Diagnostics.Debug.WriteLine("Listen Failed: " + error.Message);
        }
    }

    public override void OnDestroy()
    {
        foreach(CommunityRegistration comReg in CommunityDic.Values)
        {
            try
            {
                if (comReg != null && comReg.Registration != null)
                    comReg.Registration.Remove();
            } catch
            {
                if (comReg != null)
                    System.Diagnostics.Debug.WriteLine(string.Format("{0} wasn't being removed", comReg.Name));
                else
                    System.Diagnostics.Debug.WriteLine("A community wasn't being removed");
            }
        }
        System.Diagnostics.Debug.WriteLine("CommunityAlertsListener STOPPED!");
        base.OnDestroy();
    }
}
Sign In or Register to comment.