Neira
Neira

Reputation: 33

Consuming Web Services with Xamarin on Android device (not emulator) : AOT error

I am learning Xamarin and Web App with .Net. The goal is to build a web app (web services) and an Android app, the Android app will consume web services.

I have implemented some basic stuff on the web app, and I first tried to access it with a console application with an HttpClient. It works fine:

static HttpClient client = new HttpClient();
HttpResponseMessage response = await client.GetAsync(path); // path is the local path of my IIS server (https://localhost:44331/api/Employees)

The next step was to do the same in a Xamarin app, but since I am working with a device and not an emulator, my device hasn't access to local IIS server. I found that I could access to the IIS server thanks to Conveyor, so I add the Visual Studio plugin which shows me the following path : https://192.168.1.25:45455/api/Employees

I tried with this path in the console app and it worked. I tried to access through a browser on my android device, it works. But when I use it in the Xamarin app, the code is executed until the GetAsync method (HttpResponseMessage response = await client.GetAsync("https://192.168.1.25:45455/api/Employees");) and then nothing happened. No error, no exception, nothing. The execution is like blocked here.

In the output window, I have the following message :

03-15 20:27:04.261 D/Mono    ( 4779): Loading reference 11 of netstandard.dll asmctx DEFAULT, looking for System.Runtime.Serialization, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
03-15 20:27:04.262 D/Mono    ( 4779): Image addref System.Runtime.Serialization[0x789b9fc600] (asmctx DEFAULT) -> System.Runtime.Serialization.dll[0x789b9c8800]: 2
03-15 20:27:04.262 D/Mono    ( 4779): Prepared to set up assembly 'System.Runtime.Serialization' (System.Runtime.Serialization.dll)
03-15 20:27:04.262 D/Mono    ( 4779): Assembly System.Runtime.Serialization[0x789b9fc600] added to domain RootDomain, ref_count=1
03-15 20:27:04.263 D/Mono    ( 4779): AOT: image 'System.Runtime.Serialization.dll.so' not found: dlopen failed: library "System.Runtime.Serialization.dll.so" not found
03-15 20:27:04.263 D/Mono    ( 4779): AOT: image '/Users/builder/jenkins/workspace/archive-mono/2019-08/android/release/sdks/out/android-arm64-v8a-release/lib/mono/aot-cache/arm64/System.Runtime.Serialization.dll.so' not found: (null)
03-15 20:27:04.264 D/Mono    ( 4779): Config attempting to parse: 'System.Runtime.Serialization.dll.config'.
03-15 20:27:04.264 D/Mono    ( 4779): Config attempting to parse: '/Users/builder/jenkins/workspace/archive-mono/2019-08/android/release/sdks/out/android-arm64-v8a-release/etc/mono/assemblies/System.Runtime.Serialization/System.Runtime.Serialization.config'.
03-15 20:27:04.264 D/Mono    ( 4779): Assembly Ref addref netstandard[0x78b6e37a80] -> System.Runtime.Serialization[0x789b9fc600]: 2
03-15 20:27:04.264 D/Mono    ( 4779): Loading reference 0 of System.Runtime.Serialization.dll asmctx DEFAULT, looking for mscorlib, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
03-15 20:27:04.264 D/Mono    ( 4779): Assembly Ref addref System.Runtime.Serialization[0x789b9fc600] -> mscorlib[0x78bdbf6700]: 52
Loaded assembly: System.Runtime.Serialization.dll [External]
[HotReload] (2020-03-15 20:26:53.4): INFO: HotReload: Initialized Agent.
[HotReload] (2020-03-15 20:27:06.6): INFO: Le rechargement à chaud XAML est connecté et prêt.
03-15 20:27:04.486 D/Mono    ( 4779): DllImport searching in: '__Internal' ('(null)').
03-15 20:27:04.486 D/Mono    ( 4779): Searching for 'java_interop_jnienv_new_string'.
03-15 20:27:04.486 D/Mono    ( 4779): Probing 'java_interop_jnienv_new_string'.
03-15 20:27:04.486 D/Mono    ( 4779): Found as 'java_interop_jnienv_new_string'.
03-15 20:27:04.492 D/Mono    ( 4779): DllImport searching in: '__Internal' ('(null)').
03-15 20:27:04.492 D/Mono    ( 4779): Searching for 'java_interop_jnienv_get_static_object_field'.
03-15 20:27:04.492 D/Mono    ( 4779): Probing 'java_interop_jnienv_get_static_object_field'.
03-15 20:27:04.492 D/Mono    ( 4779): Found as 'java_interop_jnienv_get_static_object_field'.
03-15 20:27:04.502 D/NetworkSecurityConfig( 4779): No Network Security Config specified, using platform default
03-15 20:27:04.503 I/DpmTcmClient( 4779): RegisterTcmMonitor from: com.android.okhttp.TcmIdleTimerMonitor
03-15 20:27:04.648 D/Mono    ( 4779): DllImport searching in: '__Internal' ('(null)').
03-15 20:27:04.648 D/Mono    ( 4779): Searching for 'java_interop_jnienv_call_nonvirtual_boolean_method_a'.
03-15 20:27:04.648 D/Mono    ( 4779): Probing 'java_interop_jnienv_call_nonvirtual_boolean_method_a'.
03-15 20:27:04.649 D/Mono    ( 4779): Found as 'java_interop_jnienv_call_nonvirtual_boolean_method_a'.

