Kotlin data class optional variable - kotlin

data class Student(
val id: Int?,
val firstName: String?,
val lastName: String?,
val hobbyId: Int?,
val address1: String?,
val address2: String?,
val created: String?,
val updated: String?,
...
)
I have like above data class, and I want to create a Student instance with only first name and last name.
So If I run this,
// creating a student
Student(
firstName = "Mark"
lastName = "S"
)
I will get No value passed for parameter 'id' ... errors.
To avoid that, I modified the Student class like this,
data class Student(
val id: Int? = null,
val firstName: String? = null,
val lastName: String? = null,
val hobbyId: Int? = null,
val address1: String? = null,
val address2: String? = null,
val created: String? = null,
val updated: String? = null,
...
)
But it looks so ugly.
Is there any better way?

You can set default values in your primary constructor as shown below.
data class Student(val id: Int = Int.MIN_VALUE,
val firstName: String,
val lastName: String,
val hobbyId: Int = Int.MIN_VALUE,
val address1: String = "",
val address2: String = "",
val created: String = "",
val updated: String = "")
Then you can use named arguments when creating a new student instance as shown below.
Student(firstName = "Mark", lastName = "S")

I am not sure the solution I am giving you is the best or not. But definitely neat.
The only thing I don't like to go with nulls as default param, because Kotlin offers Null Safety, lets not remove it just because to fulfil some other requirement. Mark them null only if they can be null. Else old Java way is good. Initialize them with some default value.
data class Student(val id: Int,
val firstName: String,
val lastName: String,
val hobbyId: Int,
val address1: String,
val address2: String,
val created: String,
val updated: String) {
constructor(firstName: String, lastName: String) :
this(Int.MIN_VALUE, firstName, lastName, Int.MIN_VALUE, "", "", "", "")
}

data class InLog(
var clock_in_lat: String?="None",
var clock_in_lng: String?="None",
var clock_out_lat: String?="None",
val clock_out_lng: String?="None",
val created_at: String?="None",
val duration: String?="None",
val end_time: String?="None",
val id: Int?=Int.MIN_VALUE,
var late_duration: String? = "None",
val start_time: String?="None",
val type: String?="None",
val updated_at: String?="None",
val user_id: Int?=Int.MIN_VALUE)
in Kotlin we do like that remeber ? mark symbol use.

Related

Why is MapStruct warning this is an unmapped property

