xamarin / XamarinComponents

Plugins for Xamarin
MIT License
1.99k stars 695 forks source link

Xamarin.Kotlin.StdLib 1.4.20 dropped support for @Parcelize #1109

Open nielsdelporte opened 3 years ago

nielsdelporte commented 3 years ago

Context Android developers used to have the @Parcelize annotation to remove boilerplate implementation for Parcelable objects. The old plugin was deprecated in the 1.4.20 release of Kotlin, and if the native library still wants to use this feature, they now need to use the kotlin-parcelize plugin instead of the kotlin-android-extensions plugin.

Problem A bindings project which updates the Xamarin.Kotlin.StdLib NuGet package will no longer compile if a native library is using the @Parcelize annotation.

Steps to reproduce

  1. Setup a Xamarin.Android solution with a bindings project.
  2. Setup a simple native Android library which the bindings project uses.
  3. Make sure all your projects use Kotlin 1.4.20.
  4. Create a sealed class in the library which the Xamarin.Android solution should be able to access.
    
    package com.project.example.parcelize.enums

import android.os.Parcelable import androidx.annotation.DrawableRes import androidx.annotation.IdRes import androidx.annotation.StringRes import kotlinx.parcelize.Parcelize import com.project.example.parcelize.R

/**

Compile errors

/Repositories/parcelize_example/ParcelizeExample.Bindings/obj/Debug/generated/src/Com.Project.Example.Parcelize.Enums.Options.cs(68,68): Error CS0738: 'Options.Info.Creator' does not implement interface member 'IParcelableCreator.CreateFromParcel(Parcel)'. 'Options.Info.Creator.CreateFromParcel(Parcel)' cannot implement 'IParcelableCreator.CreateFromParcel(Parcel)' because it does not have the matching return type of 'Object'. (CS0738) (ParcelizeExample.Bindings) /Repositories/parcelize_example/ParcelizeExample.Bindings/obj/Debug/generated/src/Com.Project.Example.Parcelize.Enums.Options.cs(68,68): Error CS0738: 'Options.Info.Creator' does not implement interface member 'IParcelableCreator.NewArray(int)'. 'Options.Info.Creator.NewArray(int)' cannot implement 'IParcelableCreator.NewArray(int)' because it does not have the matching return type of 'Object[]'. (CS0738) (ParcelizeExample.Bindings) /Repositories/parcelize_example/ParcelizeExample.Bindings/obj/Debug/generated/src/Com.Project.Example.Parcelize.Enums.Options.cs(68,68): Error CS0738: 'Options.Delete.Creator' does not implement interface member 'IParcelableCreator.CreateFromParcel(Parcel)'. 'Options.Delete.Creator.CreateFromParcel(Parcel)' cannot implement 'IParcelableCreator.CreateFromParcel(Parcel)' because it does not have the matching return type of 'Object'. (CS0738) (ParcelizeExample.Bindings) /Repositories/parcelize_example/ParcelizeExample.Bindings/obj/Debug/generated/src/Com.Project.Example.Parcelize.Enums.Options.cs(68,68): Error CS0738: 'Options.Delete.Creator' does not implement interface member 'IParcelableCreator.NewArray(int)'. 'Options.Delete.Creator.NewArray(int)' cannot implement 'IParcelableCreator.NewArray(int)' because it does not have the matching return type of 'Object[]'. (CS0738) (ParcelizeExample.Bindings)

Attempted solutions

Environment

=== Visual Studio Community 2019 for Mac ===

Version 8.7.4 (build 38) Installation UUID: 0e4b3431-41c6-4d2a-a600-8ef884d57a59 GTK+ 2.24.23 (Raleigh theme) Xamarin.Mac 6.18.0.23 (d16-6 / 088c73638)

Package version: 612000090

=== Mono Framework MDK ===

Runtime: Mono 6.12.0.90 (2020-02/d3daacdaa80) (64-bit) Package version: 612000090

=== Roslyn (Language Service) ===

3.7.0-6.20412.3+d3c3a44a4e7ad31cc75c59be0d3df4a19ff33878

=== NuGet ===

Version: 5.7.0.6702

=== .NET Core SDK ===

SDK: /usr/local/share/dotnet/sdk/3.1.401/Sdks SDK Versions: 3.1.401 3.1.301 3.1.300 3.1.200 3.1.100 2.1.505 MSBuild SDKs: /Library/Frameworks/Mono.framework/Versions/6.12.0/lib/mono/msbuild/Current/bin/Sdks

=== .NET Core Runtime ===

Runtime: /usr/local/share/dotnet/dotnet Runtime Versions: 3.1.7 3.1.5 3.1.4 3.1.2 3.1.0 2.1.21 2.1.19 2.1.18 2.1.16 2.1.14 2.1.9

=== Xamarin.Profiler ===

Version: 1.6.12.29 Location: /Applications/Xamarin Profiler.app/Contents/MacOS/Xamarin Profiler

=== Updater ===

Version: 11

=== Apple Developer Tools ===

A valid Xcode installation was not found at the configured location: '/Applications/Xcode.app'

=== Xamarin.Mac ===

Xamarin.Mac not installed. Can't find /Library/Frameworks/Xamarin.Mac.framework/Versions/Current/Version.

=== Xamarin.iOS ===

Version: 13.20.2.2 (Visual Studio Community) Hash: 817b6f72a Branch: d16-7 Build date: 2020-07-18 18:45:00-0400

=== Xamarin Designer ===

Version: 16.7.0.492 Hash: f5afe667d Branch: remotes/origin/d16-7-vsmac Build date: 2020-07-10 18:42:54 UTC

=== Xamarin.Android ===

Version: 10.3.1.4 (Visual Studio Community) Commit: xamarin-android/d16-6/3a10de9 Android SDK: /Users/nielsdelporte/Library/Developer/Xamarin/android-sdk-macosx Supported Android versions: None installed

SDK Tools Version: 26.1.1 SDK Platform Tools Version: 30.0.5 SDK Build Tools Version: 30.0.3

Build Information: Mono: 165f4b0 Java.Interop: xamarin/java.interop/d16-6@2cab35c ProGuard: xamarin/proguard/master@905836d SQLite: xamarin/sqlite/3.31.1@49232bc Xamarin.Android Tools: xamarin/xamarin-android-tools/d16-6@bfb66f3

=== Microsoft OpenJDK for Mobile ===

Java SDK: /Users/nielsdelporte/Library/Developer/Xamarin/jdk/microsoft_dist_openjdk_1.8.0.25 1.8.0-25 Android Designer EPL code available here: https://github.com/xamarin/AndroidDesigner.EPL

=== Android SDK Manager ===

Version: 16.7.0.13 Hash: 8380518 Branch: remotes/origin/d16-7~2 Build date: 2020-08-19 22:18:28 UTC

=== Android Device Manager ===

Version: 16.7.0.24 Hash: bb090a3 Branch: remotes/origin/d16-7 Build date: 2020-08-19 22:18:52 UTC

=== Build Information ===

Release ID: 807040038 Git revision: 7f322926a2e0a5fa0c1e3833215a191883f7514a Build date: 2020-08-20 10:10:20-04 Build branch: release-8.7 Xamarin extensions: 7f322926a2e0a5fa0c1e3833215a191883f7514a

=== Operating System ===

Mac OS X 10.15.7 Darwin 19.6.0 Darwin Kernel Version 19.6.0 Tue Jan 12 22:13:05 PST 2021 root:xnu-6153.141.16~1/RELEASE_X86_64 x86_64

4brunu commented 3 years ago

I had the same problem. After some digging, the problem is that the code generated by the @Parcelize changed a bit, now it uses generics, and it seems that Xamarin doesn't work with the new implementation. To fix this, I added the following code to the Metadata.xml.

    <attr path="/api/package[@name='com.project.example.parcelize.enums']/class[@name='Info.Creator']/method[@name='createFromParcel']" name="managedReturn">Java.Lang.Object</attr>
    <attr path="/api/package[@name='com.project.example.parcelize.enums']/class[@name='Info.Creator']/method[@name='newArray']" name="managedReturn">Java.Lang.Object[]</attr>

    <attr path="/api/package[@name='com.project.example.parcelize.enums']/class[@name='Delete.Creator']/method[@name='createFromParcel']" name="managedReturn">Java.Lang.Object </attr>
    <attr path="/api/package[@name='com.project.example.parcelize.enums']/class[@name='Delete.Creator']/method[@name='newArray']" name="managedReturn">Java.Lang.Object[]</attr>

You need to add this to every class that uses @Parcelize.

nielsdelporte commented 3 years ago

Thanks for the tip, @4brunu! Unfortunately, that looks exactly like what we added to the metadata.xml as well (at first with the sealed typename included in class@name), but we still get mismatch error messages. Did you need to do any other things? Add an extra nuget package or resetting cache?

<attr path="/api/package[@name='com.project.example.parcelize.enums']/class[@name='Info.CREATOR']/method[@name='createFromParcel']" name="managedReturn">Java.Lang.Object</attr>
<attr path="/api/package[@name='com.project.example.parcelize.enums']/class[@name='Info.CREATOR']/method[@name='newArray' and count(parameter)=1 and parameter[1][@type='int']]" name="managedReturn">Java.Lang.Object[]</attr>
<attr path="/api/package[@name='com.project.example.parcelize.enums']/class[@name='Delete.CREATOR']/method[@name='createFromParcel']" name="managedReturn">Java.Lang.Object</attr>
<attr path="/api/package[@name='com.project.example.parcelize.enums']/class[@name='Delete.CREATOR']/method[@name='newArray' and count(parameter)=1 and parameter[1][@type='int']]" name="managedReturn">Java.Lang.Object[]</attr>

/Repositories/parcelize_example/ParcelizeExample.Bindings/obj/Debug/generated/src/Com.Project.Example.Parcelize.Enums.Options.cs(68,68): Error CS0738: 'Options.Info.Creator' does not implement interface member 'IParcelableCreator.CreateFromParcel(Parcel)'. 'Options.Info.Creator.CreateFromParcel(Parcel)' cannot implement 'IParcelableCreator.CreateFromParcel(Parcel)' because it does not have the matching return type of 'Object'. (CS0738) (ParcelizeExample.Bindings) /Repositories/parcelize_example/ParcelizeExample.Bindings/obj/Debug/generated/src/Com.Project.Example.Parcelize.Enums.Options.cs(68,68): Error CS0738: 'Options.Info.Creator' does not implement interface member 'IParcelableCreator.NewArray(int)'. 'Options.Info.Creator.NewArray(int)' cannot implement 'IParcelableCreator.NewArray(int)' because it does not have the matching return type of 'Object[]'. (CS0738) (ParcelizeExample.Bindings)

4brunu commented 3 years ago

It was just this on the Metadata.xml The problem here may be with the sealed class. Try the following.

    <attr path="/api/package[@name='com.project.example.parcelize.enums']/class[@name='Options.Info.Creator']/method[@name='createFromParcel']" name="managedReturn">Java.Lang.Object</attr>
    <attr path="/api/package[@name='com.project.example.parcelize.enums']/class[@name='Options.Info.Creator']/method[@name='newArray']" name="managedReturn">Java.Lang.Object[]</attr>

    <attr path="/api/package[@name='com.project.example.parcelize.enums']/class[@name='Options.Delete.Creator']/method[@name='createFromParcel']" name="managedReturn">Java.Lang.Object</attr>
    <attr path="/api/package[@name='com.project.example.parcelize.enums']/class[@name='Options.Delete.Creator']/method[@name='newArray']" name="managedReturn">Java.Lang.Object[]</attr>
nielsdelporte commented 3 years ago

That doesn't work either; it's something we tried initially as well. We're using this approach for plain Kotlin object classes, and that works for us. Does it have anything to do with the way the sealed class is written, maybe?

4brunu commented 3 years ago

This is the compiled code

package com.project.example.parcelize.enums;

import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.DrawableRes;
import androidx.annotation.IdRes;
import androidx.annotation.StringRes;
import kotlin.Metadata;
import kotlin.jvm.internal.DefaultConstructorMarker;
import kotlin.jvm.internal.Intrinsics;
import kotlinx.parcelize.Parcelize;
import org.jetbrains.annotations.NotNull;

@Metadata(bv = {1, 0, 3}, d1 = {"\u0000\u001a\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\u0010\b\n\u0002\b\f\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0000\b6\u0018\u00002\u00020\u0001:\u0002\r\u000eB'\b\u0002\u0012\b\b\u0001\u0010\u0007\u001a\u00020\u0002\u0012\b\b\u0001\u0010\t\u001a\u00020\u0002\u0012\b\b\u0001\u0010\u0003\u001a\u00020\u0002¢\u0006\u0004\b\u000b\u0010\fR\u0019\u0010\u0003\u001a\u00020\u00028\u0006@\u0006¢\u0006\f\n\u0004\b\u0003\u0010\u0004\u001a\u0004\b\u0005\u0010\u0006R\u0019\u0010\u0007\u001a\u00020\u00028\u0006@\u0006¢\u0006\f\n\u0004\b\u0007\u0010\u0004\u001a\u0004\b\b\u0010\u0006R\u0019\u0010\t\u001a\u00020\u00028\u0006@\u0006¢\u0006\f\n\u0004\b\t\u0010\u0004\u001a\u0004\b\n\u0010\u0006‚\u0001\u0002\u000f\u0010¨\u0006\u0011"}, d2 = {"Lcom/project/example/parcelize/enums/Options;", "Landroid/os/Parcelable;", "", "viewId", "I", "getViewId", "()I", "iconResId", "getIconResId", "titleResId", "getTitleResId", "<init>", "(III)V", "Delete", "Info", "Lcom/project/example/parcelize/enums/Options$Info;", "Lcom/project/example/parcelize/enums/Options$Delete;", "teleconsultationandroid_release"}, k = 1, mv = {1, 4, 1})
/* compiled from: Options.kt */
public abstract class Options implements Parcelable {
    private final int iconResId;
    private final int titleResId;
    private final int viewId;

