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.
Related
Am learning android kotlin follow this:
https://developer.android.com/topic/libraries/architecture/viewmodel#kotlin
class MyViewModel : ViewModel() {
private val users: MutableLiveData<List<User>> by lazy {
MutableLiveData<List<User>>().also {
loadUsers(it)
}
}
fun getUsers(): LiveData<List<User>> {
return users
}
private fun loadUsers() {
// Do an asynchronous operation to fetch users.
}
}
Dont know how to write the fun loadUsers()
Here is my User:
class User {
constructor(name: String?) {
this.name = name
}
var name:String? = null
}
If dont use the keyword 'also' , i know how to do it.
But if use 'also' , it seems not work.
Here is how i try to write the fun loadUsers:
private fun loadUsers( it: MutableLiveData<List<User>>){
val users: MutableList<User> = ArrayList()
for (i in 0..9) {
users.add(User("name$i"))
}
it = MutableLiveData<List<User>>(users)
}
Error tips near it : Val cant be ressigned
Part 1: According to the Kotlin documentation, also provides the object in question to the function block as a this parameter. So, every function call and property object you access is implied to refer to your MutableLiveData<List<User>>() object. also returns this from the function block when you are done.
Thus, another way of writing your MutableLiveData<> would be like this:
val users = MutableLiveData<List<User>>()
users.loadUsers()
Part 2: As far as how to implement loadUsers(), that is a separate issue (your question is not clear). You can use Retrofit + RxJava to load the data asynchronously, and that operation is totally outside of the realm of ViewModel or also.
Part 3: With your approach, you have conflicting things going on. Instead of doing a loadUsers() from your lazy {} operation, I would remove your lazy {} operation and create a MutableLiveData<> directly. Then, you can load users later on and update the users property any time new data is loaded. Here is a similar example I worked on a while ago. It uses state flows, but the idea is similar. Also use a data class to model the User instead of a regular class. Another example.
It is solved change to code:
private fun loadUsers( it: MutableLiveData<List<User>>){
val users: MutableList<User> = ArrayList()
for (i in 0..9) {
users.add(User("name$i"))
}
it.value = users
}
it can't be reassigned , but it.value could .
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.
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>?)
}
I'm struggling to understand and/or make Kotlin generics & polymorphism work for me. Consider this code:
class Item<T: BaseAttributes> {
var id: Long = -1L
lateinit var type: String
lateinit var attributes: T
}
open class BaseAttributes {
lateinit var createdAt: String
lateinit var updatedAt: String
}
open class BaseResponseList<T : BaseAttributes> {
lateinit var items: List<Item<T>> // the collection of items fetched from an API
}
class FruitAttributes(val id: Long, val color: String /* ... */) : BaseAttributes()
class FruitResponseList: BaseResponseList<FruitAttributes>()
// base service for all types of items
interface ApiService {
fun getItems(): BaseResponseList<BaseAttributes>
// fun getItemById(itemId: Long): BaseResponse<BaseAttributes>
/* other CRUD functions here ... */
}
// service for fruits
interface FruitService: ApiService {
override fun getItems(): FruitResponseList // get fruit items
}
I'm stumped by this compiler error that suggests FruitResponseList is not a subtype of the parametrized base class (BaseResponseList<FruitAttributes>):
Return type of 'getItems' is not a subtype of the return type of the overridden member 'public abstract fun getItems(): BaseResponseList<BaseAttributes> defined in ApiService'
I try to use declaration-site covariance in BaseAttributes to tell the compiler my intention that a FruitResponseList is a subclass of the base response list like this:
open class BaseResponseList<out T : BaseAttributes> {
lateinit var items: List<Item<T>> // the collection of items fetched from an API
}
leads to this error:
Type parameter T is declared as 'out' but occurs in 'invariant' position in type List<Item<T>>
How can I achieve the type-subtype relation between Fruit & Base response lists?
The Context
I'm implementing the networking code to perform CRUD operations against an API that's based on the JSON API spec format, thus I created the attributes and data (Item) classes to represent the json response objects.
My goal is to reduce the amount of duplicated code so that I only have to write the API service declarations once for every entity in my application (fruits, vendors, buyers, etc). I also want to avoid writing duplicated/boilerplate implementations of the data repository layers for every entity in my app (in the context of clean architecture). I should be able to just specify the business entity-specific types (models/entities) and let the one generic implementation do the work of fetching network data.
I thought it would make sense to use generics and inheritance to achieve this. In this particular example, the idea is that the fruit-specific GET would return a fruit response list, which is a subtype of the base response list. Will greatly appreciate any guidance on this, or alternative approaches to this problem
I'm stumped by this compiler error that suggests FruitResponseList is not a subtype of the parametrized base class (BaseResponseList<FruitAttributes>):
It is a subtype of BaseResponseList<FruitAttributes>, which isn't a subtype of BaseResponseList<BaseAttributes>.
I try to use declaration-site covariance in BaseAttributes to tell the compiler my intention that a FruitResponseList is a subclass of the base response list like this:...
This could be a correct approach, but the problem is that Item is not covariant (and it can't be because attributes is a var and its setter takes a T parameter). If Item can be modified to avoid this, good.
Another approach would be to add a type parameter to ApiService:
// base service for all types of items
interface ApiService<T: BaseAttributes> {
fun getItems(): BaseResponseList<T>
// fun getItemById(itemId: Long): BaseResponse<T>
/* other CRUD functions here ... */
}
// service for fruits
interface FruitService: ApiService<FruitAttributes> {
override fun getItems(): FruitResponseList // get fruit items
}
Why not something like this :
// base service for all types of items
interface ApiService<T> {
fun getItems(): T
// fun getItemById(itemId: Long): BaseResponse<BaseAttributes>
/* other CRUD functions here ... */
}
// service for fruits
interface FruitService: ApiService<FruitResponseList> {
override fun getItems(): FruitResponseList
}
or this :
// base service for all types of items
interface ApiService<T : BaseAttributes> {
fun getItems() : BaseResponseList<T>
// fun getItemById(itemId: Long): BaseResponse<BaseAttributes>
/* other CRUD functions here ... */
}
// service for fruits
interface FruitService: ApiService<FruitAttributes> {
override fun getItems(): FruitResponseList
}
You will be bounded to BaseResponseList<BaseAttributes> in your current implementation.
The answers provided by Mark and Alexey were very helpful. Particularly, parametrizing the ApiService on the BaseAttributes was the more flexible option since it allowed functions to take or return both BaseResponseList and BaseResponse [sub]types.
However, for what it's worth, it turns out that the Retrofit library does not allow its service declarations to extend other interfaces or even to be parametrized, supposedly in favor of composition over inheritance. There's plenty of debate about their decision on this issue here.
So I ended up creating separate interfaces for every one of my models :/
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.