I am writing this generic method to fetch data from firebase? In some cases getting null back is valid, in other cases is not, is it possible to check to see if the generic param is nullable or not?
ex
reference.obsrveObject(User.class)
should throw if null
reference.obsrveObject(User?.class)
should call onNext with null value
fun DatabaseReference.observeSingleEvent(): Observable<DataSnapshot?> {
return Observable.create { subscriber ->
val valueEventListener = object: ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot?) {
subscriber.onNext(snapshot)
subscriber.onCompleted()
}
override fun onCancelled(error: DatabaseError?) {
subscriber.onError(FirebaseDatabaseThrowable(error))
}
}
addListenerForSingleValueEvent(valueEventListener)
}
}
fun <T>DatabaseReference.obsrveObject(clazz: Class<T>): Observable<T> {
return observeSingleEvent().map { snapshot ->
if (snapshot != null) {
snapshot.getValue(clazz)
}
else {
// if clazz is nullable return null
// if clazz is not nullabel throw
throw Exception("")
}
}
}
There are no direct ways to do this in Kotlin. In order to check the generic type, we need to create an instance of the generic class<T> and then we can compare the same with our class.
"reified" is a special type of keyword that helps Kotlin developers to access the information related to a class at runtime. "reified" can only be used with inline functions. When "reified" keyword is used, the compiler copies the function's bytecode to every section of the code where the function has been called.
"Out" keyword is extensively used in Kotlin generics. Its signature looks like this − List<out T> When a type parameter T of a class C is declared out, then C can safely be a super type of C<Derived>. That means, a Number type List can contain double, integer type list.
Kotlin generic example When we call the generic method <T>printValue(list: ArrayList<T>) using printValue(stringList), the type T of method <T>printValue(list: ArrayList<T>)will be replaced by String type.
Note: I've not personally used Firebase yet so some of the examples below may not compile but should be close enough.
Neither Class<T>
nor KClass<T>
track nullability as they only represent a class. A KType
, however, can represent "a class with optional type arguments, plus nullability" and has an isMarkedNullable
property.
You can use reified type parameters to get a KClass
for the generic type but you cannot get (as of Kotlin 1.1) a KType
. However, you can still check to see if the generic type is nullable (nullable reified type : Kotlin) with null is T
(thanks to sosite for pointing out that wrapping null as T
in a try
/catch
is not necessary).
With this, as long as you can mark obsrveObject
as inline
, you can "check to see if the generic parameter is optional or not":
inline fun <reified T> DatabaseReference.obsrveObject(): Observable<T> {
return observeSingleEvent().map { snapshot ->
if (snapshot != null) {
snapshot.getValue(T::class.java)
} else if (null is T) {
null as T
} else {
throw Exception("")
}
}
}
Usage:
databaseReference.obsrveObject<User>() // Observable<User>
databaseReference.obsrveObject<User?>() // Observable<User?>
If you cannot use an inline function (and therefore reified type parameters) then you need to find a way to get a KType
.
You can get a KType
from the returnType
on KCallable<R>
but you can also create a KType
from a KClass<T>
using createType
:
User::class.createType(nullable = false) // User
User::class.createType(nullable = true) // User?
The class here is the type's classifier
so depending on how you are using obsrveObject
you might change its argument type from Class<T>
to KCallable<T>
. You could change it to a KType
directly and create instances as needed but I'm guessing your grabbing clazz
currently from the return type of a property so I would go with KCallable<T>
:
fun <T : Any> DatabaseReference.obsrveObject(callable: KCallable<T>): Observable<T?> {
val kType = callable.returnType
val kClass = kType.classifier as KClass<T>
val clazz = kClass.java
return observeSingleEvent().map { snapshot ->
if (snapshot != null) {
snapshot.getValue(clazz)
} else if (kType.isMarkedNullable) {
null
} else {
throw Exception("")
}
}
}
You would then call it using a reference to a callable (property, function, etc.):
databaseReference.obsrveObject(session::user)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With