Room database - Dao - Repository - ViewModel - repository

Writing good code between Dao - Repository - ViewModel.
The function always return null value.
from Entity:
#Entity(tableName = "diabete")
class Diabete(
#PrimaryKey(autoGenerate = true)
#NonNull
#ColumnInfo(name = "diabeteId")
var id: Int,
var date: LocalDateTime,
var balise: String,
var taux: Float,
var note: String
)
from Dao:
// Obtenir la moyenne du taux selon la balise
#Query("SELECT IFNULL(avg(taux), 0.0) FROM diabete WHERE balise = :balise")
fun avgByBalise(balise: String): LiveData<Float>
from Repository:
fun avgBaliseAJeun(balise: String): LiveData<Float> {
return dbDao.avgByBalise(balise)
}
from ViewModel:
fun avgBaliseAJeune(balise: String): LiveData<Float> {
val result = MutableLiveData<Float>()
viewModelScope.launch(Dispatchers.IO) {
val retour = repository.avgBaliseAJeun(balise)
result.postValue(retour.value)
}
return result
}
from Fragment:
val avgBaliseAJeun: Float = dpViewModel.avgBaliseAJeune("À jeun").observeAsState(initial = 0F).value
This line always return null when debugging.
All the compilation is ok.
The application crash when running.
What is missing?

Congrats on asking your first question, welcome!
A (not-so) fun thing to find out is that LiveData values can be null even if declared as non-nullable source.
The second thing to keep in mind is that upon app start up live data coming from room has a slight lag to it which means that even if the data is in Room, it will initially be null as seen is possible above. When you take the value of it then you see that it is null. It would intuitively make sense that you could use it like this but it doesn't support that.
Instead it should be used in Activities and Fragments like this:
dpViewModel.avgBaliseAJeune("À jeun").observe(this, result -> {
//Do something with 'result'
});
further reading on live data in fragments
or if you want to use Jetpack Compose then you would use something like
val avgBaliseAJeun by dpViewModel.avgBaliseAJeune("À jeun").observeAsState()
Finally I see your viewModel code is converting live data to mutablelivedata, but instead of that I recommend using the live data as a read only pipe flowing out of the repostiory/Room. Then separately make a function to change the value by inserting/updating to Room through the Dao. You can do that anywhere in the code and then the live data will observe those changes.
Google has some great codelabs to check out some samples with Room, LiveData, Flow, ViewModels (though not always all at once). Feel free to follow up here more as well!

Related

Handle NPE in Kotlin Flow For Room Database

I want to retrieve single object from Room database, so i have this method in Dao
// in Dao
#Query("SELECT * FROM table_foo ORDER BY RANDOM()")
fun getSingleFoo(): Flow<FooEntity>
That object then will be mapped into others model, let say PlainFoo.
// in Repository
fun getRandomFoo(): Flow<PlainFoo> = dao.getSingleFoo()
.map(FooEntity::asExternalModel)
But in the first launch of this app, the table is empty. It makes the dao function return null and trigger NPE when being mapped. I try to wrap it inside a sealed interface like this.
// Result.kt as wrapper
sealed interface Result<out T> {
data class Success<T>(val data: T) : Result<T>
data class Error(val exception: Throwable? = null) : Result<Nothing>
}
fun <T> Flow<T>.asResult(): Flow<Result<T>> = this
.map<T, Result<T>> {
Result.Success(it)
}
.catch {
emit(Result.Error(it))
}
And then i call this method in the presentation layer like this.
// in ViewModel
val randomFoo = fooRepository.getRandomFoo().asResult()
// in activity, log only for checking
lifecycleScope.launch {
viewModel.randomFoo.collect {
Timber.tag("RandomFooFlow").d("$it")
}
}
It catches the error, which look like this.
Error(exception=java.lang.NullPointerException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkNotNullParameter, parameter <this>)
But when new data is inserted, it does not get updated unless i reopen the app (which means new Flow is being collected, not the old one). So it seems that the flow is cancelled.
Is there any way to handle this without making my Dao return a
nullable object?
Note: if the data is already populated when opening the app, the flow is able to keep consuming new value).
Instead of dealing with exceptions, I would suggest to return nullable types from your Dao. You can then also update your mapper function to handle the type nullability. You won't need to wrap it into any Result class, just a simple null check on the UI end would suffice.
// Dao
#Query("SELECT * FROM table_foo ORDER BY RANDOM()")
fun getSingleFoo(): Flow<FooEntity?>
// Repo
fun getRandomFoo(): Flow<PlainFoo?> = dao.getSingleFoo().map { it?.asExternalModel() }
Could you please call repository getRandomFoo() method from inside coroutine in view model ? And also you need to call response with data observe like LiveData or StateFlow. By the way, you can wrap your result with wrap inside repository. In code example, I do not care about it because your error is not related with mapping.
View Model
private val _stateFlow = MutableStateFlow()
val stateFlow:StateFlow
fun getRandom(){
fooRepository.getRandomFoo().onEach{
if(it is Result.Success){
stateFlow.value = it
}
}.launchIn(viewModelScope)
}
Fragment or activity
viewLifecycleOwner.lifecycle.repeatOnLifecycle{
stateFlow.collect{
// Listen data for your UI
}
}