I am employing mapstruct to map between my network and database data objects within my current Android project.
api 'org.mapstruct:mapstruct:1.5.2.Final'
kapt 'org.mapstruct:mapstruct-processor:1.5.2.Final'
my network data class resembles this
#Keep
#Serializable
data class Item(
#SerialName("actionTested")
val actionTested: String? = null,
#SerialName("agonistAntagonist")
val agonistAntagonist: String = "",
#SerialName("cellOrigin")
val cellOrigin: String? = null,
#SerialName("cmmnt")
val comment: String? = null,
#SerialName("displayDose")
val displayDose: String = "",
#SerialName("displayValue")
val displayValue: String = "",
#SerialName("document")
val document: Document = Document(),
#SerialName("documentYear")
val documentYear: Int = 0,
#SerialName("dose")
val dose: String = "",
#SerialName("doseUnit")
val doseUnit: String? = null,
#SerialName("drug")
val drug: String = "",
#SerialName("errorType")
val errorType: String? = null,
#SerialName("errorValue")
val errorValue: String? = null,
#SerialName("experimentType")
val experimentType: String? = null,
#SerialName("hash")
val hash: String = "",
#SerialName("id")
val id: String = "",
#SerialName("isPrimaryTarget")
val isPrimaryTarget: String? = null,
#SerialName("mutationDetail")
val mutationDetail: String? = null,
#SerialName("otherExpDetails")
val otherExpDetails: String? = null,
#SerialName("parameter")
val parameter: String = "",
#SerialName("sharpN")
val sharpN: String? = null,
#SerialName("smiles")
val smiles: String? = null,
#SerialName("source")
val source: String = "",
#SerialName("specie")
val specie: String = "",
#SerialName("strainRace")
val strainRace: String? = null,
#SerialName("studyType")
val studyType: String = "",
#SerialName("target")
val target: String = "",
#SerialName("targetType")
val targetType: String = "",
#SerialName("testSystem")
val testSystem: String = "",
#SerialName("transfection")
val transfection: String = "",
#SerialName("unitNormalized")
val unitNormalized: String? = null,
#SerialName("unitOriginal")
val unitOriginal: String? = null,
#SerialName("valueNormalized")
val valueNormalized: String = "",
#SerialName("valueOriginal")
val valueOriginal: String = ""
)
My database data object resembles this
data class ActivitySearchItemDO(
#ColumnInfo(name = "action_tested") val actionTested: String?,
#ColumnInfo(name = "agonist_antagonist") val agonistAntagonist: String,
#ColumnInfo(name = "cell_origin") val cellOrigin: String?,
#ColumnInfo(name = "comment") val comment: String?,
#ColumnInfo(name = "display_dose") val displayDose: String,
#ColumnInfo(name = "display_value") val displayValue: String,
#ColumnInfo(name = "document") val document: DocumentVO,
#ColumnInfo(name = "document_year") val documentYear: Int,
#ColumnInfo(name = "dose") val dose: String,
#ColumnInfo(name = "dose_unit") val doseUnit: String?,
#ColumnInfo(name = "drug") val drug: String,
#ColumnInfo(name = "error_type") val errorType: String?,
#ColumnInfo(name = "error_value") val errorValue: String?,
#ColumnInfo(name = "experiment_type") val experimentType: String?,
#ColumnInfo(name = "hash") val hash: String,
#ColumnInfo(name = "id") val id: String,
#ColumnInfo(name = "is_primary_target") val isPrimaryTarget: String? = null,
#ColumnInfo(name = "mutation_detail") val mutationDetail: String?,
#ColumnInfo(name = "other_exp_details") val otherExpDetails: String?,
#ColumnInfo(name = "parameter") val parameter: String,
#ColumnInfo(name = "sharp_N") val sharpN: String?,
#ColumnInfo(name = "smiles") val smiles: String?,
#ColumnInfo(name = "source") val source: String,
#ColumnInfo(name = "specie") val specie: String,
#ColumnInfo(name = "strain_race") val strainRace: String?,
#ColumnInfo(name = "study_type") val studyType: String,
#ColumnInfo(name = "target") val target: String,
#ColumnInfo(name = "target_type") val targetType: String,
#ColumnInfo(name = "test_system") val testSystem: String,
#ColumnInfo(name = "transfection") val transfection: String,
#ColumnInfo(name = "unit_normalized") val unitNormalized: String?,
#ColumnInfo(name = "unit_original") val unitOriginal: String?,
#ColumnInfo(name = "value_normalized") val valueNormalized: String,
#ColumnInfo(name = "value_original") val valueOriginal: String
)
when i build my project i get the following warning and do not understand why
warning: Unmapped target property: "isPrimaryTarget".
public abstract com.myapp.model.database.ActivitySearchItemDO map(#org.jetbrains.annotations.NotNull()
my mapper resembles this
#InheritInverseConfiguration(name = "map")
fun map(dataFields: Item): ActivitySearchItemDO
why is mapstruct reporting that the isPrimaryTarget is not being mapped?
UPDATE
it appears to be the "is" prefix that is causing the issue, when i remove is from the variable names it maps ok. which i am happy with as this field is not a boolean type but a String.
however i now have another case that is boolean and its generating a similar warning. why does mapstruct have an issue when mapping boolean fields.

Why retrofit returns null in response body?

Get null response results in response body. Using retrofit, dagger and repository. Why get null in response I don't know. My model seem right. Whats the problem?
MainViewModel.kt
#HiltViewModel
class MainViewModel#Inject constructor(
private val repository: MovieRepository,
#ApplicationContext private val context: Context
) : ViewModel() {
val movieList = MutableLiveData<Resource<Movie>>()
fun getAllMovies(movieName: String) {
movieList.postValue(Resource.Loading())
viewModelScope.launch {
try {
if (hasInternetConnection(context)) {
val response = repository.getMovies(movieName, "ffe9123f")
movieList.postValue(Resource.Success(response.body()!!))
} else
movieList.postValue(Resource.Error("Internet yok"))
} catch (ex: Exception) {
when (ex) {
is IOException -> movieList.postValue(Resource.Error("Network Failure " + ex.localizedMessage))
else -> movieList.postValue(Resource.Error("Conversion Error"))
}
}
}
}
}
Resource.kt
sealed class Resource<T>(
val data: T? = null,
val message: String? = null
) {
class Success<T>(data: T): Resource<T>(data)
class Error<T>(message: String, data: T? = null): Resource<T>(data, message)
class Loading<T> : Resource<T>()
}
MovieRepository.kt
#Singleton
class MovieRepository #Inject constructor(private val movieAppService: MovieAppService) {
suspend fun getMovies(title: String, aKey: String): Response<Movie> = withContext(
Dispatchers.IO
) {
val movies = movieAppService.getMovies(title = title, aKey = aKey)
movies
}
Movie.kt
data class Movie(
val title: String,
val year: String,
val rated: String,
val released: String,
val runtime: String,
val genre: String,
val director: String,
val writer: String,
val actors: String,
val plot: String,
val language: String,
val country: String,
val awards: String,
val poster: String,
val ratings: List<Rating>,
val metascore: String,
val imdbRating: String,
val imdbVotes: String,
val imdbID: String,
val type: String,
val dvd: String,
val boxOffice: String,
val production: String,
val website: String,
val response: String
)
MovieAppService.kt
interface MovieAppService {
companion object {
const val ENDPOINT = "http://www.omdbapi.com/"
}
#GET(".")
suspend fun getMovies(#Query("t") title: String,#Query("apikey") aKey: String): Response<Movie>
}
{"Title":"A Beautiful Mind","Year":"2001","Rated":"PG-13","Released":"04 Jan 2002","Runtime":"135 min","Genre":"Biography, Drama","Director":"Ron Howard","Writer":"Akiva Goldsman, Sylvia Nasar","Actors":"Russell Crowe, Ed Harris, Jennifer Connelly","Plot":"After John Nash, a brilliant but asocial mathematician, accepts secret work in cryptography, his life takes a turn for the nightmarish.","Language":"English","Country":"United States","Awards":"Won 4 Oscars. 37 wins & 69 nominations total","Poster":"https://m.media-amazon.com/images/M/MV5BMzcwYWFkYzktZjAzNC00OGY1LWI4YTgtNzc5MzVjMDVmNjY0XkEyXkFqcGdeQXVyMTQxNzMzNDI#._V1_SX300.jpg","Ratings":[{"Source":"Internet Movie Database","Value":"8.2/10"},{"Source":"Rotten Tomatoes","Value":"74%"},{"Source":"Metacritic","Value":"72/100"}],"Metascore":"72","imdbRating":"8.2","imdbVotes":"908,920","imdbID":"tt0268978","Type":"movie","DVD":"25 Jun 2002","BoxOffice":"$170,742,341","Production":"N/A","Website":"N/A","Response":"True"}
Movie model is wrong. I didn't realize the starting letter is uppercase letter.
Movie.kt
data class Movie(
val Title: String,
val Year: String,
val Rated: String,
val Released: String,
val Runtime: String,
val Genre: String,
val Director: String,
val Writer: String,
val Actors: String,
val Plot: String,
val Language: String,
val Country: String,
val Awards: String,
val Poster: String,
val Ratings: List<Rating>,
val Metascore: String,
val imdbRating: String,
val imdbVotes: String,
val imdbID: String,
val Type: String,
val Dvd: String,
val boxOffice: String,
val Production: String,
val Website: String,
val Response: String
)

Android studio kotlin error : Execution failed for task ':app:kaptDebugKotlin'

I am trying to implement a room database for a league data but when I'm trying to compile, it's doesnt work. The error seem to be throw in the Database file. I hope you can help me
In the Dao, i select all the countryProperty and insert them in the database,
#Dao
interface CountryDataBaseDao {
#Query("SELECT * FROM DatabaseCountryProperty")
fun getData() : LiveData<List<DatabaseCountryProperty>>
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertData(data : List<DatabaseCountryProperty>)
}
In the entity, countryProperty entity get the extra entity,
#Entity
data class DatabaseCountryProperty constructor(
#PrimaryKey
val id: String,
val name: String,
val imagePath: String?,
val extra: DatabaseExtraCountry?)
data class DatabaseExtraCountry constructor(
val continent : String?,
val subRegion : String?,
val worldRegion : String?,
val fifa : String?,
val iso : String?,
val iso2 : String?,
val longitude : Double?,
val latitude : Double?,
val flag : String?) {
}
fun List<DatabaseCountryProperty>.asDomainModel() : List<DevByteCountryProperty> {
return map {
DevByteCountryProperty(
id = it.id,
name = it.name,
imagePath = it.imagePath,
extra = DevByteExtraCountry(
continent = it.extra?.continent,
subRegion = it.extra?.subRegion,
worldRegion = it.extra?.worldRegion,
fifa = it.extra?.fifa,
iso = it.extra?.iso,
iso2 = it.extra?.iso2,
longitude = it.extra?.longitude,
latitude = it.extra?.latitude,
flag = it.extra?.flag
)
)
}
}
In the Database,
#Database(entities = [DatabaseCountryProperty::class], version = 1, exportSchema = false)
abstract class DataDataBase : RoomDatabase() {
abstract val countryDataBaseDao : CountryDataBaseDao
}
private lateinit var INSTANCE: DataDataBase
fun getDatabase(context: Context): DataDataBase {
synchronized(DataDataBase::class.java) {
if (!::INSTANCE.isInitialized) {
INSTANCE = Room.databaseBuilder(context.applicationContext,
DataDataBase::class.java,
"data").build()
}
}
return INSTANCE
}
The errors was that I didn't had the annotation embedded to declare the fact that the extra is a sub_class. So the code is,
#Entity
data class DatabaseCountryProperty constructor(
#PrimaryKey
val id: String,
val name: String,
val imagePath: String?,
#Embedded
val extra: DatabaseExtraCountry?)
data class DatabaseExtraCountry constructor(
val continent : String?,
val subRegion : String?,
val worldRegion : String?,
val fifa : String?,
val iso : String?,
val iso2 : String?,
val longitude : Double?,
val latitude : Double?,
val flag : String?) {
}
Add these in your build gradle.
id 'kotlin-kapt' in your plugin
kapt "androidx.room:room-compiler:2.3.0" in your dependencies
Just Do a Try

reduce code duplication of DTOs validations

I have the following two data classes
data class CreateMedicDto(
val firstName: String,
val lastName: String,
val pin: String,
val address: String?,
val birthDate: LocalDate,
val specialty: Specialty,
)
data class UpdateMedicDto(
val firstName: String,
val lastName: String,
val address: String?,
val birthDate: LocalDate,
val specialty: Specialty,
)
The only difference is that the second one is missing pin field. The reason for that is that in case of update I do not want to allow the possibility to change the pin by using making use of the language and framework features.
Currently, in this form, I will need to validate both of them:
fun validateMedic(
input: CreateMedicDto,
): MedicValidationResult? {
with(input) {
if (checkLengthBetween1And50(input.firstName)) return MedicValidationResult.InvalidFirstNameLength
if (checkLengthBetween1And50(input.lastName)) return MedicValidationResult.InvalidLastNameLength
address?.let {
if (checkLengthGreaterThan500(it)) return MedicValidationResult.InvalidAddressLength
}
if (checkDateValidity(birthDate)) return MedicValidationResult.InvalidBirthDate
}
return null
}
fun validateMedic(
input: UpdateMedicDto,
): MedicValidationResult? {
with(input) {
if (checkLengthBetween1And50(input.firstName)) return MedicValidationResult.InvalidFirstNameLength
if (checkLengthBetween1And50(input.lastName)) return MedicValidationResult.InvalidLastNameLength
address?.let {
if (checkLengthGreaterThan500(it)) return MedicValidationResult.InvalidAddressLength
}
if (checkDateValidity(birthDate)) return MedicValidationResult.InvalidBirthDate
}
return null
}
The code is almost identical, only the input parameter type is different.
The question is how can I reduce it to a single function?
I have some solutions but there are also some reasons for that I would not like to use them:
Inherit CreateMedicDto from UpdateMedicDto. This may easily solve the problem but it doesn't really make much sense to say that CreateMedicDto is an UpdateMedicDto
Make that pin field nullable. With this approach I abandon the language null-safety feature and I am going to rely on a parameter which says "create" or "update"
Both extending a common class. The third class is actually useless and can't find a proper name for it
Any better approaches to still benefit from the type safety?
Your option 3 is called an abstract class. abstract means it cannot be instantiated, but also that it can have abstract members (properties and functions with no implementation or initial value).
An option 4 would be to create a common interface for both of them.
But in both cases, there will still be duplication of all the property names because you have to override them. But it fixes the duplication at your validation functions.
Option 3 example:
abstract class MedicDto {
abstract val firstName: String
abstract val lastName: String
abstract val pin: String
abstract val address: String?
abstract val birthDate: LocalDate
abstract val specialty: Specialty
}
data class CreateMedicDto(
override val firstName: String,
override val lastName: String,
val pin: String,
override val address: String?,
override val birthDate: LocalDate,
override val specialty: Specialty,
): MedicDto()
data class UpdateMedicDto(
override val firstName: String,
override val lastName: String,
override val address: String?,
override val birthDate: LocalDate,
override val specialty: Specialty,
): MedicDto()
Option 4 example:
interface MedicDto {
val firstName: String
val lastName: String
val pin: String
val address: String?
val birthDate: LocalDate
val specialty: Specialty
}
data class CreateMedicDto(
override val firstName: String,
override val lastName: String,
val pin: String,
override val address: String?,
override val birthDate: LocalDate,
override val specialty: Specialty,
): MedicDto
data class UpdateMedicDto(
override val firstName: String,
override val lastName: String,
override val address: String?,
override val birthDate: LocalDate,
override val specialty: Specialty,
): MedicDto

Best practice for handling null types in classes when mapping classes together

I am using Kotlin 1.30. I have the following entity classes that will be populated from the API. And some of the properties could be null from the API so I have declared them using the safe null type.
However, I will map this entity class to my domain model class. And just wondering what is the best way to handle the null types?
I have 2 ideas on how to do this when I map the classes.
declare all the equivalent properties as safe null types
use the elivs operator to return either a empty string or a emptylist
In the following snippet I am using the elvis operator. Just wondering what is the best practice for this?
class LoginResponseDomainMapperImp : LoginResponseDomainMapper {
override fun map(entity: LoginResponseEntity): LoginResponse {
return LoginResponse(
entity.token ?: "",
mapUsers(entity.user),
mapEnterprises(entity.enterprises ?: emptyList()),
mapVendors(entity.vendors ?: emptyList()))
}
private fun mapUsers(userEntity: UserEntity?): User {
return User(
userEntity?.id,
userEntity?.email,
userEntity?.firstName,
userEntity?.lastName,
userEntity?.phone,
userEntity?.address,
userEntity?.dob,
userEntity?.customer,
userEntity?.enterpriseIds ?: emptyList(),
userEntity?.vendorIds ?: emptyList())
}
private fun mapEnterprises(enterprisesEntity: List<EnterprisesEntity>): List<Enterprises> {
val enterpriseList = mutableListOf<Enterprises>()
enterprisesEntity.forEach {
val enterprise = Enterprises(
it.id,
it.enterpriseName,
it.typeLabel,
it.country,
it.imageId,
it.managers,
it.members,
it.stripe,
it.locations)
enterpriseList.add(enterprise)
}
return enterpriseList.toList()
}
private fun mapVendors(vendorsEntity: List<VendorsEntity>): List<Vendors> {
val vendorList = mutableListOf<Vendors>()
vendorsEntity.forEach {
val vendor = Vendors(
it.id,
it.vendorName,
it.typeLabel,
it.userRole,
it.latitude,
it.longitude,
it.partner,
it.country,
it.imageId,
it.stripe)
vendorList.add(vendor)
}
return vendorList.toList()
}
}
Entity class that will populate from the API, so any of these could be null
data class LoginResponseEntity(
#SerializedName("token") val token: String?,
#SerializedName("user") val user: UserEntity?,
#SerializedName("enterprises") val enterprises: List<EnterprisesEntity>?,
#SerializedName("vendors") val vendors: List<VendorsEntity>?)
data class UserEntity(
#SerializedName("id") val id: String?,
#SerializedName("email") val email: String?,
#SerializedName("first_name") val firstName: String?,
#SerializedName("last_name") val lastName: String?,
#SerializedName("phone") val phone: String?,
#SerializedName("address") val address: String?,
#SerializedName("dob") val dob: String?,
#SerializedName("customer") val customer: String?,
#SerializedName("enterprise_ids") val enterpriseIds: List<String>?,
#SerializedName("vendor_ids") val vendorIds: List<String>?)
data class EnterprisesEntity(
#SerializedName("id") val id: String?,
#SerializedName("enterprise_name") val enterpriseName: String?,
#SerializedName("type_label") val typeLabel: String?,
#SerializedName("referral_code") val referralCode: String?,
#SerializedName("country") val country: String?,
#SerializedName("image_id") val imageId: String?,
#SerializedName("managers") val managers: List<String>?,
#SerializedName("members") val members: List<String>?,
#SerializedName("stripe") val stripe: Boolean,
#SerializedName("locations") val locations: List<String>?)
data class VendorsEntity(
#SerializedName("id") val id: String?,
#SerializedName("vendor_name") val vendorName: String?,
#SerializedName("type_label") val typeLabel: String?,
#SerializedName("user_role") val userRole: String?,
#SerializedName("latitude") val latitude: Float,
#SerializedName("longitude") val longitude: Float,
#SerializedName("partner") val partner: Boolean,
#SerializedName("country") val country: String?,
#SerializedName("image_id") val imageId: String?,
#SerializedName("stripe") val stripe: Boolean)
Data model class in the domain, Its it better to declare them all safe null types?
data class LoginResponse(
val token: String,
val user: User?,
val enterprises: List<Enterprises>,
val vendors: List<Vendors>)
data class User(
val id: String?,
val email: String?,
val firstName: String?,
val lastName: String?,
val phone: String?,
val address: String?,
val dob: String?,
val customer: String?,
val enterpriseIds: List<String>,
val vendorIds: List<String>)
data class Enterprises(
val id: String,
val enterpriseName: String,
val typeLabel: String,
val country: String,
val imageId: String,
val managers: List<String>,
val members: List<String>,
val stripe: Boolean,
val locations: List<String>)
data class Vendors(
val id: String,
val vendorName: String,
val typeLabel: String?,
val userRole: String,
val latitude: Float,
val longitude: Float,
val partner: Boolean,
val country: String?,
val imageId: String,
val stripe: Boolean)
First of all there are no safe null types in Kotlin. A variable is either nullable or not.
If the API delivers null values, either by absence or by setting them explicitely null, your data classes should reflect that in the way that those variables are nullable (question mark ? right after the data type).
The moment you map those data classes to your entities (domain model) you should handle the null case properly. Since it is a valid case, that the API serves null values, which you expect, you should handle that case by assigning a default value.
Using the elvis operator is just a way to deal with nullable types elegantely, but if you use it, remains your choice.
If you declare all the properties as nullable that's not much better than Java, even when you access them with the null safety operator. If your JSON properties have null value, that means that in your business logic they don't always have a value, and is your responsibility to handle that, by fallback to a default value (i.e. emptyList() or ""), or maybe something more complicated like redirecting to a sign in screen.