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)
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)
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!
Most helpful comment
Still needed!