10

When using Kotlin to work with Firebase database, I can't seem to retrieve a value of type List<String> if I use a GenericTypeIndicator as follows:

snap.getValue(object : GenericTypeIndicator<List<String>>() {})

It produces an exception from the Firebase SDK as follows:

com.google.firebase.database.DatabaseException: Generic wildcard types are not supported
    at com.google.android.gms.internal.zzbtg.zza(Unknown Source)
    at com.google.android.gms.internal.zzbtg.zza(Unknown Source)
    at com.google.android.gms.internal.zzbtg.zza(Unknown Source)
    at com.google.android.gms.internal.zzbtg.zza(Unknown Source)
    at com.google.firebase.database.DataSnapshot.getValue(Unknown Source)

However, if I call it from Java, as follows, it works:

snap.getValue(new GenericTypeIndicator<List<String>>() {})

I was guessing that it has to do with type reifying, so I did this:

inline fun <reified T> genericType() = object: GenericTypeIndicator<T>() {}
val stringListIndicator = genericType<List<String>>()

snap.getValue(stringListIndicator)

but the same exception happened.

Why is it so?


Edit: I tried to decompile both Java and Kotlin versions using jadx-0.6.1.

Java source:

public class Randommmm {
    private static final GenericTypeIndicator<List<String>> ti = new GenericTypeIndicator<List<String>>() {
    };

    public static List<String> x(DataSnapshot snap) {
        return snap.getValue(ti);
    }
}

Decompiled:

public class Randommmm {
    private static final GenericTypeIndicator<List<String>> ti = new C12761();

    static class C12761 extends GenericTypeIndicator<List<String>> {
        C12761() {
        }
    }

    public static List<String> m48x(DataSnapshot snap) {
        return (List) snap.getValue(ti);
    }
}

Kotlin source (1):

object Randommmm {
    private val ti = object : GenericTypeIndicator<List<String>>() {
    }

    fun x(snap: DataSnapshot): List<String> {
        return snap.getValue(ti)
    }
}

Decompiled:

public final class Randommmm {
    public static final Randommmm INSTANCE = null;
    private static final Randommmm$ti$1 ti = null;

    static class C12761 extends GenericTypeIndicator<List<String>> {
        C12761() {
        }
    }

    static {
        Randommmm randommmm = new Randommmm();
    }

    private Randommmm() {
        INSTANCE = this;
        ti = new Randommmm$ti$1();
    }

    @NotNull
    public final List<String> m48x(@NotNull DataSnapshot snap) {
        Intrinsics.checkParameterIsNotNull(snap, "snap");
        Object value = snap.getValue(ti);
        Intrinsics.checkExpressionValueIsNotNull(value, "snap.getValue(ti)");
        return (List) value;
    }
}

public final class Randommmm$ti$1 extends GenericTypeIndicator<List<? extends String>> {
    Randommmm$ti$1() {
    }
}

Kotlin source (2) using ArrayList as suggested by Doug:

object Randommmm {
    private val ti = object : GenericTypeIndicator<ArrayList<String>>() {
    }

    fun x(snap: DataSnapshot): List<String> {
        return snap.getValue(ti)
    }
}

Decompiled:

public final class Randommmm {
    public static final Randommmm INSTANCE = null;
    private static final Randommmm$ti$1 ti = null;

    static class C12761 extends GenericTypeIndicator<List<String>> {
        C12761() {
        }
    }

    static {
        Randommmm randommmm = new Randommmm();
    }

    private Randommmm() {
        INSTANCE = this;
        ti = new Randommmm$ti$1();
    }

    @NotNull
    public final List<String> m48x(@NotNull DataSnapshot snap) {
        Intrinsics.checkParameterIsNotNull(snap, "snap");
        Object value = snap.getValue(ti);
        Intrinsics.checkExpressionValueIsNotNull(value, "snap.getValue(ti)");
        return (List) value;
    }
}

public final class Randommmm$ti$1 extends GenericTypeIndicator<ArrayList<String>> {
    Randommmm$ti$1() {
    }
}
Randy Sugianto 'Yuku'
  • 71,383
  • 57
  • 178
  • 228
  • I'm experiencing no problem with that Kotlin syntax. Type reifying is not the issue because you're creating a new object with runtime types, which effectively achieves the same thing as a reified type. EDIT: but I do have this problem with List instead of Foo.. weird. – Doug Stevenson Mar 02 '17 at 08:07

2 Answers2

20

In Kotlin,

val ti = object : GenericTypeIndicator<List<String>>() {}

is generated as (in Java):

SyntethicClass ti = new SyntethicClass();

public final class SyntethicClass extends GenericTypeIndicator<List<? extends String>> {}

Notice the wildcard ? extends String instead of just plain String.

See https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#variant-generics for explanation.

In order to prevent that, annotate the type parameter with @JvmSuppressWildcards:

val ti = object : GenericTypeIndicator<List<@JvmSuppressWildcards String>>() {}

This gets super ugly, but it works. Alternatively, use ArrayList as per Doug's answer.

Community
  • 1
  • 1
Randy Sugianto 'Yuku'
  • 71,383
  • 57
  • 178
  • 228
  • 1
    Thanks a lot, this helped. Just one tip, for Maps use the wildcard only for the value like `dataSnapshot.getValue(object : GenericTypeIndicator>() {})` – damienix Apr 06 '17 at 23:03
  • Thank you! I've added some typaliases to make it a bit less ugly: `typealias UsersMap = Map` `val userTypeIndicator = object : GenericTypeIndicator() {}` – treelzebub May 28 '17 at 21:12
  • 1
    Using the [Realtime Database Kotlin Extensions](https://firebaseopensource.com/projects/firebase/firebase-android-sdk/docs/ktx/database.md/) you'll no longer need to use `GenericTypeIndicator`. But you do need that annotation. It would be something like: `snap.getValue>()`. – Rosário Pereira Fernandes Nov 13 '19 at 21:48
5

Try changing this line:

snap.getValue(object : GenericTypeIndicator<List<String>>() {})

To this:

snap.getValue(object : GenericTypeIndicator<ArrayList<String>>() {})

It works for me, but I don't know why.

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
  • Thanks for suggesting `ArrayList`. It works when `ArrayList` is used. I tried to decompile the Java code, and both `List` and `ArrayList` version of Kotlin (I edited my question, results can be seen above). It seems that `List` got converted to `List extends String>`, but `ArrayList` stays as `ArrayList`. Then this seems like a Kotlin "problem". – Randy Sugianto 'Yuku' Mar 02 '17 at 11:05
  • I found the solution. I put it as an answer. – Randy Sugianto 'Yuku' Mar 02 '17 at 11:14
  • I had to add a not null assertion: `snapshot.getValue(object : GenericTypeIndicator>(){})!!` – inetphantom Apr 26 '20 at 09:33