    @Parcelize
    @Metadata(bv = {1, 0, 3}, d1 = {"\u0000 \n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\u0010\b\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0002\b\u0005\b\u0007\u0018\u00002\u00020\u0001B\u0007¢\u0006\u0004\b\u000b\u0010\fJ\u0010\u0010\u0003\u001a\u00020\u0002HÖ\u0001¢\u0006\u0004\b\u0003\u0010\u0004J \u0010\t\u001a\u00020\b2\u0006\u0010\u0006\u001a\u00020\u00052\u0006\u0010\u0007\u001a\u00020\u0002HÖ\u0001¢\u0006\u0004\b\t\u0010\n¨\u0006\r"}, d2 = {"Lcom/project/example/parcelize/enums/Options$Delete;", "Lcom/project/example/parcelize/enums/Options;", "", "describeContents", "()I", "Landroid/os/Parcel;", "parcel", "flags", "", "writeToParcel", "(Landroid/os/Parcel;I)V", "<init>", "()V", "teleconsultationandroid_release"}, k = 1, mv = {1, 4, 1})
    /* compiled from: Options.kt */
    public static final class Delete extends Options {
        public static final Parcelable.Creator<Delete> CREATOR = new Creator();

        @Metadata(bv = {1, 0, 3}, d1 = {}, d2 = {}, k = 3, mv = {1, 4, 1})
        public static class Creator implements Parcelable.Creator<Delete> {
            @Override // android.os.Parcelable.Creator
            @NotNull
            public final Delete createFromParcel(@NotNull Parcel parcel) {
                Intrinsics.checkNotNullParameter(parcel, "in");
                if (parcel.readInt() != 0) {
                    return new Delete();
                }
                return null;
            }

