Firebase-android-sdk: GenericTypeIndicator missing from firestore library but required

Created on 30 Jan 2019  ·  3Comments  ·  Source: firebase/firebase-android-sdk

[REQUIRED] Step 2: Describe your environment

  • Android Studio version: _3.2.1_
  • Firebase Component: _firestore_
  • Component version: _17.1.5_

[REQUIRED] Step 3: Describe the problem

Steps to reproduce:

What Happened:
When you try to retrieve a object and turn it to a object with a generic with type parameters EX( a Map/HashMap) you get the error Could not deserialize object. Class java.util.HashMap has generic type parameters, please use GenericTypeIndicator instead
What would normally happen is using GenericTypeIndicator to define the types but that class isn't included with the firebase-firestore library for android and is only included in the firebase-database (com.firebase.client.GenericTypeIndicator) android library.

To Reproduce:
Create a collection and a document with values ex:
{
"dude":"wow",
"whatever":32,
}
Retrieve item and convert to object with a with a generic map documentSnapshot.toObject(typeParameterClass)
Ex.: documentSnapshot.toObject(HashMap.class)

Relevant Code:

    java.lang.RuntimeException: Could not deserialize object. Class java.util.HashMap has generic type parameters, please use GenericTypeIndicator instead
        at com.google.firebase.firestore.util.CustomClassMapper.deserializeError(com.google.firebase:firebase-firestore@@17.1.5:524)
        at com.google.firebase.firestore.util.CustomClassMapper.deserializeToClass(com.google.firebase:firebase-firestore@@17.1.5:232)
        at com.google.firebase.firestore.util.CustomClassMapper.convertToCustomClass(com.google.firebase:firebase-firestore@@17.1.5:97)
        at com.google.firebase.firestore.DocumentSnapshot.toObject(com.google.firebase:firebase-firestore@@17.1.5:203)
        at com.google.firebase.firestore.DocumentSnapshot.toObject(com.google.firebase:firebase-firestore@@17.1.5:183)
        at // My package
        at // My package
        at com.google.firebase.firestore.DocumentReference.lambda$addSnapshotListenerInternal$2(com.google.firebase:firebase-firestore@@17.1.5:541)
        at com.google.firebase.firestore.DocumentReference$$Lambda$3.onEvent(Unknown Source:6)
        at com.google.firebase.firestore.util.ExecutorEventListener.lambda$onEvent$0(com.google.firebase:firebase-firestore@@17.1.5:42)
        at com.google.firebase.firestore.util.ExecutorEventListener$$Lambda$1.run(Unknown Source:6)
        at android.os.Handler.handleCallback(Handler.java:790)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6494)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
firestore feature request

Most helpful comment

Still needed!

All 3 comments

Thanks for pointing this out!

For the moment you can work around this in a few ways:

1) In this specific case, documentSnapshot.getData() will return a Map<String, Object> containing the values you want ("wow" and 32). Of course you're likely interested in getting a Map of more interesting complex types and this doesn't handle that.

2) If you know the keys ahead of time, you can create a simple wrapper struct

class DudeWhateverHolder {
  public String dude;
  public int whatever;
}

snapshot.toObject(DudeWhateverHolder.class);

3) Alternatively, get(String, Class<T>) methods on the snapshot will also pull out complex values:

String dude = snapshot.get("dude", String.class);
int whatever = snapshot.get("whatever", Integer.class);

4) If you have some specific homogeneous type you're trying to load then you can declare a subclass of the map you want like so:

class DudeWhateverMap extends HashMap<String, MyClass> {}

// Usage
snapshot.toObject(DudeWhateverMap.class);

5) If you have lots of these you can also emulate the missing toObject(new GenericTypeIndicator<Map<String, MyClass>>() {}) with something like this:

public static <T> Map<String, T> toMapWithValues(Class<T> valueClass, DocumentSnapshot snapshot) {
  Map<String, T> result = new HashMap<>();
  for (String key : snapshot.getData().keys()) {
    T value = snapshot.get(key, valueClass);
    result.put(key, value);
  }
  return result;
}

// Usage
toMapWithValues(MyClass.class, snapshot);

Of course built-in support would be better, but the error message is erroneously pointing to a feature that doesn't exist rather than just a missing class. Fixing this isn't trivial and will require an additional public API review process so it won't happen too quickly.

Note that for heterogeneous values like those in your example (String and Integer), GenericTypeIndicator won't really help. The common supertype of values in your example would be Object and passing toObject(new GenericTypeIndicator<Map<String, Object>>() {}) would be essentially equivalent to calling getData().

Oh, and to be clear, I've added this to our backlog, but given the available workarounds we'll prioritize this along with other feature work.

Still needed!

Was this page helpful?
0 / 5 - 0 ratings