FP patterns for combining data from different sources (preferrably in Kotlin and Arrow)

Disclaimer upfront: Recently, my interest in functional programming has grown and I've been able to apply the most basic approaches (using pure functions as much as my knowledge and working environment permits) in my job. However, I'm still very inexperienced when it comes to more advanced techniques and I thought trying to learn some by asking a question on this site might be the right idea. I've stumbled over similar issues once every while, so I think there should be patterns in FP to deal with this type of problems.
Problem description
It boils down to the following. Suppose there is an API somewhere providing a list of all possible pets.
data class Pet(val name: String, val isFavorite: Boolean = false)
fun fetchAllPetsFromApi(): List<Pet> {
// this would call a real API irl
return listOf(Pet("Dog"), Pet("Cat"), Pet("Parrot"))
}
This API knows nothing about the "favorite" field and it shouldn't. It's not under my control. It's basically just returning a list of pets. Now I want to allow users to mark pets as their favorite. And I store this flag in a local database.
So after fetching all pets from the api, I have to set the favorite flag according to the persisted data.
class FavoriteRepository {
fun petsWithUserFavoriteFlag(allPets: List<Pet>) {
return allPets.map { it.copy(isFavorite = getFavoriteFlagFromDbFor(it) }
}
fun markPetAsFavorite(pet: Pet) {
// persist to db ...
}
fun getFavoriteFlagFromDbFor(pet: Pet): Boolean {...}
}
For some reason, I think this code dealing with the problem of "fetch one part of the information from one data source, then merge it with some information from another" might benefit from the application of FP patterns, but I'm not really sure in which direction to look.
I've read some of the documentation of Arrow (great project btw :)) and am quite a Kotlin enthusiast, so answers utilizing this library would be very appreciated.
Here's something I'd potentially do. Your code has a couple of important flaws that make it unsafe from the functional programming perspective:
It doesn't flag side effects, so compiler is not aware of those and cannot track how they're are used. That means we could call those effects from anywhere without any sort of control. Examples of effects would be the network query or all the operations using the database.
Your operations don't make explicit the fact that they might succeed or fail, so callers are left to try / catch exceptions or the program will blow up. So, there's not a strong requirement to handle both scenarios, which could drive to missing some exceptions and therefore get runtime errors.
Let's try to fix it. Let's start by modeling our domain errors so we have a set of expected errors that our domain understands. Let's also create a mapper so we map all potential exceptions thrown to one of those expected domain errors, so our business logic can react to those accordingly.
sealed class Error {
object Error1 : Error()
object Error2 : Error()
object Error3 : Error()
}
// Stubbed
fun Throwable.toDomainError() = Error.Error1
As you see, we're stubbing the errors and the mapper. You can put time on designing what errors you'll need for your domain on an architecture level and write a proper pure mapper for those. Let's keep going.
Time for flagging our effects to make the compiler aware of those. To do that we use suspend in Kotlin. suspend enforces a calling context at compile time, so you cannot ever call the effect unless you're within a suspended environment or the integration point (a couroutine). We are going to flag as suspend all operations that would be a side effect here: the network request and all db operations.
I'm also taking the freedom to pull out all DB operations to a Database collaborator just for readability.
suspend fun fetchAllPetsFromApi(): List<Pet> = ...
class FavoriteRepository(private val db: Database = Database()) {
suspend fun petsWithUserFavoriteFlag(allPets: List<Pet>) {
... will delegate in the Database ops
}
}
class Database {
// This would flag it as fav on the corresponding table
suspend fun markPetAsFavorite(pet: Pet): Pet = ...
// This would get the flag from the corresponding table
suspend fun getFavoriteFlagFromDbFor(pet: Pet) = ...
}
Our side effects are safe now. They've become description of effects instead, since we cannot ever run them without providing an environment capable of running suspended effects (a coroutine or another suspended function). In functional jargon we'd say our effects are pure now.
Now, let's go for the second issue.
We also said that we were not making explicit the fact that each effect might succeed or fail, so callers might miss potential exceptions thrown and get the program blown up. We can raise that concern over our data by wrapping it with the functional Either<A, B> data type. Let's combine both ideas together:
suspend fun fetchAllPetsFromApi(): Either<Error, List<Pet>> = ...
class FavoriteRepository(private val db: Database = Database()) {
suspend fun petsWithUserFavoriteFlag(allPets: List<Pet>): Either<Error, List<Pet>> {
... will delegate in the Database ops
}
}
class Database {
// This would flag it as fav on the corresponding table
suspend fun markPetAsFavorite(pet: Pet): Either<Error, Pet> = ...
// This would get the flag from the corresponding table
suspend fun getFavoriteFlagFromDbFor(pet: Pet): Either<Error, Boolean> = ...
}
Now this makes explicit the fact that each one of those computations might succeed or fail, so the caller will be forced to handle both sides and will not forget about handling the potential errors. We're using the types in our benefit here.
Let's add the logics for the effects now:
// Stubbing a list of pets but you'd have your network request within the catch block
suspend fun fetchAllPetsFromApi(): Either<Error, List<Pet>> =
Either.catch { listOf(Pet("Dog"), Pet("Cat")) }.mapLeft { it.toDomainError() }
We can use Either#catch to wrap any suspended effects that might throw. This automatically wraps the result into Either so we can keep computing over it.
More specifically, it wraps the result of the block in Either.Right in case it succeeds, or the exception into Either.Left in case it throws. We also have mapLeft to map potential exceptions thrown (Left side) to one of our strongly typed domain errors. That is why it returns Either<Error, List<Pet>> instead of Either<Throwable, List<Pet>>.
Note that with Either we always model errors on the left side. This is by convention, since Right represents the happy path and we want our successful data there, so we can keep computing over it with map, flatMap, or whatever.
We can apply the same idea for our db methods now:
class Database {
// This would flag it as fav on the corresponding table, I'm stubbing it here for the example.
suspend fun markPetAsFavorite(pet: Pet): Either<Error, Pet> =
Either.catch { pet }.mapLeft { it.toDomainError() }
// This would get the flag from the corresponding table, I'm stubbing it here for the example.
suspend fun getFavoriteFlagFromDbFor(pet: Pet): Either<Error, Boolean> =
Either.catch { true }.mapLeft { it.toDomainError() }
}
We're stubbing the results again, but you can imagine we'd have our actual suspended effects loading from or updating the DB tables inside each Either.catch {} block above.
Finally, we can add some logic to the repo:
class FavoriteRepository(private val db: Database = Database()) {
suspend fun petsWithUserFavoriteFlag(allPets: List<Pet>): Either<Error, List<Pet>> =
allPets.map { pet ->
db.getFavoriteFlagFromDbFor(pet).map { isFavInDb ->
pet.copy(isFavorite = isFavInDb)
}
}.sequence(Either.applicative()).fix().map { it.toList() }
}
Ok this one might be a bit more complex due to how our effects are written, but I'll try to make it clear.
We need to map the list so for each pet loaded from network we can load its fav state from the Database. Then we copy it as you were doing. But given getFavoriteFlagFromDbFor(pet) returns Either<Error, Booelan> now we'd have a List<Either<Error, Pet>> as a result 🤔 That might make it hard to work with the complete list of pets, since we'd need to iterate and for each one first we'd need to check whether it's Left or Right.
To make it easier to consume the List<Pet> as a whole, we might want to swap the types here, so we'd have Either<Error, List<Pet>> instead.
To this magic, one option would be sequence. sequence requires the Either applicative in this case since that'll be used to lift the intermediate results and the final list into Either.
We're also using the chance to map the ListK into the stdlib List instead, since ListK is what sequence uses internally, but we can understand it as a functional wrapped over List in broad words, so you have an idea. Since here we're only interested on the actual list to match our types, we can map the Right<ListK<Pet>> to Right<List<Pet>>.
Finally, we can go ahead and consume this suspended program:
suspend fun main() {
val repo = FavoriteRepository()
val hydratedPets = fetchAllPetsFromApi().flatMap { pets -> repo.petsWithUserFavoriteFlag(pets) }
hydratedPets.fold(
ifLeft = { error -> println(error) },
ifRight = { pets -> println(pets) }
)
}
We're going for flatMap since we have sequential ops here.
There are potential optimizations we could do like using parTraverse to load all the fav states from DB for a list of pets in parallel and gather results in the end, but I didn't use it since I'm not sure your database is prepared for concurrent access.
Here's how you could do it:
suspend fun petsWithUserFavoriteFlag(allPets: List<Pet>): Either<Error, List<Pet>> =
allPets.parTraverse { pet ->
db.getFavoriteFlagFromDbFor(pet).map { isFavInDb ->
pet.copy(isFavorite = isFavInDb)
}
}.sequence(Either.applicative()).fix().map { it.toList() }
I think we could also simplify the whole thing a bit more by changing some of the types and how operations are structured but wasn't sure about refactoring it too much from your codebase since I'm not aware of your current team constraints.
And here's the complete codebase:
import arrow.core.Either
import arrow.core.extensions.either.applicative.applicative
import arrow.core.extensions.list.traverse.sequence
import arrow.core.extensions.listk.foldable.toList
import arrow.core.fix
import arrow.core.flatMap
data class Pet(val name: String, val isFavorite: Boolean = false)
// Our sealed hierarchy of potential errors our domain understands
sealed class Error {
object Error1 : Error()
object Error2 : Error()
object Error3 : Error()
}
// Stubbed, would be a mapper from throwable to any of the expected domain errors used via mapLeft.
fun Throwable.toDomainError() = Error.Error1
// This would call a real API irl, stubbed here for the example.
suspend fun fetchAllPetsFromApi(): Either<Error, List<Pet>> =
Either.catch { listOf(Pet("Dog"), Pet("Cat")) }.mapLeft { it.toDomainError() }
class FavoriteRepository(private val db: Database = Database()) {
suspend fun petsWithUserFavoriteFlag(allPets: List<Pet>): Either<Error, List<Pet>> =
allPets.map { pet ->
db.getFavoriteFlagFromDbFor(pet).map { isFavInDb ->
pet.copy(isFavorite = isFavInDb)
}
}.sequence(Either.applicative()).fix().map { it.toList() }
}
class Database {
// This would flag it as fav on the corresponding table, I'm stubbing it here for the example.
suspend fun markPetAsFavorite(pet: Pet): Either<Error, Pet> =
Either.catch { pet }.mapLeft { it.toDomainError() }
// This would get the flag from the corresponding table, I'm stubbing it here for the example.
suspend fun getFavoriteFlagFromDbFor(pet: Pet): Either<Error, Boolean> =
Either.catch { true }.mapLeft { it.toDomainError() }
}
suspend fun main() {
val repo = FavoriteRepository()
val hydratedPets = fetchAllPetsFromApi().flatMap { pets -> repo.petsWithUserFavoriteFlag(pets) }
hydratedPets.fold(
ifLeft = { error -> println(error) },
ifRight = { pets -> println(pets) }
)
}

Nested safe call Null check Kotlin

I need to display an image in an ImageView, so I need to make sure that the image url is not null first. Are these 3 options valid?
Answer data class
data class Answer(
val id: Long?,
val title: String?,
val answerImage: AnswerImage?
) {
data class AnswerImage(
val x0: AnswerImageData?,
val x1: AnswerImageData?,
val x2: AnswerImageData?
) {
data class AnswerImageData(
val id: String?,
val url: String?
)
}
}
Option 1
answer.answerImage?.let { answerImage ->
answerImage.x0?.let { answerImageData ->
answerImageData.url?.let {
//display image
}
}
}
Option 2
if (answer.answerImage?.x0?.url != null)
{
//display image
}
Option 3
answer.answerImage?.x0?.url?.let {
//display image
}
Short answer: yes.
Option 1: Would only be a good choice if you actually need to do more things with answerImage and answerImageData rather than just cast it safely. In this specific case, we don't have a use for declaring those variables explicitly. To conclude: option 1 in this case is not a very neat solution, but it does work.
Option 2: should work, because all attributes are immutable. The compiler can then deduce on the next line (inside if scope), that the url property will still be non-null.
Option 3: this is in my opinion the best one: it's the easiest one to process as a reader of the code, as you would generally finish it with code like this: .let { safeUrl -> .. }.
As specified by #Ken Van Hoeylandt all 3 options are valid, another valid option could be to use elvis operator:
fun attemptDisplayingImage(answer: Answer) {
val answerImage = answer.answerImage ?: return
val answerImageData = answerImage.x0 ?: return
val answerImageDataUrl = answerImageData.url ?: return
// display image
}
There's an interesting article about this topic here
Ken covered the answers (they're all fine and do the same thing, the last one is how the language is designed to be used really, nice and neat!) but I wanted to touch on your actual data model.
First, you say you need to check that an AnswerImageData's url isn't null. But the only reason it could be null, is because you've explicitly made it nullable, with a String? type. Is an AnswerImageData with a null url ever valid? Or does it always need to have one? I'm guessing it does, and I'm guessing it always needs an id too - so just make them non-null!
data class AnswerImageData(
val id: String,
val url: String
)
Now all your AnswerImageData objects are guaranteed to have non-null values - they're all valid in that sense, it's baked into your design. So you don't need to null check them anymore!
And the same goes for your other classes - can you have an AnswerImage with null values? This might be a trickier one, let's assume there needs to always be at least one AnswerImageData in an AnswerImage - in which case you can make the first non-null, and the others optional:
data class AnswerImage(
val x0: AnswerImageData,
val x1: AnswerImageData?,
val x2: AnswerImageData?
)
This isn't necessarily the best way to do this - I'd personally prefer a vararg parameter, or some kind of collection, so you can have an arbitrary number of AnswerImageDatas and do operations like .first(predicate) to loop over them all easily. But if you want exactly three slots, three parameters is a way to do it!
Same goes for Answer - I'm guessing that requires an id, title and answerImage - if so, don't let them be null. Enforce that valid structure through your types, it'll make your life a lot easier! And so will avoiding nullable types unless you actually need them!
I don't know if that applies to what you're doing, but it probably does, so it's worth mentioning. (This kind of thing is called *Domain-Driven Design if you want to look into it - basically enforcing the rules and structure of your business logic through the way you design your code, your types, your objects etc.)
All the answers above were great but I wanted to mention something. You declared your properties as nullable so I'm guessing you are getting them from somewhere else (from your data layer if you're familiar with clean architecture).
my recommendation is to create a domain model for your class and map the data to your domain model(which has non-null properties). this way you handle nulls in the mapper. it's cleaner and follows the separation of concerns and single responsibility principles.
interface Mapper<F, S> {
fun firstToSecond(first: F): S
fun secondToFirst(second: S): F
}
data class DataModel(
val id: Long?,
val title: String?,
val answerImage: AnswerImage?
)
data class DomainModel(
val id: Long,
val title: String,
val answerImage: AnswerImage
)
class DataToDomainMapper: Mapper<DataModel, DomainModel> {
override fun firstToSecond(first: DataModel): DomainModel {
return DomainModel(
id = first.id ?: -1,
title = first.title ?: "no title",
answerImage = first.answerImage ?: AnswerImage()
)
}
override fun secondToFirst(second: DomainModel): DataModel {
return DataModel(
id = second.id,
title = second.title,
answerImage = second.answerImage
)
}
}
this way you don't have to handle nulls anywhere else in your code. and for data validation, you can check the id not to be negative. I've shortened your models, but you get the idea

Kotlin multiple class for data storage

I am developing a simple Android app, that will display an icon of a vehicle and the user can click on the icon to display the vehicle information. I want to load the data dynamically when I build the app i.e. the data will come from an external source including the picture for the icon.
I am new to Kotlin and not sure what to search for to understand a suitable solution. What is the correct way to define the data, is it best to create an class as below then create an array of the class (not sure if this is possible)
public class VehicleSpec()
{
var OEM: String? = null
var ModelName: String? = null
var EngineSize: String? = null
}
Or would be better to create a multiple dimension array and then link the data to the cells?
var VehicleSpec = arrayOf(20,20)
VehicleSpec[0][0] = Null //OEM
VehicleSpec[0][1] = Null //ModelName
VehicleSpec[0][2] = Null //EngineSize
What is the best way to set up the data storage, is there any good references to understand how this should be setup?
What is the correct way to define the data, is it best to create an class as below then create an array of the class
Using an array for the properties of an object is not making the full use of the type safety you have in Kotlin (and even Java for that matter).
If what you want to express is multiple properties of an object, then you should use a class to define those properties. This is especially true if the properties have different types.
There is no performance difference between an array and a class, because you'll get a reference to the heap in both cases. You could save on performance only if you convert your multi-dimensional array approach to a single-dimension array with smart indexing. Most of the time, you should not consider this option unless you are handling a lot of data and if you know that performance is an issue at this specific level.
(not sure if this is possible)
Defining lists/arrays of classes is definitely possible.
Usually, for classes that are only used as data containers, you should prefer data classes, because they give you useful methods for free, and these methods totally make sense for simple "data bags" like in your case (equals, hashcode, component access, etc.).
data class Vehicle(
val OEM: String,
val ModelName: String,
val EngineSize: String
)
Also, I suggest using val instead of var as much as possible. Immutability is more idiomatic in Kotlin.
Last but not least, prefer non-null values to null values if you know a value must always be present. If there are valid cases where the value is absent, you should use null instead of a placeholder value like empty string or -1.
First at all, using the "class aprocah" makes it easy for you to understand and give you the full benefits of the language itself... so dont dry to save data in an array .. let the compiler handle those stuff.
Secondly i suggest you have maybe two types (and use data classes ;-) )
data class VehicleListEntry(
val id: Long,
val name: String
)
and
data class VehicleSpec(
val id: Long,
val oem: String = "",
val modelName: String = "",
val engineSize: String = ""
)
from my perspective try to avoid null values whenever possible.
So if you have strings - which you are display only - use empty strings instead of null.
and now have a Model to store your data
class VehicleModel() {
private val specs: MutableMap<Long, VehicleSpec> = mutableMapOf()
private var entries: List<VehicleListEntry> = listOf()
fun getSpec(id: Long) = specs[id]
fun addSpec(spec: VehicleSpec) = specs[spec.id] = spec
fun getEntries(): List<VehicleListEntry> = entries
fun setEntries(data: List<VehicleListEntry>) {
entries = data.toMutableList()
}
}
You could also use a data class for your model which looks like
data class VehicleModel(
val specs: MutableMap<Long, VehicleSpec> = mutableMapOf(),
var entries: List<VehicleListEntry> = listOf()
)
And last but not least a controller for getting stuff together
class VehicleController() {
private val model = VehicleModel()
init{
// TODO get the entries list together
}
fun getEntries() = model.entries
fun getSpec(id: Long) : VehicleSpec? {
// TODO load the data from external source (or check the model first)
// TODO store the data into the model
// TODO return result
}
}

