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.
Related
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!
Ok, so I have three interface types.
Movement<T : Animal>
Animal with subinterfaces Cat, Dog, Horse
AnimalMovement
Movement Interface
interface Movement<T : Animal> {
fun moveAnimal(type:T)
}
Animal Interfaces
interface Animal {
fun takeSteps()
fun jump()
fun hide()
}
interface Cat : Animal
interface Dog : Animal
AnimalMovement
interface CatMovement : Movement<Cat>
I then implement the CatMovement interface
class CatMovementImpl : CatMovement {
override fun moveAnimal(type: Cat) {
TODO("not implemented")
}
}
Problem
fun TestGenerics() {
var catMovement : Movement<Cat> = CatMovementImpl() // this works
var catMovement : Movement<Animal> = CatMovementImpl() // this doesn't?
}
I am sure in Java both lines would have worked fine. However in Kotlin the second line fails to execute. Why would that be? An animal is the base type for cat, so this should have worked right?
I am not an expert of Kotlin, but that seems perfectly normal:
When declared like this:
var animalMovement : Movement<Animal>
You can write code:
animalMovement.moveAnimal(dog)
But if assigning it like this:
var animalMovement : Movement<Animal> = CatMovementImpl()
is allowed, it means that your CatMovementImpl should be able to move a dog ?
What moskito said in the comments is correct.
I am pretty sure that doesn't work in Java either. A Movement<Cat> is
NOT a subtype of Movement<Animal>, the same way a List is NOT
a subtype of List<Object>. You might want to read this.
But in Kotlin this is possible using type variance.
fun TestGenerics() {
var catMovement1: Movement<Cat> = CatMovementImpl()
var catMovement2: Movement<out Animal> = CatMovementImpl() // works
}
You basically tell the compiler "accept all implementations of Movement<Animal> or implementations Movement<S> for which S has Animal as upper bound".
But, then a problem arises. You cannot invoke
val cat: Cat = /* ... */
catMovement2.moveAnimal(cat) // error
giving you the error
Out-projected type Movement<out Animal> prohibits the use of [...].
because T can only be used as producer (out position) and not as consumer (in position) like this (function made up to demonstrate the point):
val c: Cat = catMovement2.getAnimal() // works
This problem becomes clear right away when you use out at the Movement declaration like this:
interface Movement<out T : Animal> {
fun moveAnimal(type: T) // error
}
It depends on your use case but maybe you should just let Kotlin infer the type, which would be CatMovementImpl.
var catMovement = CatMovementImpl()
Credit goes to EpicPandaForce for already suggesting using out in the comments.
I need to create a list of events with a string as name and a list of pairs as properties, some events properties are the static value, some need dynamically changed, so I create specific functions inside the enum entry to update it but complied with error unresolved reference:
Actually, what I want to implement is a list of enums with associated values,
something like these articles mentioned:
KT-4075 Allow setters overloading for properties, or
Kotlin: single property with multiple setters of different types, or
Using Kotlin’s sealed class to approximate Swift’s enum with associated data
Because I have more than 100 events, 95% of them are static, only several of them need to be updated during runtime, so sealed class might not suit my situation:
enum class Event(val eventName: String, vararg eventProperties: Pair<String, String?>) {
LOGIN_CLICKED("Login", ("View" to "button clicked")),
LOGIN_SUCCEED("Login", ("Type" to "succeed")),
LOGIN_ERROR("Login") {
fun errorMessage(errorMessage: String) {
eventProperties = listOf("ErrorType" to errorMessage)
}
},
// ... some other events
LIST_ITEM_CLICKED("LIST") {
fun listItemName(itemName: String) {
eventProperties = listOf("View" to itemName)
}
};
var eventProperties: List<Pair<String, String?>>? = listOf(*eventProperties)
// Although this approach can fix my problem, but I don't prefer it,
// because these functions are only meaningful to specific enum item,
// I don't want them be opened to all enum items.
//
// fun errorMessage(errorMessage: String) {
// eventProperties = listOf("ErrorType" to errorMessage)
// }
// fun listItemName(itemName: String) {
// eventProperties = listOf("View" to itemName)
// }
}
fun main(args: Array<String>) {
// unresolved reference
println(Event.LOGIN_ERROR.eventProperties)
Event.LOGIN_ERROR.errorMessage("error password")
println(Event.LOGIN_ERROR.eventProperties)
}
Because I have more than 100 events, 95% of them are static, only several of them need to be updated during runtime, so sealed class might not suit my situation
Why wouldn't it? If you are bothered with slightly longer declarations:
object LoginClicked : Event("Login", mapOf("View" to "button clicked"))
\\ vs
LOGIN_CLICKED("Login", mapOf("View" to "button clicked"))
you can create a helper enum class for them:
sealed class Event(val eventName: String, val eventProperties: Map<String, String?>) {
enum class Basic(val eventName: String, val eventProperties: Map<String, String?>) {
LOGIN_CLICKED("Login", mapOf("View" to "button clicked")),
LOGIN_SUCCEED("Login", mapOf("Type" to "succeed")),
...
}
class BasicEvent(b: Basic) : Event(b.eventName, b.eventProperties)
class LoginError(errorMessage: String) : Event("Login", mapOf("ErrorType" to errorMessage))
...
}
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>?)
}
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.