TL;DR: How to correctly use a generic class with Firebase DataSnapshot.getValue()?
Use case: I want to implement a single generic remote datasource class for all my entities (a bunch of them) using Firebase. When listening to data change, I want to get the values from datasnapshot as an object of type E (its type determined elsewhere) but I don’t know if it is doable with Firebase query, like the following:
public class GenRemoteDataSource<E extends SomeClass> { //... public void onDataChange(DataSnapshot dataSnapshot) { E item = (E) dataSnapshot.getValue(); // <- unchecked cast and it doesn't work items.add(item); } }
For example, I have a Foo class that extends SomeClass, and the implementation of this GenRemoteDataSource with a class of Foo would be:
public class Foo extends SomeClass{} public class FooRemoteDataSource extends GenRemoteDataSource<Foo> { //... }
but Firebase throws a runtime error because instead of casting the getValue() as Foo, it would try to cast value as a upper bound SomeClass instead. I’m baffled as to why this happens:
Java.lang.ClassCastException: java.util.HashMap cannot be cast to com.example.app.SomeClass
please advise on how should I do this with Type-safety (No unchecked cast) . Thank you.
EDIT Stuff turned out to be irrelevant down below, see GenericTypeIndicator
EDIT
I’ve also tried (blindly and heck worth a try) the GenericTypeIndicator,
GenericTypeIndicator<E> mTypeIndicator = new GenericTypeIndicator<>(); E item = dataSnapshot.getValue(mTypeIndicator);
but it instead spits the following runtime error.
com.google.firebase.database.DatabaseException: Not a direct subclass of GenericTypeIndicator: class java.lang.Object
Advertisement
Answer
TL;DR my solution, (introduces more dependency than semantically necessary ?)
public class GenRemoteDataSource<E extends SomeClass> { //... private final Class<E> clazz; GenRemoteDataSource(Class<E> clazz) { this.clazz = clazz; } //... public void onDataChange(DataSnapshot dataSnapshot) { E item = dataSnapshot.getValue(clazz); // <- now is type safe. items.add(item); } }
I ended up just providing the Class definition to the getValue() via a constructor injection. This is done by passing a Class<T>
into the generic class’s constructor and sets it for Firebase’s getValue() to use as a instance parameter. Please comment if there are better solution, because to me this seems redundant. Thank You!