            @Override // android.os.Parcelable.Creator
            @NotNull
            public final Delete[] newArray(int i) {
                return new Delete[i];
            }
        }

        public Delete() {
            super(R.drawable.background_icon, R.string.msg_xpto, R.id.card_view, null);
        }

        public int describeContents() {
            return 0;
        }

        public void writeToParcel(@NotNull Parcel parcel, int i) {
            Intrinsics.checkNotNullParameter(parcel, "parcel");
            parcel.writeInt(1);
        }
    }

    @Parcelize
    @Metadata(bv = {1, 0, 3}, d1 = {"\u0000 \n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\u0010\b\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0002\b\u0005\b\u0007\u0018\u00002\u00020\u0001B\u0007¢\u0006\u0004\b\u000b\u0010\fJ\u0010\u0010\u0003\u001a\u00020\u0002HÖ\u0001¢\u0006\u0004\b\u0003\u0010\u0004J \u0010\t\u001a\u00020\b2\u0006\u0010\u0006\u001a\u00020\u00052\u0006\u0010\u0007\u001a\u00020\u0002HÖ\u0001¢\u0006\u0004\b\t\u0010\n¨\u0006\r"}, d2 = {"Lcom/project/example/parcelize/enums/Options$Info;", "Lcom/project/example/parcelize/enums/Options;", "", "describeContents", "()I", "Landroid/os/Parcel;", "parcel", "flags", "", "writeToParcel", "(Landroid/os/Parcel;I)V", "<init>", "()V", "teleconsultationandroid_release"}, k = 1, mv = {1, 4, 1})
    /* compiled from: Options.kt */
    public static final class Info extends Options {
        public static final Parcelable.Creator<Info> CREATOR = new Creator();

