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 ?
Related
When implementing a REST API with Ktor (and Kotlin), it supports the optional field handling of Kotlin. Which works for POST and GET, but what about PATCH (update)?
For example, you have the following resource:
#Serializable
data class MyAddress(
var line1: String? = null,
var line2: String? = null,
var city: String? = null,
var postal_code: String? = null,
var state: String? = null,
var country: String? = null
)
So all MyAddress fields are optional (with a default value).
When you create an address with POST:
"line1" : "line1",
"country" : "XX"
and you than want to remove the country with a PATCH:
"country" : null
the end result of the resource should be:
"line1" : "line1"
But how can you determine this by parsing the json of the PATCH request? Because there is no way, as far as I know, to determine if it was null by default, or submitted.
Furthermore, the default null value for the MyAddress is required, because else the parsing will not work.
Code example:
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
#kotlinx.serialization.Serializable
data class MyAddress(
var line1: String? = null,
var line2: String? = null,
var city: String? = null,
var postal_code: String? = null,
var state: String? = null,
var country: String? = null
)
fun main() {
val jsonStringPOST = "{\"line1\":\"street\",\"country\":\"GB\"}"
println("JSON string is: $jsonStringPOST")
val myAddressPost = Json.decodeFromString<MyAddress>(jsonStringPOST)
println("MyAddress object: $myAddressPost")
val jsonStringPATCH = "{\"country\":null}"
println("JSON string is: $jsonStringPATCH")
val myAddressPatch = Json.decodeFromString<MyAddress>(jsonStringPATCH)
println("MyAddress object: $myAddressPatch")
}
I tried to add Optional<String>? as well, but it complains about missing serialization of Optional, and preferably I do not want to make all my data var's Optionals.
Note: I am looking for a more structured solution, that also works with all other resources in the api (10+ classes).
A second solution, based on Aleksei's example:
#Serializable
data class Address2(val line1: OptionalValue<String> = Undefined, val country: OptionalValue<String> = Undefined)
#Serializable(with = OptionalValueSerializer::class)
sealed interface OptionalValue<out T>
object Undefined: OptionalValue<Nothing> {
override fun toString(): String = "Undefined"
}
object Absent: OptionalValue<Nothing> {
override fun toString(): String = "Absent"
}
class WithValue<T>(val value: T): OptionalValue<T> {
override fun toString(): String = value.toString()
}
open class OptionalValueSerializer<T>(private val valueSerializer: KSerializer<T>) : KSerializer<OptionalValue<T>> {
override val descriptor: SerialDescriptor = valueSerializer.descriptor
override fun deserialize(decoder: Decoder): OptionalValue<T> {
return try {
WithValue(valueSerializer.deserialize(decoder))
} catch (cause: SerializationException) {
Absent
}
}
override fun serialize(encoder: Encoder, value: OptionalValue<T>) {
when (value) {
is Undefined -> {}
is Absent -> { encoder.encodeNull() }
is WithValue -> valueSerializer.serialize(encoder, value.value)
}
}
}
fun main() {
val jsonStringPOST = "{\"line1\":\"street\",\"country\":\"GB\"}"
println("JSON string is: $jsonStringPOST")
val myAddressPost = Json.decodeFromString<Address2>(jsonStringPOST)
println("MyAddress object: $myAddressPost")
val jsonStringUPDATE = "{\"country\":null}"
println("JSON string is: $jsonStringUPDATE")
val myAddressUpdate = Json.decodeFromString<Address2>(jsonStringUPDATE)
println("MyAddress object: $myAddressUpdate")
if(myAddressUpdate.country is Absent || myAddressUpdate.country is WithValue) {
println("Update country: ${myAddressUpdate.country}")
} else {
println("No update for country: ${myAddressUpdate.country}")
}
}
Output is:
JSON string is: {"line1":"street","country":"GB"}
MyAddress object: Address2(line1=street, country=GB)
JSON string is: {"country":null}
MyAddress object: Address2(line1=Undefined, country=Absent)
Update country: Absent
You can use a sealed interface for a part of an address to represent undefined value, absence of value, and actual value. For this interface, you need to write a serializer that will encode and decode values accordingly to your logic. I'm not good at the kotlinx.serialization framework so I wrote an example as simple as possible.
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.request.*
import io.ktor.server.routing.*
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
fun main() {
embeddedServer(Netty, port = 4444) {
install(ContentNegotiation) {
json()
}
routing {
post {
val address = call.receive<Address>()
println(address)
}
}
}.start()
}
#Serializable
data class Address(val line1: MyValue = Undefined, val country: MyValue = Undefined)
#Serializable(with = AddressValueSerializer::class)
sealed interface MyValue
object Undefined: MyValue {
override fun toString(): String = "Undefined"
}
object Absent: MyValue {
override fun toString(): String = "Absent"
}
class WithValue(val value: String): MyValue {
override fun toString(): String = value
}
object AddressValueSerializer: KSerializer<MyValue> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("AddressValue", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): MyValue {
return try {
WithValue(decoder.decodeString())
} catch (cause: SerializationException) {
Absent
}
}
#OptIn(ExperimentalSerializationApi::class)
override fun serialize(encoder: Encoder, value: MyValue) {
when (value) {
is Undefined -> {}
is Absent -> { encoder.encodeNull() }
is WithValue -> { encoder.encodeString(value.value) }
}
}
}
With some help from medium.com, I came to the following solution:
#Serializable(with = OptionalPropertySerializer::class)
open class OptionalProperty<out T> {
object NotPresent : OptionalProperty<Nothing>()
data class Present<T>(val value: T) : OptionalProperty<T>() {
override fun toString(): String {
return value.toString()
}
}
fun isPresent() : Boolean {
return this is Present
}
fun isNotPresent(): Boolean {
return this is NotPresent
}
fun isEmpty(): Boolean {
return (this is Present) && this.value == null
}
fun hasValue(): Boolean {
return (this is Present) && this.value != null
}
override fun toString(): String {
if(this is NotPresent) {
return "<NotPresent>"
}
return super.toString()
}
}
open class OptionalPropertySerializer<T>(private val valueSerializer: KSerializer<T>) : KSerializer<OptionalProperty<T>> {
override val descriptor: SerialDescriptor = valueSerializer.descriptor
override fun deserialize(decoder: Decoder): OptionalProperty<T> =
OptionalProperty.Present(valueSerializer.deserialize(decoder))
override fun serialize(encoder: Encoder, value: OptionalProperty<T>) {
when (value) {
is OptionalProperty.NotPresent -> {}
is OptionalProperty.Present -> valueSerializer.serialize(encoder, value.value)
}
}
}
#Serializable
private data class MyAddressNew(
var line1: OptionalProperty<String?> = OptionalProperty.NotPresent,
var line2: OptionalProperty<String?> = OptionalProperty.NotPresent,
var city: OptionalProperty<String?> = OptionalProperty.NotPresent,
var postal_code: OptionalProperty<String?> = OptionalProperty.NotPresent,
var state: OptionalProperty<String?> = OptionalProperty.NotPresent,
var country: OptionalProperty<String?> = OptionalProperty.NotPresent,
)
fun main() {
val jsonStringPOST = "{\"line1\":\"street\",\"country\":\"GB\"}"
println("JSON string is: $jsonStringPOST")
val myAddressPost = Json.decodeFromString<MyAddressNew>(jsonStringPOST)
println("MyAddress object: $myAddressPost")
val jsonStringUPDATE = "{\"country\":null}"
println("JSON string is: $jsonStringUPDATE")
val myAddressUpdate = Json.decodeFromString<MyAddressNew>(jsonStringUPDATE)
println("MyAddress object: $myAddressUpdate")
if(myAddressUpdate.country.isPresent()) {
println("Update country: ${myAddressUpdate.country}")
} else {
println("No update for country: ${myAddressUpdate.country}")
}
}
This prints:
JSON string is: {"line1":"street","country":"GB"}
MyAddress object: MyAddressNew(line1=street, line2=<NotPresent>, city=<NotPresent>, postal_code=<NotPresent>, state=<NotPresent>, country=GB)
JSON string is: {"country":null}
MyAddress object: MyAddressNew(line1=<NotPresent>, line2=<NotPresent>, city=<NotPresent>, postal_code=<NotPresent>, state=<NotPresent>, country=null)
Update country: null
Hi i have a searchbar on maps and I want it to compare when I search for a city if it is in my City Table
I wrote a Dao and a method for this but every time when I write a wrote a city that doesn't exist the apps crashed`
My data serviceinterface
fun getCitiesFindbyName(name: String, getName: GetName)
fun startBtn(view: View){
lateinit var location: String
val dataService: DataService = (requireActivity().application as MyApp).dataService
val searchView = view.findViewById<SearchView>(R.id.sv_location)
location = searchView.query.toString()
dataService.getCitiesFindbyName(location, this)
the callback
interface GetName {
fun onFinish( city: City)
}
and the dao
#Query ("SELECT * FROM City WHERE name = :name")
fun getCitybyId(name:String):City
and the map view fragment
fun startBtn(view: View){
lateinit var location: String
val dataService: DataService = (requireActivity().application as MyApp).dataService
val searchView = view.findViewById<SearchView>(R.id.sv_location)
location = searchView.query.toString()
dataService.getCitiesFindbyName(location, this)
override fun onFinish(city:City ) {
val dataService: DataService = (requireActivity().application as MyApp).dataService
if (city.name.isNotEmpty()){
val latLng = LatLng(city.coord.lat!!, city.coord.lon!!)
val lat= (city.coord.lat)
val long = (city.coord.lon)
val name =(city.name)
nMap.addMarker(MarkerOptions().position(latLng).title(city.name))
retrofitResponse2(lat,long,city.name)
nMap.animateCamera(CameraUpdateFactory.newLatLng(latLng))
dataService.getCitiesFindbyName(name, this)
}
else{
Toast.makeText(requireContext(), "There is no info about this city", Toast.LENGTH_LONG).show()
}
}
}
I'm trying to bind a nullable property (String?) from my domain model to a tornadofx view model. But I get a compiler error.
The nullable properties are: idCard, phone, and discharge
My code:
class Patient(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<Patient>(Patients)
var patientId: Int by Patients.patientId
var name: String by Patients.name
var lastName: String by Patients.lastName
var recordNumber: Int by Patients.recordNumber
var idCard: String? by Patients.idCard
var phone: String? by Patients.phone
var age: Int by Patients.age
var gender: Char by Patients.gender
var admission: DateTime by Patients.admission
var discharge: DateTime? by Patients.discharge
var specialty: String by Patients.specialty
}
class PatientViewModel(patient: Patient) : ViewModel() {
val patientId = bind { patient.observable(Patient::patientId) }
val name = bind { patient.observable(Patient::name)}
val lastName = bind { patient.observable(Patient::lastName) }
val recordNumber = bind { patient.observable(Patient::recordNumber) }
val idCard = bind { patient.observable(Patient::idCard) }
val phone = bind { patient.observable(Patient::phone)}
val age = bind { patient.observable(Patient::age) }
val gender = bind { patient.observable(Patient::gender) }
val admission = bind { patient.observable(Patient::admission) }
val discharge = bind { patient.observable(Patient::discharge) }
val specialty = bind { patient.observable(Patient::specialty) }
}
Compiler error:
e: D:\projects\manager\src\main\kotlin\manager\model\Patient.kt: (49, 18): Type inference failed: Cannot infer type parameter T in inline fun <reified PropertyType : Property<T>, reified T : Any, ResultType : PropertyType> bind(autocommit: Boolean = ..., forceObjectProperty: Boolean = ..., defaultValue: T? = ..., noinline propertyProducer: () -> PropertyType?): ResultType
None of the following substitutions
(Boolean,Boolean,String?,() -> Property<String?>?)
(Boolean,Boolean,Any?,() -> Property<String?>?)
can be applied to
(() -> ObjectProperty<String?>)
Try doing this with your model bindings:
val patientId = bind(Patient::patientId)
I am making a toDoList app with multiple users. I have two entities. One is User and the other one is Task
#Entity(tableName = "tasks")
data class Task(
#PrimaryKey(autoGenerate = true)
val id : Int,
val taskUserId : Int,
val taskName : String,
val taskDescription : String,
var taskPriority : Int)
#Entity(tableName = "users")
data class User(
#PrimaryKey
val userId : Int,
val userName : String,
val password : String)
As you can see the two entities need to have one thing in common and that would be the users id so I could query for tasks based on the users id. This is what my dao looks like:
#Dao
interface UserDao {
#Query("SELECT * FROM users")
fun getAllUsers() : LiveData<List<User>>
#Query("SELECT * FROM tasks WHERE taskUserId LIKE :userId ORDER BY taskPriority DESC")
fun getAllUsersTasks(userId : Int) : LiveData<List<Task>>
#Insert
suspend fun saveUser(user : User)
#Insert
suspend fun saveTask(task : Task)
}
And here are the other model elements:
class TaskRepository(private val userDao: UserDao) {
var userId : Int = 0
val allTasks = userDao.getAllUsersTasks(userId)
val allUsers : LiveData<List<User>> = userDao.getAllUsers()
suspend fun saveUser(user : User){
userDao.saveUser(user)
}
suspend fun saveTask(task : Task){
userDao.saveTask(task)
}
}
And the ViewModel:
class MainViewModel(application: Application) : AndroidViewModel(application) {
private val taskRepository : TaskRepository
val allTasks : LiveData<List<Task>>
val allUsers : LiveData<List<User>>
private var userId : Int = 0
init {
val taskDao = UserTasksDatabase.getDatabase(application)!!.userDao()
taskRepository = TaskRepository(taskDao)
allTasks = taskRepository.allTasks
allUsers = taskRepository.allUsers
taskRepository.userId = this.userId
}
fun saveUser(user: User) = viewModelScope.launch(Dispatchers.IO){
taskRepository.saveUser(user)
}
fun saveTask(task: Task) = viewModelScope.launch(Dispatchers.IO){
taskRepository.saveTask(task)
}
fun setUserId(id : Int){
this.userId = id
}
fun getUserId() : Int{
return this.userId
}
}
When the user pushes the login or the sign up button I set the user's id in the viewModel
logInButton.setOnClickListener {
val userName: String = userNameEditText.text.toString()
val password: String = passwordEditText.text.toString()
val id: Int = IdMaker.generateId(userName)
val user = User(id, userName, password)
if(listOfAllUsers.contains(user)){
sharedViewModel.setUserId(id)
Toast.makeText(requireContext(), "Welcome $userName", Toast.LENGTH_LONG).show()
fragmentManager!!.beginTransaction().apply {
replace(R.id.fl_activity_main, MainFragment())
commit()
}
}else{
Toast.makeText(requireContext(), "The user already exists", Toast.LENGTH_LONG).show()
}
}
But when i try to access the users in the main fragment and set them on my recyclerview I get nothing. And I can't really see what am I missing.
sharedViewModel.allTasks.observe(this, Observer {tasks ->
tasks.let { adapter.setTasks(it) }
})
I'm sorry if a question is a bit simple but I hope I will gain some insight in what am I missing.
Side note: The recyclerview is set up properly and tested.
I think you are not setting the TaskRepository.userId correctly.
class MainViewModel(application: Application) : AndroidViewModel(application) {
private val taskRepository : TaskRepository
private var userId : Int = 0
init {
...
// init is called only once at the start of this viewModel's instance creation
// Here, you set your repository's userId to 0
taskRepository.userId = this.userId
}
}
At the instance creation of your MainViewModel, you set the userId of its TaskRepository instance to 0, which in your following codes is not changed anymore. Upon your login button click, you do call:
sharedViewModel.setUserId(id)
but the function alters only the viewModel's userId property, not the userId property of your TaskRepository instance. So try:
class MainViewModel(application: Application) : AndroidViewModel(application) {
private lateinit var taskRepository : TaskRepository
val allTasks = taskRepository.allTasks
val allUsers = taskRepository.allUsers
// remove init {}
fun setUserId(id : Int){
// set up the repository only after userId is known
val taskDao = UserTasksDatabase.getDatabase(application)!!.userDao()
taskRepository = TaskRepository(taskDao, id)
}
}
And alter your TaskRepository class:
class TaskRepository(private val userDao: UserDao, private val userId: Int) {
val allTasks: LiveData<List<Task>>
get() = userDao.getAllUsersTasks(userId)
val allUsers: LiveData<List<User>>
get() = userDao.getAllUsers()
...
}
This is annotation definition:
#Target(AnnotationTarget.PROPERTY)
#Retention(AnnotationRetention.RUNTIME)
#MustBeDocumented
annotation class MyAnno(val desc: String, val comment: String) { }
And below is where the MyAnno used:
class MyAnnoUser {
#MyAnno(desc = "name", comment = "name comment")
lateinit var name: String
#MyAnno(desc = "age", comment = "age comment")
var age: Int = 0
#MyAnno(desc = "money", comment = "money comment")
var money: Double = 0.0
#MyAnno(desc = "gender", comment = "gender comment")
var gender: Boolean = false
override fun toString(): String {
return "(name: $name; age: $age; money: $money; gender: ${if (gender) "men" else "women"})"
}
}
Here's code to read the value in MyAnno:
class MyAnnoExpression(val obj: Any, val context: Context) {
val numTypeSet = setOf("Int", "Double", "Byte")
fun expression() {
val clazz = obj::class
clazz.declaredMemberProperties.forEach { prop ->
val mutableProp = try {
prop as KMutableProperty<*>
} catch (e: Exception) {
null
} ?: return#forEach
val desc = mutableProp.findAnnotation<MyAnno>()
desc?.let {
val propClassName = mutableProp.returnType.toString().removePrefix("kotlin.")
when (propClassName) {
in numTypeSet -> mutableProp.setter.call(obj, (readProp(it, context) as kotlin.String).toNum(propClassName))
"String" -> mutableProp.setter.call(obj, (readProp(it, context) as kotlin.String))
"Boolean" -> mutableProp.setter.call(obj, (readProp(it, context) as kotlin.String).toBoolean())
}
}
}
}
private fun readProp(value: MyAnno, context: Context): Any? {
val prop = Properties()
val input = context.assets.open("app.properties")
prop.load(InputStreamReader(input, "utf-8"))
return prop.get(value.desc)
}
}
Now the Debugger shows me following info of value in readProp(...) function:
#com.demo.basekotlin.MyAnno(comment=age comment, desc=age)
But i got exception when read desc from value:
An exception occurs during Evaluate Expression Action : org.jetbrains.eval4j.VOID_VALUE cannot be cast to org.jetbrains.eval4j.AbstractValue
I can't find any thing wrong in my code, is there another program setting needed?
As I understand you just want to see annotation value for given property.
First, let's declare an annotation.
#Target(PROPERTY)
#Retention(AnnotationRetention.RUNTIME)
annotation class PropertyAnnotation(val desc: String)
Container:
class Container {
#PropertyAnnotation("Name")
var name: String? = null
#PropertyAnnotation("Age")
var age: Int = -1
var notAnnotatedProperty: String = "not annotated"
}
And finally, code responsible for get all declared properties, then find a properties annotated as PropertyAnnotation, cast it to it, and get value from it.
fun main() {
val container = Container()
container::class.declaredMemberProperties.forEach { property ->
(property.annotations.find {
it is PropertyAnnotation
} as? PropertyAnnotation)?.let {
println("Property: `$property` is ${it.desc}")
}
}
}
Output:
Property: `var Container.age: kotlin.Int` is Age
Property: `var Container.name: kotlin.String?` is Name
Kind ugly. But, let's use more Kotlin pro-dev-features.
Let's create extension function for any not-null type which returns all member property of given type:
inline fun <reified T : Any> Any.getMemberProperty(): List<T> {
return this::class.declaredMemberProperties.mapNotNull { prop ->
(prop.annotations.find { ann -> ann is T }) as? T
}
}
And now usage:
fun main() {
val container = Container()
container.getMemberProperty<PropertyAnnotation>().forEach {
println(it.desc)
}
}