How to define an interface in Kotlin that uses #DrawableRes annotations? - android

I want to create an interface to define a group of drawables needed for a mic state, something like:
interface MicrophoneState {
val iconResource: Int
val backgroundResource: Int
}
since it should be a resource id I wanted to decorated it with #DrawableRes but I get an error when I do that:
This annotation is not applicable to target 'member property without backing field or delegate'
Is there a way to fix that problem? all the question I have found are about classes but not interfaces.

As it's a state use of data class is ideal
data class MicrophoneState (
#DrawableRes val iconResource: Int,
#DrawableRes val backgroundResource: Int
)
or if you want to use interface
interface MicrophoneState {
#DrawableRes fun iconResource(): Int
#DrawableRes fun backgroundResource(): Int
}
Any one will work.
Update :
I think you are looking for this exactly :
interface MicrophoneState {
#get:DrawableRes val iconResource:Int
#get:DrawableRes val backgroundResource:Int
}

I don't think you need an interface as apparently you're trying to build a model to store data?
class HelloWorldClass (#DrawableRes val value1: Int, #DrawableRes val value2: Int)
(you can as well go with data class)
Interface is a contract for something:
interface HelloWorldInterface {
fun myMethod(#DrawableRes value: Int)
}

Related

Model class implementations for a generic solution

I have following project in Github : https://github.com/alirezaeiii/TMDb-Paging
I have a generic solution in my classes using TmdbItem interface, such as :
abstract class BaseFragment<T : TmdbItem> : BaseNavTypeFragment() {
protected abstract val viewModel: BaseViewModel<T>
protected lateinit var tmdbAdapter: TmdbAdapter<T>
}
TmdbItem interface and the class implementations are as follow :
interface TmdbItem : Parcelable {
val id : Int
val overview: String
val releaseDate: String?
val posterPath: String?
val backdropPath: String?
val name: String
val voteAverage: Double
}
#Parcelize
data class Movie(
override val id: Int,
override val overview: String,
#SerializedName("release_date")
override val releaseDate: String?,
#SerializedName("poster_path")
override val posterPath: String?,
#SerializedName("backdrop_path")
override val backdropPath: String?,
#SerializedName("title")
override val name: String,
#SerializedName("vote_average")
override val voteAverage: Double) : TmdbItem
#Parcelize
data class TVShow(
override val id: Int,
override val overview: String,
#SerializedName("first_air_date")
override val releaseDate: String?,
#SerializedName("poster_path")
override val posterPath: String?,
#SerializedName("backdrop_path")
override val backdropPath: String?,
override val name: String,
#SerializedName("vote_average")
override val voteAverage: Double) : TmdbItem
As you see I have to use #SerializedName in both classes even if the value is the same such as poster_path and backdrop_path. Is there anyway that I could write them in one place such as a base class instead of both class implementations?
That is probably not easily possible with Gson. The closest you could get to avoid duplicating the name is to define the property names somewhere as const val and then refer to them in #SerializedName, e.g. #SerializedName(POSTER_PATH_NAME).
Another solution which is however quite complicated and error-prone would be:
Place #SerializedName on the getter functions of the interface, e.g. #get:SerializedName("..."). This is possible because #SerializedName supports METHOD as target, but Gson only considers it on fields by default. Then write a custom FieldNamingStrategy which does the following:
Check if the field has a #SerializedName annotation, in that case return its value
Otherwise, obtain the Kotlin property (if any) for the Java field and obtain its getter method:
field.kotlinProperty?.javaGetter
Check if the getter has a #SerializedName, in that case return its value
Otherwise, go through the superclasses and then superinterfaces to check if any of them declare the getter (which is overridden) and have a #SerializedName
This is how it might work in theory; I have not tried to fully implement this yet. And as you can see the logic would be quite complex, so I am not sure if that would really be worth it. Also keep in mind that Gson directly reads field values and does not call getters during serialization. Therefore using #SerializedName on getters (especially if they have a custom implementation) might cause confusion.
Otherwise you might have to look for external Gson extensions which support getter methods, or other JSON libraries.

Is there a way to make extension function to data class type only?

My problem is that i want one extension function that works only on data classes.
for example lets say I have multiple data classes.
data class Person(
val name: String,
val age: Int
)
data class Car(
val color: Color,
val brand: String
)
and so on.
Now i want to create an extension function that is only extended for the data classes, not like this were i have to create extension function for each class.
for example my extension functions:
fun Person.convertToJson() : String {
return Gson().toJson(this)
}
fun Car.convertToJson() : String {
return Gson().toJson(this)
}
I want only one function that does the magic, also i don't want to use generic since it will be available to all objects. This is the generic function example:
fun <T> T.convertToJson() : String {
return Gson().toJson(this)
}
I want something equivalent to the generic function that will only work for data classes type.
There is no feature in the language to define an extension function on all data classes. if you can modify your data classes to implement an interface then you can use a marker interface for this as
/* define a marker interface and have all your data classes implement it */
interface Jsonable
data class Person(val name: String, val age: Int): Jsonable
data class Car(val color: Color, val brand: String): Jsonable
Now you can define the extension function on the interface as
fun Jsonable.convertToJson(): String = Gson().toJson(this)
and you can use it as
Person("A", 50).convertToJson()
I think you couldn't make an extension function to the data class type but you can overcome this by making an interface that your data class can extend and then make an extension for any class that implements the defined interface
interface IHuman
data class Person(val name: String, val age: Int): IHuman
data class Student(val name: String, val schoolName: String): IHuman
inline fun <reified T: IHuman> T.doSomething(){
println("Which one call me: ${T::class.java.simpleName}")
}
fun main(){
Person(name = "Mohamed", age = 28).doSomething()
Student(name = "Ahmed", schoolName = "MySchool").doSomething()
}
I hope below code will help to solve your problem without any Interface
ExtensionFunction:
inline fun <reified T : Any> T.objectToString(): String = Gson().toJson(this, T::class.java)
inline fun <reified T : Any> String.stringToObject(): T = Gson().fromJson(this, T::class.java)
Call to make:
Object.objectToString() // This will return string
String.stringToObject<Object>() // This will return Object

Abstract val with annotation in kotlin android

Can I write:
#IdRes
abstract fun getHeaderId(): Int
With a val instead of a fun in kotlin? It complains I need a backing field or delegate when i write:
#IdRes <-- errors
abstract val headerId: Int
Which is the most idiomatic in this case? One-liner with a fun or mess around with a backing field (I'm not used to backing fields, maybe it's change-resistance, i have never really used them so i think they are unpleasant)
Since abstract val or var is just a function without a backing field it cannot be annotated by IdRes annotation but there is a workaround. You can use it like this:
#get:IdRes
abstract val headerId: Int
EDIT:
Why does this works? We need to closer inspect IdRes annotation and its source code:
#Documented
#Retention(CLASS)
#Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
public #interface IdRes {
}
As we can see this annotation can be used on methods, parameters, fields and local variables. When we use abstract val it's neither of those since it is abstract and we cannot have abstract fields in Java. Normally equivalent of abstract val something: Int in Java is:
private int something
public int getSomething() {
return something;
}
From example, it's easy to see that the private field is what is called backing field of a property and you can't have those as abstract so that was the problem.
As mentioned in #AtulGupta comment, #theKarlo 's answer does not force the subclass to pass in an IdRes.
Therefore, an alternative to
#IdRes
abstract fun getHeaderId(): Int
and
#get:IdRes
abstract val headerId: Int
Is to pass the value into the constructor of the class itself, so that the backing field issue can be avoided and the subclass is forced to pass in an IdRes.
For example:
abstract class SomeClass(#IdRes val idRes: Int)

Kotlin Inheritance with Boolean generics

I have a problem when I try to override a generics method with Boolean,Double,Integer,Float.
It works with Date. (May because is it Serializable?)
The interface:
interface AnInterface<C, T> {
fun doSomething(items: List<T>, vararg value: C): List<T>
}
An abstact implementation: (No override doSomething)
abstract class BaseClass<C, T> : AnInterface<C, T> { ... }
It's work:
class AnImplementetion<T> : BaseClass<Date, T>() {
override fun doSomething(items: List<T>, vararg value: Date): List<T> {
// It works
}
}
It doesn't work:
class AnAnotherImplementetion<T> : BaseClass<Boolean, T>() {
override fun doSomething(items: List<T>, vararg value: Boolean): List<T> {
// It doens't
}
}
The IDE always want to implement the doSomething. When I implement it with IDE it creates always the same one.
Error message:
Class 'AnAnotherImplementetion' is not abstract and does not implement abstract base class member
public abstract fun fun doSomething(items: List<T>, vararg value: Boolean): List<T> defined in BaseClass
'doSomething' overrides nothing
How can I fix it?
Thank you
UPDATE:
It works with JAVA. But Why doesn't with Kotlin?
public class AnAnotherImplementetion<T> extends BaseClass<Boolean, T> {
#NotNull
#Override
public List<T> doSomething(#NotNull List<? extends T> items, Boolean... value) {
// It works with JAVA
}
}
UPDATE 2:
It works when vararg is nullable.
interface AnInterface<C, T> {
fun doSomething(items: List<T>, vararg value: C?): List<T>
}
It looks like a bug in kotlin compiler. As I know during compiling it decides to use primitive type (int) or wrap (Integer). Java generics can't work with primitives, so compiler uses wrap for generic-type, BUT then compiler sees, that method param is never null and replaces it with primitive-type, and type-conflict appears. And here nullable saves a day.
But I'm not sure, it's just a guess.
The kotlin reference regarding basic types contains a passage which explains how it should deal with primitive types and generics in particular.
Obviously the latter is not working correctly. When generics are involved it should box the types which it either doesn't do or which the compiler complains about falsely.
You should open a bug at https://youtrack.jetbrains.com/ and link it here too. Maybe it was also a conscious design decision.
A workaround is to use the nullable type Boolean? as that will work as it is described in the reference. It will be boxed and therefore will work with generics.
Alternatively, if you are on the JVM, you can use the java.lang.Boolean instead. It's the object type of the primitive boolean and even though it is discouraged to use the Java types in Kotlin it is a possible workaround until Kotlin behaves as it should. However... testing it, Kotlin does some more magic around it so that its usage isn't that helpful neither. You would then even need to cast the java.lang.Boolean.TRUE as java.lang.Boolean. That's clearly not helpful at all. Opening a bug is the best you can do here.

Kotlin One type argument expected for class for abstract generic view holder

I'm trying to create a RecyclerView.Adapter with the following:
View holders - Provided the abstract parent and one son
abstract class BaseSettingsViewHolder<T>(var viewDataBinding :
ViewDataBinding) : RecyclerView.ViewHolder(viewDataBinding.root) {
abstract fun onBind(data: T, presenter: ISettingsPresenter, position: Int)
}
class SettingsTitleViewHolder(viewDataBinding: ViewDataBinding) : BaseSettingsViewHolder<TitleData>(viewDataBinding) {
override fun onBind(data: TitleData, presenter: ISettingsPresenter, position: Int) {
viewDataBinding.setVariable(BR.titleData, data)
viewDataBinding.setVariable(BR.itemPosition, position)
viewDataBinding.setVariable(BR.settingsPresenter, presenter)
viewDataBinding.executePendingBindings()
}
}
And when trying to declare the adapter:
class SettingsAdapter(var context: Context, var presenter: ISettingsPresenter) : RecyclerView.Adapter<BaseSettingsViewHolder>() {
I'm getting "One type argument expected for class" compile error on the:
RecyclerView.Adapter<BaseSettingsViewHolder>
Appreciate the help!
1- If you use always SettingTitleViewHolder;
RecyclerView.Adapter<SettingTitleViewHolder>
2- If you want to use any class which extended BaseSettingViewHolder;
RecyclerView.Adapter<BaseSettingsViewHolder<*>>
3- Use Any Object Type
RecyclerView.Adapter<BaseSettingsViewHolder<Any>>
4- Use extended type parameter for adapter class
class SettingsMenuAdapter<T>:RecyclerView.Adapter<T> where T : BaseSettingViewHolder<*>
5- Define out for abstract class type parameter.
abstract class BaseSettingsViewHolder<out T> RecyclerView.Adapter<BaseSettingsViewHolder<Any>>
7- Read that article =>
"Generics in Kotlin" ;)
You should specify the type argument for the BaseSettingsViewHolder in the RecyclerView.Adapter<BaseSettingsViewHolder> type.
Kotlin, unlike Java, does not have the raw types, so you cannot simply omit the type arguments.
The closest concept to raw types is star-projected types: use BaseSettingsViewHolder<*> in RecyclerView.Adapter<BaseSettingsViewHolder<*>>, this will mean that the type argument for BaseSettingsViewHolder is unknown.

Categories

Resources