Unable to initialize kotlin class member from secondary function - kotlin

class Person constructor(val first_name: String){
init{
println("Welcome, ${first_name}")
}
val last_name: String
constructor(fname: String, lname: String): this(fname){
this.last_name = lname
}
fun fullNameGreeting(){
println("Welcome, ${first_name} ${last_name}")
}
}
fun main() {
val kotlin = "🙂"
println(kotlin)
val adam: Person = Person("Adam", "Kim")
}
I get following error in Kotlin Playground:-
Property must be initialized or be abstract
Val cannot be reassigned
I am just trying it after reading the documentation on Kotlin website. I am still learning but I am not able to solve this problem. Please Help.

Property must be initialized or be abstract:
You got this error because you didn't initialize a value to last_name, you can't just declare the name and the type of the variable, you need to assign a default value to it like that val last_name: String = ""
Val cannot be reassigned:
And for this error because you set last_name as a value by using val and that means that you can't change it anymore, but you are trying to change it here this.last_name = lname, so to be able to change last_name you need to set is a variable by using var, like that var last_name: String = ""
So your final code should look like this:
class Person constructor(val first_name: String){
init{
println("Welcome, ${first_name}")
}
var last_name: String = ""
constructor(fname: String, lname: String): this(fname){
this.last_name = lname
}
fun fullNameGreeting(){
println("Welcome, ${first_name} ${last_name}")
}
}
fun main() {
val kotlin = "🙂"
println(kotlin)
val adam: Person = Person("Adam", "Kim")
}
If you want to create an instance of Person with only first_name or both first_name and last_name, you can use this approach:
class Person constructor(
val first_name: String,
val last_name: String = ""
){
init{
println("Welcome, ${first_name}")
}
fun fullNameGreeting(){
println("Welcome, ${first_name} ${last_name}")
}
}
This way you can either call Person("Your first name"), or Person("Your first name", "Your last name")

I'd suggest switching the constructors around. Then you can do
class Person constructor(val first_name: String, val last_name: String){
init{
println("Welcome, ${first_name}")
}
constructor(fname: String): this(fname, "")
fun fullNameGreeting(){
println("Welcome, ${first_name} ${last_name}")
}
}
or better yet, make the second parameter optional, like
class Person constructor(val first_name: String, val last_name: String = ""){
init{
println("Welcome, ${first_name}")
}
fun fullNameGreeting(){
println("Welcome, ${first_name} ${last_name}")
}
}

It seems that val is used for constant and var for variable (overwritable) properties. If you replace vals in the class with vars, it works.

Related

Kotlin with(receiver) equivalent in Dart

Let's say I have a class like this :
class User {
lateinit var firstname: String
lateinit var lastname: String
lateinit var age: String
}
Now, I want to populate this class, and use it, like this :
fun main() {
val user = User()
user.firstname = "firstname"
user.lastname = "lastname"
user.age = "25";
println("Hello, ${user.firstname} ${user.lastname}, you are ${user.age} !")
}
Output : > Hello, firstname lastname, you are 25 !
In Kotlin, we have some syntactic sugar keyword with, which returns the receiver passed in params and allows us not to repeat the variable name over and over, for example, this will output the same thing and is a bit more pretty :
fun main() {
val user = User()
with (user) {
firstname = "firstname"
lastname = "lastname"
age = "25";
println("Hello, $firstname $lastname, you are $age !")
}
}
Is there an equivalent to Kotlin's with(receiver: T, block: T.() -> R) function in Dart ?

Identifying relationships in Kotlin

Are nested classes a good way to model identifying relationships in Kotlin?
Requirements:
an issue can not be without a serno
a revocation can not be without an issue
an affirmation can not be without a revocation
Looks quite verbose:
class Have {
inner class Serno(val value: String) {
override fun toString(): String = "serno: $value"
fun nothing () = this#Have
inner class Issue(val value: String) {
override fun toString(): String = "issue: $value (${serno()})"
fun serno () = this#Serno
inner class Revocation(val value: String) {
override fun toString(): String = "revocation: $value (${issue()})"
fun issue () = this#Issue
inner class Affirmation(val value: String) {
override fun toString(): String = "affirmation: $value (${revocation()})"
fun revocation () = this#Revocation
}
}
}
}
}
val serno: Have.Serno = Have().Serno("123")
val issue: Have.Serno.Issue = serno.Issue("SUP-1")
val revocation: Have.Serno.Issue.Revocation = issue.Revocation("2020")
val affirmation: Have.Serno.Issue.Revocation.Affirmation = revocation.Affirmation("2022")
println(serno)
println(issue)
println(revocation)
println(affirmation)
println(serno == affirmation.revocation().issue().serno())
Is there a simpler way to achieve the same?
This would usually be achieved with simple non-null properties:
class Serno(val value: String) {
override fun toString(): String = "serno: $value"
}
class Issue(val value: String, val serno: Serno) {
override fun toString(): String = "issue: $value ($serno)"
}
class Revocation(val value: String, val issue: Issue) {
override fun toString(): String = "revocation: $value ($issue)"
}
class Affirmation(val value: String, val revocation: Revocation) {
override fun toString(): String = "affirmation: $value ($revocation)"
}
val serno = Serno("123")
val issue = Issue("SUP-1", serno)
val revocation = Revocation("2020", issue)
val affirmation = Affirmation("2022", revocation)
And if you're not strict on the toString format, you could even use the built-in toString of data classes, and simplify further:
data class Serno(val value: String)
data class Issue(val value: String, val serno: Serno)
data class Revocation(val value: String, val issue: Issue)
data class Affirmation(val value: String, val revocation: Revocation)