        @Metadata(bv = {1, 0, 3}, d1 = {}, d2 = {}, k = 3, mv = {1, 4, 1})
        public static class Creator implements Parcelable.Creator<Info> {
            @Override // android.os.Parcelable.Creator
            @NotNull
            public final Info createFromParcel(@NotNull Parcel parcel) {
                Intrinsics.checkNotNullParameter(parcel, "in");
                if (parcel.readInt() != 0) {
                    return new Info();
                }
                return null;
            }

            @Override // android.os.Parcelable.Creator
            @NotNull
            public final Info[] newArray(int i) {
                return new Info[i];
            }
        }

        public Info() {
            super(R.drawable.background_icon, R.string.msg_xpto, R.id.card_view, null);
        }

        public int describeContents() {
            return 0;
        }

        public void writeToParcel(@NotNull Parcel parcel, int i) {
            Intrinsics.checkNotNullParameter(parcel, "parcel");
            parcel.writeInt(1);
        }
    }

    private Options(@DrawableRes int i, @StringRes int i2, @IdRes int i3) {
        this.iconResId = i;
        this.titleResId = i2;
        this.viewId = i3;
    }

    public final int getIconResId() {
        return this.iconResId;
    }

    public final int getTitleResId() {
        return this.titleResId;
    }

    public final int getViewId() {
        return this.viewId;
    }

