Generic Extension Property Receiver Type Mismatch - android

The following code:
package com.gitlab.morality.navigable.support
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentActivity
import kotlin.reflect.primaryConstructor
interface Navigable
val <FragmentType> FragmentType.parameters : MutableMap<String, in Any>
where FragmentType : Fragment,
FragmentType : Navigable
by lazy { mutableMapOf<String, Any>() }
inline fun <reified FragmentType>
FragmentType.navigate(containerViewId : Int, vararg parameters : Pair<String, Any>)
where FragmentType : Fragment,
FragmentType : Navigable {
val fragment = FragmentType::class.primaryConstructor!!.call()
parameters.forEach { fragment.parameters[it.first] = it.second }
activity.supportFragmentManager.beginTransaction()
.replace(containerViewId, fragment)
.commit()
}
Causes the following compiler errors with Kotlin 1.0.5:
Error:(34, 9) Public-API inline function cannot access non-public-API
'internal open fun (): [ERROR : ] defined in root package'
Error:(34, 18) Unresolved reference. None of the following candidates
is applicable because of receiver type mismatch: public val
FragmentType#1 (type parameter of
com.gitlab.morality.navigable.support.navigate).parameters:
MutableMap where FragmentType : Navigable defined in
com.gitlab.morality.navigable.support
I don't understand what I'm doing wrong here. What do these error messages mean?
I can work around the issue by instead making the parameters property a member of Navigable, but then an implementor is required to implement the property, which takes away from the attraction of this technique which is that an implementing class doesn't need to do anything special beyond being marked as Navigable to make use of Navigable functionality.

Extension properties with multiple receiver types are not supported at this time. You can vote for KT-10468 to get notified when the issue is fixed.

Related

How to set the exact type of kotlin property which has definite implementations?

I am trying to get a list of my enums.
val weatherTypes: List<Array<out Enum<*>>> = listOf(Atmosphere.values(), Clear.values(), ...)
All my enums are implementing StringOperation interface:
enum class Atmosphere(override val ...) : StringOperation { ... }
How can I specify the exact type implementation, like I did in function with where keyword?
fun <T> ItemsSelection(...) where T : StringOperation {...}
What do I want to get is the property with fixed type:
This doesn't work:
Thanks!

How to get class of generic type parameter in Kotlin