Kotlin modify the default constructor

How would I achieve the following in Kotlin?
public class Person {
String firstName;
String lastName;
public Person(String name, String somethingElse) {
this.firstName = name.substring(0, 5);
this.lastName = name.substring(5, 10) + somethingElse;
}
}
The use case might be a bit weird, but this should be possible in Kotlin right? I have something like the following but then I get Conflicting overloads
data class Person(
val firstName: String,
val lastName: String
) {
constructor(name: String, somethingElse: String) :
this(firstName = name.substring(0, 5), lastName = name.substring(5, 10) + somethingElse)
}
You could have a companion object:
data class Person(
val firstName: String,
val lastName: String) {
companion object {
fun create(name: String, somethingElse: String): Person {
return Person(
name.substring(0, 5),
name.substring(5, 10) + somethingElse
)
}
}
}
fun main() {
val person = Person.create("Long first name", "Last name")
println(person)
}
Yes, it's possible:
data class Person(private val name: String, private val somethingElse: String) {
val firstName: String = name.substring(0, 5)
val lastName: String = name.substring(5, 10) + somethingElse
//You may want to override default "Person(name='$name', somethingElse='$somethingElse')"
override fun toString() = "Person(firstName='$firstName', lastName='$lastName')"
}

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

moshi custom qualifier annotation to serialise null on one property only

I'd like to serialise null for only one property in my JSON body that is going on a PUT. I don't want to serialize null for any other types in the object. Model class is like this
#Parcel
class User #ParcelConstructor constructor(var college: College?,
var firstname: String?,
var lastname: String?,
var email: String?,
var active: Boolean = true,
var updatedAt: String?,
var gender: String?,
var picture: String?,
var id: String?,
#field: [CollegeField] var collegeInput: String?,
#field: [CollegeField] var otherCollege: String?,)
I only want to serialise collegeInput and otherCollege fields if either of them are null. For example
val user = User(firstname = "foo", lastname=null, collegeInput="abcd", otherCollege = null)
Json will look something like this:
{"user":{
"firstname": "foo",
"collegeInput": "abcd",
"otherCollege": null
}}
Where otherCollege is null, lastname is omitted from the object as by default moshi does not serialise nulls which is what I want, but qualifer fields should be serialized with null values
I tried using
class UserAdapter {
#FromJson
#CollegeField
#Throws(Exception::class)
fun fromJson(reader: JsonReader): String? {
return when (reader.peek()) {
JsonReader.Token.NULL ->
reader.nextNull()
JsonReader.Token.STRING -> reader.nextString()
else -> {
reader.skipValue() // or throw
null
}
}
}
#ToJson
#Throws(IOException::class)
fun toJson(#CollegeField b: String?): String? {
return b
}
#Retention(AnnotationRetention.RUNTIME)
#JsonQualifier
annotation class CollegeField
I added the adapter to moshi but it never gets called
#Provides
#Singleton
fun provideMoshi(): Moshi {
return Moshi.Builder()
.add(UserAdapter())
.build()
}
#Provides
#Singleton
fun provideRetrofit(client: OkHttpClient, moshi: Moshi, apiConfig: ApiConfig): Retrofit {
return Retrofit.Builder()
.baseUrl(apiConfig.baseUrl)
.client(client)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
}
Your toJson adapter method will return null when the qualified string value is null, and the JsonWriter will not write the null value.
Here is a qualifier and adapter factory to install that will work.
#Retention(RUNTIME)
#JsonQualifier
public #interface SerializeNulls {
JsonAdapter.Factory JSON_ADAPTER_FACTORY = new JsonAdapter.Factory() {
#Nullable #Override
public JsonAdapter<?> create(Type type, Set<? extends Annotation> annotations, Moshi moshi) {
Set<? extends Annotation> nextAnnotations =
Types.nextAnnotations(annotations, SerializeNulls.class);
if (nextAnnotations == null) {
return null;
}
return moshi.nextAdapter(this, type, nextAnnotations).serializeNulls();
}
};
}
Now, the following will pass.
class User(
var firstname: String?,
var lastname: String?,
#SerializeNulls var collegeInput: String?,
#SerializeNulls var otherCollege: String?
)
#Test fun serializeNullsQualifier() {
val moshi = Moshi.Builder()
.add(SerializeNulls.JSON_ADAPTER_FACTORY)
.add(KotlinJsonAdapterFactory())
.build()
val userAdapter = moshi.adapter(User::class.java)
val user = User(
firstname = "foo",
lastname = null,
collegeInput = "abcd",
otherCollege = null
)
assertThat(
userAdapter.toJson(user)
).isEqualTo(
"""{"firstname":"foo","collegeInput":"abcd","otherCollege":null}"""
)
}
Note that you should use the Kotlin support in Moshi to avoid the #field: oddities.
Try approach from my gist:
https://gist.github.com/OleksandrKucherenko/ffb2126d37778b88fca3774f1666ce66
In my case I convert NULL from JSON into default double/integer value. You can easily modify the approach and make it work for your specific case.
p.s. its JAVA, convert it to Kotlin first.