    public /* synthetic */ Options(int i, int i2, int i3, DefaultConstructorMarker defaultConstructorMarker) {
        this(i, i2, i3);
    }
}

The change in kotlin 1.4.20 that was causing this issue, was this

            public final Object createFromParcel(@NotNull Parcel parcel) {
                ...
            }

            ...

            public final Object[] newArray(int i) {
                ...
            }

Turned in this

            public final Info createFromParcel(@NotNull Parcel parcel) {
                ...
            }

            ...

            public final Info[] newArray(int i) {
                ...
            }

And apparently xamarin don't deal very well with generics.

So, in theory, the solution would be

    <attr path="/api/package[@name='com.project.example.parcelize.enums']/class[@name='Options.Info.Creator']/method[@name='createFromParcel']" name="managedReturn">Java.Lang.Object</attr>
    <attr path="/api/package[@name='com.project.example.parcelize.enums']/class[@name='Options.Info.Creator']/method[@name='newArray']" name="managedReturn">Java.Lang.Object[]</attr>

    <attr path="/api/package[@name='com.project.example.parcelize.enums']/class[@name='Options.Delete.Creator']/method[@name='createFromParcel']" name="managedReturn">Java.Lang.Object</attr>
    <attr path="/api/package[@name='com.project.example.parcelize.enums']/class[@name='Options.Delete.Creator']/method[@name='newArray']" name="managedReturn">Java.Lang.Object[]</attr>

This was how I came up with the solution to my problem.