How do I use an enum within a kotlin data class - kotlin

I have this model
data class HourlyModel(
val time: String,
#DrawableRes val image: Int,
val temp: Double
)
I realized that the server will provide me with weather codes which translate to the icon that will be displayed. I think if I pull the #DrawableRes into an enum, it maybe better because I have a model for Today's Weather and WeeklyWeather forecasts.
All 3 models will be using the same weather codes.
I am new to Kotlin but I think if I have an enum class, I should be able to somehow use this within each model
enum class WeatherTypes (
val weatherCode: Int,
#DrawableRes val drawable: Int
) {
SUNNY(0, R.drawable.sunny_t),
RAIN(1,R.drawable.rain_t);
companion object {
fun weatherToImage(weatherCode: Int) = when(weatherCode) {
0 -> SUNNY
1 -> RAIN
else -> SUNNY
}
}
}
Can someone help me out and tell me what I should do to my model to use this enum class to replace the #DrawableRes? if I can't then what is the best option for me?

I'll assume you have different models for different layers. Let's say you have a data class for the data you receive from the server.
data class HourlyDto(
val time: String,
val weatherCode: Int,
val temp: Double,
)
Your domain model will be something like so:
data class HourlyModel(
val time: String,
val weatherType: WeatherType,
val temp: Double,
)
I've refactored your enum class:
enum class WeatherType(
#DrawableRes val imageResId: Int,
) {
SUNNY(R.drawable.sunny_t),
RAIN(R.drawable.rain_t);
companion object {
fun from(weatherCode: Int): WeatherType {
return WeatherType.values()[weatherCode]
}
}
}
Now, you can map your remote model to your domain model. Let's create an extension function for doing that( for the sake of example. You may be doing it another way )
fun HourlyDto.mapToModel(): HourlyModel {
return HourlyModel(
time,
WeatherType.from(weatherCode),
temp
)
}
Finally, you can use you can get your drawable resource-id like so:
val hourlyWeather: HourlyModel = ...
hourlyWeather.weatherType.imageResId
Note: This answers the question of how to use the enum in your model class, but I guess to solve this specific issue you could use original model(with drawable resource-id) and then create a helper function that takes in weathercode and returns the drawable resource-id and reuse that wherever required.

Related

Difference between object and companion object and how to test them

I have a data class PersonRecord. But the data I receive from an API has different form, I need to process it in order to extract A.
The first solution consist of creating a data class PersonForm to represent the API-data and then create an independent function that take into parameters an instance of class PersonForm and returns an instance of class PersonRecord.
Looking at some stackoverflow posts, I have also found the following solutions :
2.
data class PersonRecord(val name: String, val age: Int, val tel: String){
object ModelMapper {
fun from(form: PersonForm) =
PersonRecord(form.firstName + form.lastName, form.age, form.tel)
}
}
Same as two but with companion object instead of object.
Is there a way that is more idiomatic/efficient/natural etc ? In which context, each one is preferred ?
Thanks.
The most idiomatic/natural way is creating secondary constructor:
data class PersonRecord(val name: String, val age: Int, val tel: String) {
constructor(form: PersonForm) : this(form.firstName + form.lastName, form.age, form.tel)
}

Convert attributes from one data class to another data class (subclasses)

hello) . Need some help.I know how to transfer data from A class to B class, but i dont know how to transfer data from subclass A.A1 - which included A class, to subclass B.B1 which included B class.
My A class:
data class SpaceXProperty(
val flight_number: Int,
val launch_year: Int,
val links: Links
){
data class Links(val mission_patch: String?)
}
My B class:
data class Models(val flightNumber: Int,
val mlinks: Mlinks
)
{
data class Mlinks(val mission_patch: String?)
}
I created next function
fun List<SpaceXProperty>.asDomainModel(): List<Models>{
return map {
Models(
flightNumber = it.flight_number,
llinks = it.links //here i got error type mismatch "required: Models.Mlinks found: SpaceXProperty.Links"
)
}
}
So thats to avoid this - i change B class to next
data class Models(val flightNumber: Int,
val llinks: SpaceXProperty.Links
)
I new at kotlin, so dont know how correctly mapping data from one subclass to another. Help please.
i trying to put next function at fun List.asDomainModel(), but its dont work.
fun List<SpaceXProperty.Links>.asDDomainmodel(): List<Models.Llinks>{
return map {
Models.Llinks(
mission_patch = it.mission_patch) }
}
Totally i getting data from "https://api.spacexdata.com/v3/launches" and want paste this data to another data class like "repository". Here Moshi cannot parse nullable i get help that i need to use subclass, but at next step i got problems.
Couple of minor errors:
In the first asDomainModel function you used llinks property instead of mlinks
Trying to assign object of Links to Mlinks
Following code resolves the issue
fun List<SpaceXProperty>.asDomainModel() = map {
Models(
flightNumber = it.flight_number,
mlinks = Models.Mlinks(it.links.mission_patch)
)
}
The original classes stays as is
data class SpaceXProperty(
val flight_number: Int,
val launch_year: Int,
val links: Links) {
data class Links(val mission_patch: String?)
}
data class Models(val flightNumber: Int, val mlinks: Mlinks) {
data class Mlinks(val mission_patch: String?)
}

