Kotlin syntactic sugar: make var all private in constructor - constructor

I'm new to kotlin and astonished by all the useful syntactic sugars and features it contains.
But whenever I declare a constructor, I have to make all my fields private independantly.
class Result(private val startTime: String?, private val stopTime: String?,
private val niveau: Int, private val contraction: String?,
private val VPcount: Int, private val VNcount: Int,
private val FPcount: Int, private val FNcount: Int) {...}
Is there any way I could write something like this ?
class Result(private {val startTime: String?, val stopTime: String?,
val niveau: Int, val contraction: String?,
val VPcount: Int, val VNcount: Int,
val FPcount: Int, val FNcount: Int}) {...}
I couldn't find anything about this in the doc...

There's no way to do this currently. The default visibility is public, and you can only change it on a per-property basis.
Perhaps your class could implement an interface that doesn't expose all these properties, and you could pass instances of it to client code as that type - although I don't know your exact situation and requirements.

Related

How to convert list of int in json to list/array of enums using Moshi?

I'm getting a list of ints (which are really enums) from the API. When I try to parse it, I get: Unable to create converter for java.util.List<MyEnum>
My adapter is currently looking like this:
#Retention(AnnotationRetention.RUNTIME)
#JsonQualifier
annotation class MyEnumListAnnotation
class MyEnumListAdapter {
#ToJson
fun toJson(#MyEnumListAnnotation myEnumList: List<MyEnum>): List<Int> {
return myEnumList.map { it.type }
}
#FromJson
#MyEnumListAnnotation
fun fromJson(typeList: List<Int>): List<MyEnum> {
return typeList.map { MyEnum.from(it) }
}
}
I'm adding this to the network client like this:
Moshi.Builder()
.add([A lot of other adapters])
.add(MyEnumListAdapter())
And I'm using the annotation like this (in the object I want to parse to):
data class InfoObject(
val id: String,
val name: String,
val email: String,
val phone: String,
#MyEnumListAnnotation
val myEnums: List<MyEnum>
)
How can I write my adapter so that this is working? Thanks for all help. :)
If you use Moshi's codegen (which you should), you only need to write adapter for your MyEnum itself.
class MyEnumAdapter {
#ToJson
fun toJson(enum: MyEnum): Int {
return enum.type
}
#FromJson
fun fromJson(type: Int): MyEnum {
return MyEnum.from(it)
}
}
Attach the adapter to your Moshi builder the way you did it in your question. Then, update your InfoObject:
#JsonClass(generateAdapter = true)
data class InfoObject(
#Json(name = "id") val id: String,
#Json(name = "name") val name: String,
#Json(name = "email") val email: String,
#Json(name = "phone") val phone: String,
#Json(name = "myEnums") val myEnums: List<MyEnum>
)
#JsonClass(generateAdapter = true) will ensure that the library will auto-create an adapter for your InfoObject, including an adapter for List<MyEnum> (the one you tried to create yourself), so you don't have to create those adapters yourself. #Json(name="...") is just a convention, you can omit it.
To integrate codegen, just add to dependencies:
kapt("com.squareup.moshi:moshi-kotlin-codegen:1.9.3")
See https://github.com/square/moshi for more details.

JSON reader Kotlin

