Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin - Check to see if the generic parameter is optional or not?

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("")
        }
    }
}
like image 723
aryaxt Avatar asked Aug 19 '17 01:08

aryaxt


People also ask

How can I check for generic type in Kotlin?

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.

What is reified in Kotlin?

"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.

What is out T in Kotlin?

"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.

How do I call generic method Kotlin?

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.


1 Answers

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)
like image 134
mfulton26 Avatar answered Nov 15 '22 06:11

mfulton26