Is there a way to "disable" the primary constructor or force the user to always use a secondary constructor?

Example:
data class Car (
val type: TypeEnum,
val brand: BrandEnum,
val modelNumber: Int)
{
constructor(val type: TypeEnum,
val brand: BrandEnum,
val input: String) : this (
type,
brand,
Valdidator.validateModelNumber(input)
)
}
In the code above, the method validateModelNumber() validates a raw input and throws an exception if the model number has an invalid format. I want to force the user to use this constructor every time he/she wants to make a Car object.
Essentially: I want to make sure that no invalid Car object can exist, while still making the code as immutable as possible.
You could use the init block instead. Something like this
data class Car (
val type: TypeEnum,
val brand: BrandEnum,
val modelNumber: Int)
{
init {
Valdidator.validateModelNumber(input)
}
}
Using an init block for validation (as per another answer) can work well if it only needs the parameters/properties specified in the primary constructor.  However, there are other approaches.
If you don't want the primary constructor to be used by other code, you can make it private, by changing:
data class Car(
to:
data class Car private constructor(
You could then leave a public secondary constructor for other classes to use, as in the question.  However, that's still a bit limiting, as you can't do any serious processing before calling the primary constructor.
So the usual pattern is to have a private constructor and factory methods in the companion object.  This is much more flexible: you can do any amount of processing before and after calling the actual constructor; you can even return cached instances, subclass instances, etc.
You can make those look like constructors by implementing them as operator fun invoke() with suitable parameters.  In this case, that could look like:
data class Car private constructor(
val type: TypeEnum,
val brand: BrandEnum,
val modelNumber: Int)
{
companion object {
operator fun invoke(type: TypeEnum, brand: BrandEnum, input: String)
= Car(type, brand, Validator.validateModelNumber(input))
}
}
You could then create instances with e.g.:
Car(TypeEnum.SPORTS, BrandEnum.ASTON_MARTIN, "DB5")
looking just like an ordinary constructor.

Why should I implement a function type as an interface in Kotlin

I came across something and wondered all the time why you should do this.
You implement an interface in Kotlin through a simple function type:
"It is possible for a class to implement a function type as if it were an interface. It must then supply an operator function called invoke with the given signature, and instances of that class may then be assigned to a variable of that function type:"
class Divider : (Int, Int) -> Double {
override fun invoke(numerator: Int, denominator: Int): Double = ...
}
But why should I do this? Why should I add an interface in that way? I think its only possible to add one function and not more.
Or is it an advantage that I can implement a function with a function body and not only the function head like in normal interfaces? I think it is possible in Java to add default methods to interfaces with a function body. So maybe it is something like that?
Function as a class can have state. For example you could store the last invocations and use the history as a cache:
class Divider : (Int, Int) -> Double {
val history = mutableMapOf<Pair<Int, Int>, Double>()
override fun invoke(numerator: Int, denominator: Int): Double {
return history.computeIfAbsent(Pair(numerator, denominator)) {
numerator.toDouble() / denominator.toDouble()
}
}
}
fun main() {
val divider = Divider()
println(divider(1,2))
println(divider(2,3))
println(divider.history)
}
It is probably not very useful to write a class that only implements a function type interface; however, it might be useful to write a class that can among other things be used in place of a function.
An example from the standard library is the KProperty1 interface. You can write code like this:
data class C(val id: Int, val name: String)
val objs = listOf(C(1, "name1"), C(2, "name2"), C(3, "name3"))
val ids = objs.map(C::id)
Here, C::id is a property reference of type KProperty1<C, Int>, and it can be used as an argument to List.map in place of a lambda because KProperty1<C, Int> extends (C) -> Int. However, KProperty1 has a lot of other uses besides being passed as a function.

Jackson deserialization - Kotlin data classes - Defaults for missing fields per mapper

Given this data class:
data class MyPojo(val notInJson: Int, val inJson: Int)
Assume I want to implement a function of the form:
fun deserialize(jsonString: String, valueForFieldNotInJson: Int): MyPojo
Where jsonString does not include a field named notInJson. Assume also, that I have no control over MyPojo class definition.
How could I use Jackson library to deserialize MyPojo from jsonString and augment the missing field (notInJson) from valueForFieldNotInJson parameter?
Notes:
Basically, the question is about deserializing a Immutable class, where some fields come from Json and others are supplied at runtime.
Using custom deserializers or builders will not work because missing values are unknow at compile time.
This can be achieved by combining MinInAnnotations and ValueInjection.
Complete solution as follows:
data class MyPojo(val notInJson: Int, val inJson: Int)
class MyPojoMixIn {
#JacksonInject("notInJson") val notInJson: Int = 0
}
fun deserialize(jsonString: String, valueForFieldNotInJson: Int): MyPojo {
val injectables = InjectableValues.Std().addValue("notInJson", valueForFieldNotInJson)
val reader = jacksonObjectMapper()
.addMixIn(MyPojo::class.java, MyPojoMixIn::class.java)
.readerFor(MyPojo::class.java)
.with(injectables)
return reader.readValue(jsonString)
}