I would like get the class property from a generic type T.
I've decided to extend to Any but I'm getting an error.
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-any/index.html#extension-properties
I have the following code:
class FirebaseDBRepo<T : Any>(val child:String) {
private var callback: FirebaseDatabaseRepositoryCallback<T>? = null
private val ref: DatabaseReference
private val listener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
//T::class.java is showing the error cannot use t as reified type parameter use class instead
val gameDS = dataSnapshot.getValue(T::class.java)
callback!!.onSuccess(gameDS!!)
}
override fun onCancelled(databaseError: DatabaseError) {
}
}
init {
ref = FirebaseDatabase.getInstance().reference.child(child)
}
fun addListener(callback: FirebaseDatabaseRepositoryCallback<T>) {
this.callback = callback
ref.addValueEventListener(listener)
}
fun removeListener() {
ref.removeEventListener(listener)
}
}
You can only get the class on reified variables. The same thing happens in java, but with a slightly different message:
public <T> void x(){
T t = T.class.newInstance();
}
In Java, you'd solve this like:
public <T> void x(Class<T> cls){
T t = cls.newInstance();
}
The same applies to Kotlin, and any calls. You'd need to get a class instance in most cases. However, Kotlin supports reified generics using a keyword, but only on inline generic functions. You could pass a class, but in functions, it's really easy just using the reified keyword.
As in you can't declare a class with reified generics, which means this is invalid:
class SomeClass<reified T>
But it is valid for inline functions, meaning you can do:
inline fun <reified T> someFunction()
So you have two options. But since you extend a listener, the first option of adding the generics to the function isn't an option. You can't override a non-generic method with generics. It won't compile.
Which leaves the second option, which unfortunately is rather hackish; passing the class to the constructor. So it should look like this:
class FirebaseDBRepo<T : Any>(val child: String, private val cls: Class<T>) {
Now, I don't use Firebase, so I have no clue what classes you'd pass, so for this next example, I just use String.
Kotlin supports some type minimization without going over to raw types. This:
val t = FirebaseDBRepo<String>("", String::class.java)
Could be shortened to this:
val t = FirebaseDBRepo("", String::class.java)
The inferred type in both cases is FirebaseDBRepo<String>.
Since you are running on the JVM, type erasure is a thing.
This means (in simplified terms), that during compilation, the generics are simply ignored. Therefore, you cannot get the class of T, as the JVM doesn't even know what you mean by "T".
Kotlin uses a clever trick to come around this limitation in some cases. When you are using inline functions, the compiler does not call the function you defined, but instead, copies the whole body to the location where you called it. This can only be done for inline functions. Not classes.
There is a workaround tough: Just add private val classT: Class<T>
to the constructor and use the parameter instead!
Maybe it is too late but you could get the memory address from the generic class.
try to use:
object: GenericTypeIndicator<"T>() {}
to get the memory address from ur generic value.
It looks then so:
val gameDS = dataSnapshot.getValue(object: GenericTypeIndicator<"T">(){}
But you need to give your genericType without the ""
Maybe it is a solution for you.

How does kotlin inheritance work? and how can "*", "in" and "out" be used

I have the below
open class Model
class WorkOrder : Model()
//An interface
interface ViewInterface<T : Model> {
fun notifyDataSuccessful(model: T?, models:ArrayList<T>?)
}
class WorkOrderSystemImpl(val viewInterface: ViewInterface<Model>) {
fun doSomething() {
val workOrders: ArrayList<WorkOrder> = ArrayList()
//the below line complains of type mismatch
viewInterface.notifyDataSuccessful(workOrders)
}
}
It complains of type-mismatch which is quite strange to me, because WorkOrder is a sub-type of Model and i'd expect it to resolve to same type.
It's about the Generics's invariant & covariant, see Kotlin docs here.
In short, you can just remember:
Consumer in, Producer out!
which the Consumer & Producer is determined from the List's view, that means you should think about the role of you List, is it aConsumer or Producer? In your case, the models:ArrayList<T>? is a Producer, because it will be used by the implementation of ViewInterface, so you should define the ViewInterface like this:
interface ViewInterface<T: Model> {
fun notifyDataSuccessful(model: T?, models: ArrayList<out T>?)
}

Kotlin and generics confusion

I have some Drawers with generics:
abstract class BaseGeoDrawer<KEY : Any, GEO : Any, ITEM : Any>
abstract class BasePolygonDrawer<KEY : Any, ITEM : Any>: BaseGeoDrawer<KEY, Polygon, ITEM>
class TeamAreaDrawer : BasePolygonDrawer<String, Team>
abstract class BaseMarkerDrawer<KEY : Any, ITEM : Any> : BaseGeoDrawer<KEY, Marker, ITEM>
class TeamPositionDrawer : BaseMarkerDrawer<String, Team>
Then I have a controller that accept these Drawers, putting them in a ArrayList
private val drawers = ArrayList<BaseGeoDrawer<Any, Any, Any>>()
open fun addGeoDrawer(drawer: BaseGeoDrawer<Any, Any, Any>) {
drawers.add(drawer)
}
And later on calling methods in these Drawers
//Method in controller
private fun onMarkerClicked(marker: Marker): Boolean {
return drawers.any { it.onGeoClicked(marker) }
}
//Method in BaseGeoDrawer
fun onGeoClicked(geo: GEO): Boolean
The problem appear on this line
teamAreaDrawer = TeamAreaDrawer(this)
mapController.addGeoDrawer(teamAreaDrawer)
Android Studio will not allow it, telling me
Type mismatch.
Required: BaseGeoDrawer<Any, Any, Any>
Found: TeamAreaDrawer
I tried using out for drawers
private val drawers = ArrayList<BaseGeoDrawer<out Any, out Any, out Any>>()
But then onMarkerClicked will not compile, with the following error
Out-projected type BaseGeoDrawer<out Any, out Any, out Any> prohibits the use of 'public final fun onGeoClicked(geo: GEO) defined in mypackage.BaseGeoDrawer'
The problem here is that you need GEO as a contravariant type parameter in BaseGeoDrawer to use onGeoClicked(GEO) but ArrayList<BaseGeoDrawer<Any, Any, Any>> is invariant in its type. This means that you can't add anything else than a BaseGeoDrawer<Any, Any, Any>. If you try to use the types of BaseGeoDrawer as covariant it will not compile because you need it as contravariant when you call onGeoClicked(GEO).
Considering that until now in Kotlin a type parameter can't be bivariant, the only way to do it, is to do an unchecked cast.
In this specific case, you can do:
val teamAreaDrawer = TeamAreaDrawer(this) as BaseGeoDrawer<Any, Any, Any>
mapController.addGeoDrawer(teamAreaDrawer)
If you think about it, in Java, you would have done the same, because you would have had:
List<BaseGeoDrawer> drawers = new ArrayList<>();
public void example() {
TeamAreaDrawer teamAreaDrawer = new TeamAreaDrawer();
drawers.add(teamAreaDrawer);
// This is an unchecked cast.
drawers.get(0).onGeoClicked("Example");
}
I recommend you to read more about variance here.

Kotlin Property: "Type parameter of a property must be used in its receiver type"

I have the following simple Kotlin extension functions:
// Get the views of ViewGroup
inline val ViewGroup.views: List<View>
get() = (0..childCount - 1).map { getChildAt(it) }
// Get the views of ViewGroup of given type
inline fun <reified T : View> ViewGroup.getViewsOfType() : List<T> {
return this.views.filterIsInstance<T>()
}
This code compiles and works fine. But, I want the function getViewsOfType to be a property, just like the views. Android Studio even suggests it. I let AS do the refactoring and it generates this code:
inline val <reified T : View> ViewGroup.viewsOfType: List<T>
get() = this.views.filterIsInstance<T>()
But this code doesn't compile. It causes error: "Type parameter of a property must be used in its receiver type"
What is the issue here? Searching for help on this error doesn't seem to lead to an answer.
The error means that you can only have a generic type parameter for an extension property if you're using said type in the receiver type - the type that you're extending.
For example, you could have an extension that extends T:
val <T: View> T.propName: Unit
get() = Unit
Or one that extends a type that uses T as a parameter:
val <T: View> List<T>.propName: Unit
get() = Unit
As for why this is, I think the reason is that a property can't have a generic type parameter like a function can. While we can call a function with a generic type parameter...
val buttons = viewGroup.getViewsOfType<Button>()
... I don't believe a similar syntax exists for properties:
val buttons = viewGroup.viewsOfType<Button> // ??

Categories

Resources