Search code examples
c#asynchronousxamarin.iosrestsharp

Xamarin IOS can't create controller async task


I'm going to do my best in english to explain the problem. I'm currently trying Xamarin to check if we will migrate our IOS app and Android App on this, because it seems to be really nice.

The small test I'm doing is just test my simple login page.

I'm really new to CSharp, so sorry in advance if I ask stupid and simple questions...

I'll introduce first the implementation, then the problem at then end of this post.

[IMPLEMENTATION]

So this is what I've done using RestSharp (Rest library client) library from nuggets. I have a shared project which will be used in Android App and IOs app (in order to make the job only once). I'm working with Xamarin Studio on Mac.

In my LoginApiManager.cs I have a "basic" implementation like this:

public interface UserConnectionStates
    {
        //Regroup all state created above
        void userIsConnected(User user);
        void userConnectionFailed(IRestResponse<User> response);
        void userDisconnected();
}

public class LoginApiManager : RestManager
{
    //Used for instance and shared element
    public static LoginApiManager instance;

    //Permits to store the information about the current loged in user
    private User loggedInUser;

    //All listeners for that class
    private List<UserConnectionStates> userConnectionStatesListeners = new List<UserConnectionStates>();

    public LoginApiManager()
    {
        //Create the the Rest Client to make Api Calls
        base.client = new RestClient();
        client.BaseUrl = new Uri(apiPath);
        client.Authenticator = new HttpBasicAuthenticator(null, null);
    }

    public static LoginApiManager getInstance()
    {
        if (instance != null)
        {
            return instance;
        }
        else
        {
            return new LoginApiManager();
        }
    }

    //------- UserConnectionStates Listeners accessors --------
    public void addUserConnectionStatesListener(UserConnectionStates listener)
    {

        if (!userConnectionStatesListeners.Contains(listener))
        {
            userConnectionStatesListeners.Add(listener);
        }

    }

    public void removeUserConnectionStatesListener(UserConnectionStates listener)
    {
        if (userConnectionStatesListeners.Contains(listener))
        {
            userConnectionStatesListeners.Remove(listener);
        }
    }

    /*
     * Permits to connect a user with a given username and password. It will
     * call the API and return a User Object containing all information needed
     * for connection, including token.
     */
    public void connectUserWithUserNameAndPassword(string username, string password)
    {

        var request = new RestRequest(Method.POST);
        request.Resource = "/myApiEndpoints/";

        //Add it to the request
        request.AddParameter("username", username);
        request.AddParameter("password", password);

        //Execute the query
        client.ExecuteAsync<User>(request, (response) =>
        {
            if (response.StatusCode == System.Net.HttpStatusCode.OK)
            {
                //We serialize the user from response
                loggedInUser = response.Data;
                foreach (var l in this.userConnectionStatesListeners)
                {
                    l.userIsConnected(response.Data);
                }

            }
            else
            {
                //We send error to the code
                foreach(var l in this.userConnectionStatesListeners)
                {
                    l.userConnectionFailed(response);
                }
            }

        });
    }
}

...Ok, let's continue, my LoginViewController implement the interface UserConnectionStates and reference himself to the listeners list in order to be informed about the different connection states modifications. Just like this:

in my LoginController.cs

public partial class LoginController : UIViewController, UserConnectionStates
{
    //Some code here ...

    public override void ViewDidLoad()
    {
        base.ViewDidLoad();
        loginManager = LoginApiManager.getInstance();
        loginManager.addUserConnectionStatesListener(this); //we create a listener for information about user connection

    }

    //Here comme the interface methods...

    //Interface for listening user connection state
    public void userIsConnected(User user)
    {
        var alertController = UIAlertController.Create("Connexion", "Connexion Success!",UIAlertControllerStyle.Alert);
        alertController.AddAction(UIAlertAction.Create("OK", UIAlertActionStyle.Cancel, null));
        this.PresentViewController(alertController, true, null);

    }

    public void userConnectionFailed(IRestResponse<User> response)
    {
        var alert= UIAlertController.Create("Connexion",  "Unable to connect", UIAlertControllerStyle.Alert);
        alert.AddAction(UIAlertAction.Create("OK", UIAlertActionStyle.Cancel, null));
        PresentViewController(alert, animated: true, completionHandler: null);
    }

    public void userDisconnected()
    {

    }

}

[PROBLEM]

... Here is my problem, when the client.ExecuteAsync(request, (response)) is called, as an example for a success, it will call the interface method implemented by the listener object public void userIsConnected(User user) of my LoginController, but because it's called in an other thread it stops at

 var alertController = UIAlertController.Create("Connexion", "Connexion Success!",UIAlertControllerStyle.Alert);

nos crashes just stop and do nothing.

I made a test by using a simple

IRestResponse response = client.Execute(request); 

and it works because the method is synchronous and called in the same Thread, but it locks the UI until task is done...

What is the best way in CSharp or Xamarin to implement this kind of patterns? If there is another way to do things like this I'll learn it, but because I'm new to this I don't know what are the best practice for this kind of things and I didn't find this in documentation (it's for sure in it, but don't find it sorry).


Solution

  • To be honest I would rethink your current code structure. First of all I would stop using RestSharp and go for: Refit

    This is a pretty cool post which may shed some light on how to structure code properly: Mobile resiliency with Xamarin

    Use MVVM in your iOS and Android applications. This way you can share the ViewModels and have more code that is reusable and separated.