Search code examples
c#xamarinnsurlsessionnsurlsessiondownloadtask

Xamarin: How to return data to Shared Project from NSUrlSession task from Xamarin iOS Project?


I have a class in Xamarin iOS project like:

public class NetworkUtility : INetworkUtility
{

    public void GetData()
    {

        NSUrl url = new NSUrl("https://reqres.in/api/users?page=2");
        NSUrlRequest request = new NSUrlRequest(url);
        NSUrlSession session = null;
        NSUrlSessionConfiguration myConfig = NSUrlSessionConfiguration.DefaultSessionConfiguration;
        myConfig.MultipathServiceType = NSUrlSessionMultipathServiceType.Handover;
        session = NSUrlSession.FromConfiguration(myConfig);
        NSUrlSessionTask task = session.CreateDataTask(request, (data, response, error) => {
            Console.WriteLine(data);

        });

        task.Resume();
    }
}

And I am calling it from the shared project in some Xaml code behind like:

DependencyService.Get<INetworkUtility>().GetStringData();

Now in the GetData() function, I get data from the web service in the anonymous function part:

NSUrlSessionTask task = session.CreateDataTask(request, (data, response, error) => {
                Console.WriteLine(data);

            });

How can I return this data to the caller (i.e. to the Xaml code behind)?

If I change the return type of the GetData() function to string or Task and try to return inside the lambda expression, it gives a compile error.

I read and various forums say I need to implement an NSUrlDelegate to handle the response. I have tried multiple variations for this but no success. Please advise.

Also, (I think is not possible or the right approach) but is there a way to just return the whole NSUrlSession to Shared Project and just call data task methods from shared project itself?


Solution

  • There is a simple solution. In your INetworkUtility interface declare the GetData() method like this:

    Task<string> GetData();
    

    In your iOS NetworkUtility class create a TaskCompletionSource and return the Task. In your lambda expression you can then, when everything is set and done, set a task result.

    public Task<string> GetData()
    {
        TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
        NSUrl url = new NSUrl("https://reqres.in/api/users?page=2");
        NSUrlRequest request = new NSUrlRequest(url);
        NSUrlSession session = null;
        NSUrlSessionConfiguration myConfig = NSUrlSessionConfiguration.DefaultSessionConfiguration;
        myConfig.MultipathServiceType = NSUrlSessionMultipathServiceType.Handover;
        session = NSUrlSession.FromConfiguration(myConfig);
        NSUrlSessionTask task = session.CreateDataTask(request, (data, response, error) => {
            //Console.WriteLine(data);
            //tell the TaskCompletionSource that we are done here:
            tcs.TrySetResult(data); //assuming data is a string but i guess you can convert it if not
        });
    
        task.Resume();
        return tcs.Task;
    }
    

    In your shared code you can create an async function which awaits the resulting Task:

    public async Task ReceiveDataShared() {
        string yourData = await DependencyService.Get<INetworkUtility>().GetData();
    }
    

    Edit: I guess there could be an edge case where the lambda expression and thus TrySetResult(...) is never called. In this case your shared code will await forever. To prevent this you can set a timeout like this:

    Task<string> dataTask = DependencyService.Get<INetworkUtility>().GetData();
    await Task.WhenAny(dataTask, Task.Delay(10000)); // 10.000ms timeout
    if(dataTask.IsCompletedSuccessfully) {
        //all good:
        string dataString = dataTask.Result;
    }else{
        //we hit the time limit
    }