How can I read JSON file into more than one documents and save it in Mongo DB.
I have two models:
#Document
data class Person(val name: String){
#Id
private val id : String? = null
And:
#Document
data class Floor (private var floorName: StoreyEnum,
private val roomNumber: String
private val personID: String){
#Id
private val id : String? = null}
I have JSON file in which I have fields to both models. Moreover I want connect this documents with "relation", how can I do that?
Use Gson if it's on a JVM backend.
BTW, I don't quite get your purpose of making id private, val, and initialized to null at the same time. Because in that way it's always set to null, never changed and never read. so I changed it to this:
data class Person(val name: String, private val id: String? = null)
Then you can use Gson to encode and parse the object:
fun main(args: Array<String>) {
val gson = Gson()
val person = Person("name", "0")
println(person)
val personJson = gson.toJson(person)
println(personJson)
val parsedPerson = gson.fromJson(personJson, Person::class.java)
println(parsedPerson)
}
Output:
Person(name=name, id=0)
{"name":"name","id":"0"}
Person(name=name, id=0)

How to access secondary constructor parameter outside constructor block in Kotlin

I am new to Kotlin and working through the tutorials that are available.
However now I seem to have a problem with a secondary constructor:
Parameters declared in the primary constructor can be accessed in a function,
but when I try to do this with a parameter from the secondary constructor I get an error: Unresolved reference:nbr
The code:
class Test(_name: String) {
val name: String = _name
constructor(_name: String, _nbr: Int) : this(_name) {
val nbr: Int = _nbr
}
fun printNameAndNumber() {
println("Name: $name")
println("Number: $nbr")
}
}
It is clear to me that I am doing something basically wrong but who can tell me what?
nbr should be a variable, because in this specific case it is optional:
class Test(_name: String) {
val name: String = _name
var nbr: Int? = null
constructor(_name: String, _nbr: Int) : this(_name) {
this.nbr = _nbr
}
fun printNameAndNumber() {
println("Name: $name")
println("Number: $nbr")
}
}
Parameters of the primary constructor are not available in member functions. Fields are. Fortunately Kotlin has a short syntax to make primary constructor parameters member properties right away.
What do you expect nbr to be when constructed using the primary constructor? I suggest you to swap your constructors, so it's clear what are properties and what are just parameters:
class Test(val name: String, val nbr: Int) {
constructor(name: String) : this(name, 0)
fun printNameAndNumber() {
println("Name: $name")
println("Number: $nbr")
}
}
fun main(args : Array<String>) {
Test("Péter").printNameAndNumber()
}
name is accessible because it is a member.
nbr is not accessible because it is a local (immutable) variable inside the secondary constructor.
If you declare nbr as member: putting val nbr: Int for example below the val name line, it will be accessible, however it will not compile if nbr is defined as immutable (val).
A simpler structure would be:
class Test(_name: String, _nbr: Int = 0) {
val name: String = _name
val nbr: Int = _nbr
fun printNameAndNumber() {
println("Name: $name")
println("Number: $nbr")
}
}
or even simpler
class Test(val name: String, val nbr: Int = 0) {
fun printNameAndNumber() {
println("Name: $name")
println("Number: $nbr")
}
}
If you want your nbr member as nullable, you couldgo with the suggestion of #gil.fernandes.

Can Kotlin data class have more than one constructor?

I know that data class are like simple models in kotlin with getters and setter by default and are as simple this:
data class User(val name: String, val age: Int)
Is it possible to declare a second constructor for that data class?
A Kotlin data class must have a primary constructor that defines at least one member. Other than that, you can add secondary constructors as explained in Classes and Inheritance - Secondary Constructors.
For your class, and example secondary constructor:
data class User(val name: String, val age: Int) {
constructor(name: String): this(name, -1) { ... }
}
Notice that the secondary constructor must delegate to the primary constructor in its definition.
Although many things common to secondary constructors can be solved by having default values for the parameters. In the case above, you could simplify to:
data class User(val name: String, val age: Int = -1)
If calling these from Java, you should read the Java interop - Java calling Kotlin documentation on how to generate overloads, and maybe sometimes the NoArg Compiler Plugin for other special cases.
Updated answer for data classes:
Yes you can, but you will need to delegate everything to the primary constructor
data class User(val name: String, val age: Int)
{
constructor(name: String): this(name, -1) {
}
constructor(age: Int): this("Anon", age) {
}
}
// Anon name: Anon
println("Anon name: " + User(30).name)
// No age: -1
println("No age: " + User("Name").age)
// Name: Name age: 20
val u = User("Name", 20)
println("Name: " + u.name + " age: " + u.age)
You can also set default values in your primary constructor as Alexey did.
I wanted to have a class similar to below (with a constructor that parses an input)
data class Data(val a: String, val b: String) {
constructor(spaceSeparated: String) { // error because we don't call this()
val split = spaceSeparated.split(" ")
this(split.first(), split.last()) // error again because it's not valid there
}
}
The solution is to do this:
data class Data(val a: String, val b: String) {
companion object {
operator fun invoke(spaceSeparated: String): Data {
val split = spaceSeparated.split(" ")
return Data(split.first(), split.last())
}
}
}
And it can be called just as if it were a constructor
Default values in the primary constructor eliminates many needs for secondary constructors, but if the needed instance depends on logic based on data that must be analyzed the better answer may be to use a companion object.
data class KeyTag(val a: String, val b: Int, val c: Double) {
companion object Factory {
val empty = KeyTag("", 0, 0.0)
fun create(bigString: String): KeyTag {
// Logic to extract appropriate values for arguments a, b, c
return KeyTag(a, b, c)
}
fun bake(i: Int): KeyTag = KeyTag("$i", i, i.toDouble())
}
}
Usage is then:
val ks = KeyTag.create("abc:1:10.0")
val ke = KeyTag.empty
val kb = KeyTag.bake(2)
Yes, but each variable should be initialized, so you may set default arguments in your data class constructor, like this:
data class Person(val age: Int, val name: String = "Person without name")
Now you can create instance of this data class in two ways
Person(30)
Person(20, "Bob")
you can set the data class like this
data class User(val name: String? = null, val id: String? = null, val email: String? = null)
and you can instance the object with multiple constructors like this
val userId = User(id = "123456")
val userMail = User(email= "email#email.com")
val userName = User("Name")
Data class will ensure consistency and meaningful behavior also we need to have val for immutability.
data class SampleData(val name: String, val age: Int, val email: String ?= null) {
constructor(name: String, age: Int) : this(name, age, null) {
}
}
secondary constructor must delegate to the primary constructor in its definition, so to maintain the immutability, having "null" will work.
Instructs the Kotlin compiler to generate overloads for this function that substitute default parameter values.
If a method has N parameters and M of which have default values, M overloads are generated: the first one takes N-1 parameters (all but the last one that takes a default value), the second takes N-2 parameters, and so on.
data class User #JvmOverloads constructor(
var email: String="",
var password: String="")
Yes, we can use like below code, and in primary constructor for data class should have min one parameter.
data class SampleData(val name: String, val age: Int) {
constructor(name: String, age: Int, email: String) : this(name, age) {
}
}
Yes you can have multiple contractors on data classes. But there is something which makes the primary constructor special. The compiler will auto generate methods like equals, hashCode, copy, toStrings based on the primary constructor for the data class.
Below is an example of two instances of a data class which looks different (first.gender = male, second.gender = female) but equals method would evaluate to true because gender is not defined in the primary constructor and therefore not considered in the auto generated methods. Likewise, gender would not be included in the string representation.
data class A(val name: String, val age: Int) {
var gender: String = "Female"
constructor(name: String, age: Int, gender: String) : this(name, age) {
this.gender = gender
}
}
fun main(args: Array<String>) {
val first = A("foo", 10)
val second = A("foo", 10, "Male")
println(first == second) //prints true
println(second) //prints A(name=foo,age=10)
}