How do I cast a JavaScript object to a Kotlin object?

I have received a JavaScript object in response to a remote HTTP request. I have a kotlin model (trait) that defines the various fields I expect on the object (the nullable ones are optional).
First, I want to do an is check to make sure my object is in fact of the expected type. I initially tried payload is MyModel but that doesn't work due to the way the is operator is written in kotlin.js.
Second, I want to cast to MyModel so I can get auto-complete, etc. on the object while I work with it. Normally, the is alone would be enough but since that doesn't work I need something for this problem as well.
I would like to avoid manually populating my object from a dynamic. I wouldn't mind doing this so much if I could use by Delegates.mapVal(...) but that requires a Map<String, Any?> and I don't know how to get my dynamic/Any? payload into a Map<String, Any?>.
1) We don't have structure check for is in performance reasons.
I don't sure that we need generic solution for this, but anyway I created issue about it, feel free to vote or star it to get updates.
2) is enough if you use smart cast, like:
if (payload is MyModel) {
// call MyModel members on payload
}
But don't forget about (1) :)
3) You can write something like:
class MapDynamic<out V>(val d: dynamic) {
public fun get(thisRef: Any, desc: PropertyMetadata): V {
return d[desc.name]
}
}
class Foo(data: dynamic) {
val field: Int by MapDynamic(data)
}
fun main(args : Array<String>) {
val f = Foo(object { val field = 123 })
println(f.field)
}
But it looks too verbose, but You can add additional logic for e.g. when data don't have requested field. And if You don't need custom logic I think cast is enough.
For the second part, the cast, you can do:
fun responseHandler(payload: dynamic) {
val myModel = payload as MyModel
}
or
fun responseHandler(payload: dynamic) {
val myModel: MyModel = payload
}
This will throw an NPE if payload is null, but it won't actually validate that the payload matches MyModel. In particular, you may end up with null fields/properties that shouldn't be if the payload was missing those fields/properties.