Xamarin.Android Publishing your Xamarin.Android APK "Mysterious" bugs related to ProGuard and Linker


Example

You made a great app and tested it in Debug, with good results. Everything was working fine!

But then, you decided to prepare your app for release. You set up MultiDex, ProGuard and Linker, and then, it stopped working.

This tutorial aims to help you to find out common problems related to ProGuard and Linker that can cause mysterious bugs.


Understanding Xamarin.Linker

Xamarin.Linker is a tool in the building process that removes unused code and classes from your .NET code (not Java code). In your project's Properties -> Android Options -> Linker, there will be an selection box Linking with the options:

Linker options

None: No code is removed.

Sdk Assemblies Only: This option makes the Xamarin.Linker to check for unused code only in the Xamarin libraries. This option is safe.

Sdk and User Assemblies: This option makes the Xamarin.Linker to check for unused code in the Xamarin libraries and in the project code (including PCLs, Xamarin components and NuGet packages). This option is not always safe!

When using Sdk and User Assemblies option, Xamarin.Linker may think that parts of the code are unused when actually they are very much used! That may cause some libraries to stop working properly and cause bugs in your app.

To make the Xamarin.Linker not remove code, there are 3 options:

  1. Setting the Linking option to None or Sdk Assemblies Only;
  2. Skip linking assemblies;
  3. Using the Preserve attribute.

Example for 2. Skip linking assemblies:

In the example below, using Xamarin.Linker caused a NuGet Package (Octokit) that works fine to stop working, because it could not connect to the internet anymore:

[0:] ERROR
[0:] SOURCE: mscorlib
[0:] MESSAGE: Object reference not set to an instance of an object.
[0:] STACK TRACE:   at Octokit.PocoJsonSerializerStrategy.DeserializeObject (System.Object value, System.Type type) [0x003d8] in D:\repos\octokit.net\Octokit\SimpleJson.cs:1472 
  at Octokit.Internal.SimpleJsonSerializer+GitHubSerializerStrategy.DeserializeObject (System.Object value, System.Type type) [0x001c3] in D:\repos\octokit.net\Octokit\Http\SimpleJsonSerializer.cs:165 
  at Octokit.SimpleJson.DeserializeObject (System.String json, System.Type type, Octokit.IJsonSerializerStrategy jsonSerializerStrategy) [0x00007] in D:\repos\octokit.net\Octokit\SimpleJson.cs:583 
  at Octokit.SimpleJson.DeserializeObject[T] (System.String json, Octokit.IJsonSerializerStrategy jsonSerializerStrategy) [0x00000] in D:\repos\octokit.net\Octokit\SimpleJson.cs:595 
  at Octokit.Internal.SimpleJsonSerializer.Deserialize[T] (System.String json) [0x00000] in D:\repos\octokit.net\Octokit\Http\SimpleJsonSerializer.cs:21 
  at Octokit.Internal.JsonHttpPipeline.DeserializeResponse[T] (Octokit.IResponse response) [0x000a7] in D:\repos\octokit.net\Octokit\Http\JsonHttpPipeline.cs:62 
  at Octokit.Connection+<Run>d__54`1[T].MoveNext () [0x0009c] in D:\repos\octokit.net\Octokit\Http\Connection.cs:574 
--- End of stack trace from previous location where exception was thrown ---

To make the library start working again, it was necessary to add the package reference name in the Skip linking assemblies field, located in project -> Properties -> Android Options -> Linker, as in the picture below:

Adding project reference to skip linking assemblies

After that, the library started to work without any issues.

Example for 3. Using the Preserve attribute:

Xamarin.Linker perceives as unused code mostly code from model classes in your project's core.

To make the class preserved during the linking process, you can use the Preserve attribute.

First, create in your project core's a class named PreserveAttribute.cs, insert the following code and replace the namespace with your project's namespace:

PreserveAttribute.cs:

namespace My_App_Core.Models
{
    public sealed class PreserveAttribute : System.Attribute
    {
        public bool AllMembers;
        public bool Conditional;
    }
}

In each model class of your project's core, insert the Preserve attribute as in the example below:

Country.cs:

using System;
using System.Collections.Generic;

namespace My_App_Core.Models
{
    [Preserve(AllMembers = true)]
    public class Country
    {
        public String name { get; set; }
        public String ISOcode { get; set; }

        [Preserve(AllMembers = true)]
        public Country(String name, String ISOCode)
        {
            this.name = name;
            this.ISOCode = ISOCode;
        }
    }
}

After that, the linking process will not remove the preserved code anymore.


Understanding ProGuard

ProGuard is a tool in the building process that removes unused code and classes from your Java code. It also obfuscates and optimizes the code.

However, ProGuard sometimes may remove code that it perceives as unused, when it is not. To avoid that, the developer must debug the app (in Android Device Monitor and in the Visual Studio Debug) and detect which class was removed, for then to configure the ProGuard configuration file to keep the class.

Example

In the example below, ProGuard removed two classes (Android.Support.V7.Widget.FitWindowsLinearLayout and Android.Support.Design.Widget.AppBarLayout) used in AXML layout files, but that were perceived as unused in the code. The removal caused ClassNotFoundException in the Java code when rendering the activity layout:

layout_activitymain.axml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activitymain_drawerlayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true" <!-- ### HERE ### -->
    tools:openDrawer="start">
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true">
        <!-- ### HERE ## -->
        <android.support.design.widget.AppBarLayout
            android:id="@+id/activitymain_appbarlayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/AppTheme.AppBarOverlay">
...

LogCat showing error when creating the layout in SetContentView:

Error showing in LogCat

To fix this error, it was necessary to add the following lines to the ProGuard configuration file of the project:

-keep public class android.support.v7.widget.FitWindowsLinearLayout
-keep public class android.support.design.widget.AppBarLayout

After that, no more errors were shown when creating the layout.

ProGuard Warnings

ProGuard sometimes show warnings in the Error List after building your project. Although they raise a question of whether your app is OK or not, not all of their warnings indicate troubles, especially if your app successfully builds.

One example for that is when using the Picasso library: when using ProGuard, this may show warnings such as okio.Okio: can't find referenced class (...) or can't write resource [META-INF/MANIFEST.MF] (Duplicate zip entry [okhttp.jar:META-INF/MANIFEST.MF]) (...), but the app builds and the library works without problems.