Kotlin: How can I avoid code duplication in constructors?

Often I find myself in a situation where I have a superclass that has lots of optional parameters, and those same parameters need to also be optional parameters in its subclasses.
For example, the superclass:
abstract class Plugin(val name: String, val version: String = "1.0",
val author: String = "", val description: String = "")
Extending this class is a pain. Here's an example subclass:
abstract class CyclePlugin(name: String, version: String = "1.0", author: String = "",
description: String = "", val duration: Int, val durationUnit: TimeUnit
= MILLISECONDS) : Plugin(name, version, author, description)
Note: I will answer this question with my solution. I am in search of a better solution.
The way I normally solve this problem is by creating a data class to represent the parameters.
data class PluginInfo(val name: String, val version: String = "1.0",
val author: String = "", val description: String = "")
I then take this class as a parameter in the constructors.
abstract class Plugin(val info: PluginInfo)
abstract class CyclePlugin(info: PluginInfo, val duration: Int,
val durationUnit: TimeUnit = MILLISECONDS) : Plugin(info)
Then an example plugin can be implemented like this:
class ExamplePlugin : CyclePlugin(PluginInfo("Example Plugin", author = "Jire"), 8, TimeUnit.SECONDS)
Like #miensol mentioned, you can define your properties outside of the constructor.
abstract class Plugin(val name: String) {
open val version: String = "1.0"
open val author: String = ""
open val description: String = ""
}
Then you're able to define CyclePlugin with only the necessary name parameter:
abstract class CyclePlugin(name: String, val duration: Int,
val durationUnit: TimeUnit = MILLISECONDS) : Plugin(name)
Then for example, you can override some fields for ExamplePlugin:
class ExamplePlugin : CyclePlugin("Example Plugin", 8, TimeUnit.SECONDS) {
override val author = "Giovanni"
override val description = "This is an example plugin"
}