So I thought it may come from this 'AOT: image 'System.Runtime.Serialization.dll.so' not found'. Looking on the Web, I have found that I have to set <AotAssemblies>True</AotAssemblies> into the project.csproj file.

First I don't know how to do that. Second, I am using Visual Studio Community and I have read this : "The Community version of Visual Studio does not support AOT; and, sometime in the past six months, an update to Xamarin explicitly forces it off (if you had manually turned AOT on by editing the csproj file). Now must have Enterprise version to build with AOT.".

So right now I am a bit lost because I don't know what to do. If anyone has a clue on what I could do, it would be very helpful.

Edit await client.GetAsync() is called in EmployeeServices class by the following function :

public static async Task<List<Employee>> GetEmployeesAsync()
    {
        List<Employee> employees = null;
        HttpResponseMessage response = await _client.GetAsync("https://192.168.1.25:45455/api/Employees");
        if (response.IsSuccessStatusCode)
        {
            string json = await response.Content.ReadAsStringAsync();
            employees = JsonConvert.DeserializeObject<List<Employee>>(json);
        }
        return employees;
    }

GetEmployeesAsync is called MainViewModel.cs in the following function :

 async Task<List<Employee>> IntermediateMethod()
    {
        return await EmployeesServices.GetEmployeesAsync();
    }

which is itself called in MainViewModel constructor :

public MainViewModel()
    {
        var employeesServices = new EmployeesServices();
        try
        {
             EmployeesList = IntermediateMethod().Result;
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
        //EmployeesList = employeesServices.GetEmployeesStatic();
    }

and the MainViewModel is binded in the MainPage :

<ContentPage.BindingContext>
    <ViewModels:MainViewModel/>
</ContentPage.BindingContext>

Upvotes: 1

Views: 363

Answers (1)

Cheesebaron
Cheesebaron

Reputation: 24460

OK, basically what you are doing in the constructor is wrong.

Why? A constructor is not async, and depending on which context you invoke that constructor you will end up in a deadlock, since you are calling .Result on the Task.

Instead I suggest you use one of the life-cycle methods in Xamarin.Forms, which you seem to be using. I suggest using a OnAppearing override to start getting data:

protected override async void OnAppearing()
{
    // get data
}

Also I would suggest encapsulating getting the data in the Command pattern as well:

public ICommand GetDataCommand { get; }

Then in your ViewModel constructor initialize it:

public MainViewModel()
{
    GetDataCommand = new Command(async () => await DoGetDataCommand());
}

Then in DoGetDataCommand:

private async Task DoGetDataCommand()
{
    try
    {
        Employees = await EmployeesServices.GetEmployeesAsync();
    }
    catch (Exception ex)
    {
        // TODO: handle exception
    }
}

Then in OnAppearing call: GetDataCommand.Execute(null);.

Additionally. All awaited tasks in non-UI code. Meaning when they are not inside a ViewModel, you should consider adding .ConfigureAwait(false) on them like:

var response = await _client.GetAsync("https://192.168.1.25:45455/api/Employees").ConfigureAwait(false);

What this will do is not trying to switch back to the thread we came from. This will limit overhead of switching back and forth between threads, which can cause deadlocks if the thread you are trying to switch to is already busy.

Your ViewModel will roughly look like:

public class MainViewModel : BaseViewModel
{
    private List<Employee> _employees;

    public ICommand GetDataCommand { get; }

    public List<Employee> Employees
    {
        get => _employees;
        set => SetProperty(ref _employees, value);
    }

    public MainViewModel()
    {
         GetDataCommand = new Command(async () => await DoGetDataCommand());
    }

    private async Task DoGetDataCommand()
    {
        try
        {
            Employees = await EmployeesServices.GetEmployeesAsync();
        }
        catch (Exception ex)
        {
            // TODO: handle exception
        }
    }

Upvotes: 1

Related Questions