Question

Hi I am developing an App which has a requirement to do syncing operation (data submission and retrieval) on web server.

User can submit forms offline (ie. storing data to local db on device). And whenever network is available, background service should submit those data to web server.

The detail requirement of background service is like:

  • Background service will first check whether network is available or not
  • if network is available, it will collect the data store in Local db (SQLite) on device
  • It submit the data to server
  • ask server to have any new data, and if available, get that data and update the local database on device.

I am quite new to iOS and xamarin/monotouch, and would like to know how to achieve this?

I know about various background modes in iOS, such as background fetch, nsurlsession, background transfer etc.

I have try to implement Background Fetch which i think is suitable for my situation. but it runs on it's own time.

Also would like to know that if user has killed my application, then also background fetch will invoke and still run my application ?

the code is something like this in my appdelegate -> PerformFetch method:

if(networkService.IsNetworkAvailable())
{
   if(this.syncDataService.DownloadNewDataFromServer())
   {
       Console.WriteLine("Data downloaded successfully from server..");
   }
   if(this.syncDataService.UploadDataToServer())
   {
       Console.WriteLine("Data submitted successfully to server...");
   }
   completionHandler(UIBackgroundFetchResult.NewData);
}
else
{
   completionHandler(UIBackgroundFetchResult.NoData);
}

Update: Finally I have implemented it like this way (hope it can be helpful for someone) :

public class LocationUpdatedEventArgs : EventArgs
{
    private CLLocation location;

    public LocationUpdatedEventArgs(CLLocation location)
    {
        this.location = location;
    }

    public CLLocation Location
    {
        get { return this.location; }
    }
}

public class LocationManager
    {
        private static DateTime lastServiceRun;

        private CLLocationManager locMgr;

        public LocationManager()
        {
            this.locMgr = new CLLocationManager();
            this.LocationUpdated += this.PrintLocation;
            this.locMgr.Failed += (object sender, NSErrorEventArgs e) =>
            {
                Console.WriteLine("didFailWithError " + e.Error);
                Console.WriteLine("didFailWithError coe " + e.Error.Code);
            };
        }


        public event EventHandler<LocationUpdatedEventArgs> LocationUpdated = delegate { };


        public static TimeSpan TimeDiff { get; set; }


        public CLLocationManager LocMgr
        {
            get
            {
                return this.locMgr;
            }
        }


        public void StartLocationUpdates()
        {

            if (CLLocationManager.LocationServicesEnabled)
            {
                // sets the accuracy that we want in meters
                this.LocMgr.DesiredAccuracy = 1;

                //// Location updates are handled differently pre-iOS 6. If we want to support older versions of iOS,
                //// we want to do perform this check and let our LocationManager know how to handle location updates.

                if (UIDevice.CurrentDevice.CheckSystemVersion(6, 0))
                {
                    this.LocMgr.LocationsUpdated += (object sender, CLLocationsUpdatedEventArgs e) =>
                    {
                        //// fire our custom Location Updated event
                        this.LocationUpdated(this, new LocationUpdatedEventArgs(e.Locations[e.Locations.Length - 1]));
                    };
                }
                else
                {
                    //// this won't be called on iOS 6 (deprecated). We will get a warning here when we build.
                    this.LocMgr.UpdatedLocation += (object sender, CLLocationUpdatedEventArgs e) =>
                    {
                        this.LocationUpdated(this, new LocationUpdatedEventArgs(e.NewLocation));
                    };
                }

                //// Start our location updates
                this.LocMgr.StartUpdatingLocation();

                lastServiceRun = DateTime.Now;

                // Get some output from our manager in case of failure
                this.LocMgr.Failed += (object sender, NSErrorEventArgs e) =>
                {
                    Console.WriteLine(e.Error);
                };
            }
            else
            {
                //// Let the user know that they need to enable LocationServices
                Console.WriteLine("Location services not enabled, please enable this in your Settings");
            }
        }

        /// <summary>
        /// The stop updating location.
        /// </summary>
        public void StopUpdatingLocation()
        {
            this.locMgr.StopUpdatingLocation();
        }

        /// <summary>
        /// The print location. (This will keep going in the background)
        /// </summary>
        /// <param name="sender"> The sender. </param>
        /// <param name="e"> Location updated event argument </param>
        public void PrintLocation(object sender, LocationUpdatedEventArgs e)
        {
            CLLocation location = e.Location;

            Console.WriteLine("Longitude: " + location.Coordinate.Longitude);
            Console.WriteLine("Latitude: " + location.Coordinate.Latitude);

            var diff = DateTime.Now - lastServiceRun;
            TimeDiff = diff;
            if (TimeDiff.Minutes == 2)
            {
        // RunInBackground() is my method which call the service to upload/download data from server
                if (this.RunInBackground())
                {
                    lastServiceRun = DateTime.Now;
                }
            }
        }
}
Was it helpful?

Solution

The timer never works in background, and you cannot add another thread in this method. DidEnterBackground is used to complete pending tasks. This will usually last for approximately 30secs - 1 minute.

You can add a loop or add a long running task in this method.

OTHER TIPS

I believe the solution to your problem is to register your task as a long running task.

Considering that you are using Xamarin, there is also a very good explanation on background tasks using Xamarin here, which would probably be more relevant to what you want to do.

Also, have a look at App States and Multitasking for an explanation on what is happening when an application goes to the background: " After returning from the applicationDidEnterBackground: method, most apps move to the suspended state shortly afterward. Apps that request specific background tasks (such as playing music) or that request a little extra execution time from the system may continue to run for a while longer."

In general looking at the apple developer site is a good thing since 100% of iOS APIs are in Xamarin

In your app delegate on FinishedLaunching you create timer, that will invoke you code in the time interval that you will set

    public override bool FinishedLaunching(UIApplication app, NSDictionary options)
    {
        ...
        Timer timer = new Timer(5000); //5000 milisecs - 5 secs
        timer.Elapsed += timer_Elapsed;
        timer.Start();
        ...
    }

    private void timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        //your code here
    }

Also if user will kill your app it means that no code even background fetch will not invoke any more

A thought, for iOS. A way of doing it could be to create a background task scheduler, possibly based on LimitedConcurrencyLevelTaskScheduler (https://msdn.microsoft.com/en-us/library/ee789351(v=vs.110).aspx), that will execute your IBackgroundTask jobs. These jobs could be persisted to a storage and will be performed sequentially. E.g. if you wanna download a lot of large files, you wanna do them one by one and show progress or render them in UI as they land on your device. Then you enqueue any downloading task with this scheduler and you restart it when it's about to die:

void StartBackgroundTask()
{
	BackgroundTaskId = UIApplication.SharedApplication.BeginBackgroundTask(() => {
		// Handle expiration: will happen before reaching 600 secs
		TaskScheduler.Stop();
		UIApplication.SharedApplication.EndBackgroundTask(BackgroundTaskId);

		// Start again
		StartBackgroundTask();
	});

	Task.Run(() => {
		TaskScheduler = new BackgroundTaskScheduler().Start();
	});
}

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top