I have a default function like this:
fun <T> makeDefault(): Animal<T> = Animal<Nothing>(
name = "",
size = 0,
age = 0
)
I saw that there is the by operator, which can be used for view models like this:
val model: MyViewModel by viewModels()
Question: How I can create a function that behaves like that for my makeDefault()?
What I want to do:
val animal: Animal<Dog> by makeDefault()
You can create your own Delegate, not extension:
import kotlin.reflect.KProperty
class DefaultDelegate<T> {
private var created: Animal<T> = Animal<Nothing>(
name = "",
size = 0,
age = 0
)
operator fun getValue(thisRef: Any?, property: KProperty<*>): Animal<T> {
return created
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Animal<T>) {
created = value
}
}
Then use it:
val animal: Animal<Dog> by DefaultDelegate()
For more information, see Delegated properties doc
Related
I am struggling to understand what is the best way to get this to work.
I have some input fields and I created a TextFieldState to keep all the state in one place.
But it is not triggering a re-composition of the composable so the state never updates.
I saw this stack overflow answer on a similar question, but I just find it confusing and it doesn't make sense to me
Here is the code:
The Composable:
#Composable
fun AddTrip (
addTripVm: AddTripVm = hiltViewModel()
) {
var name = addTripVm.getNameState()
var stateTest = addTripVm.getStateTest()
Column(
//verticalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxSize()
) {
Text(text = "Add Trip")
Column(
){
println("From Composable: ${name.value.value}") //No Recomposition
meTextField(
value = name.value.value,
onChange = {
addTripVm.updateName(it)
},
placeholder = "Name",
)
}
View Model code:
#HiltViewModel
class AddTripVm #Inject constructor(
private val tripRepository: TripRepositoryContract,
private val tripValidator: TripValidatorContract
): TripValidatorContract by tripValidator, ViewModel() {
/**
* Name of the trip, this is required
*/
private val nameState: MutableState<TextFieldState> = mutableStateOf(TextFieldState())
private var stateTest = mutableStateOf("");
fun updateStateTest(newValue: String) {
stateTest.value = newValue
}
fun getStateTest(): MutableState<String> {
return stateTest
}
fun getNameState(): MutableState<TextFieldState> {
return nameState;
}
fun updateName(name: String) {
println("From ViewModel? $name")
nameState.value.value = name
println("From ViewModel after update: ${nameState.value.value}") //Updates perfectly
}
}
Text field state:
data class TextFieldState(
var value: String = "",
var isValid: Boolean? = null,
var errorMessage: String? = null
)
Is this possible? Or do I need to separate the value as a string and keep the state separate for if its valid or not?
You don't change instance of nameState's value with
nameState.value.value = name
It's the same object which State checks by default with
fun <T> structuralEqualityPolicy(): SnapshotMutationPolicy<T> =
StructuralEqualityPolicy as SnapshotMutationPolicy<T>
private object StructuralEqualityPolicy : SnapshotMutationPolicy<Any?> {
override fun equivalent(a: Any?, b: Any?) = a == b
override fun toString() = "StructuralEqualityPolicy"
}
MutableState use this as
fun <T> mutableStateOf(
value: T,
policy: SnapshotMutationPolicy<T> = structuralEqualityPolicy()
): MutableState<T> = createSnapshotMutableState(value, policy)
Easiest way is to set
nameState.value = nameState.value.copy(value= name)
other option is to write your own SnapshotMutationPolicy
I want to save in my Room database an object where one of the variables can either be of on type or another. I thought a sealed class would make sense, so I took this approach:
sealed class BluetoothMessageType() {
data class Dbm(
val data: String
) : BluetoothMessageType()
data class Pwm(
val data: String
) : BluetoothMessageType()
}
Or even this, but it is not necessary. I found that this one gave me even more errors as it did not know how to handle the open val, so if I find a solution for the first version I would be happy anyway.
sealed class BluetoothMessageType(
open val data: String
) {
data class Dbm(
override val data: String
) : BluetoothMessageType()
data class Pwm(
override val data: String
) : BluetoothMessageType()
}
Then the Entity class
#Entity(tableName = MESSAGES_TABLE_NAME)
data class DatabaseBluetoothMessage(
#PrimaryKey(autoGenerate = true)
val id: Long = 0L,
val time: Long = Instant().millis,
val data: BluetoothMessageType
)
I have created a TypeConverter to convert it to and from a String as well, so I assume that it is not a problem.
First, is this possible? I assume this should function in a similar way that it would with an abstract class, but I have not managed to find a working solution with that either. If it is not possible, what sort of approach should I take when I want to save some data that may be either of one or another type if not with sealed classes?
We faced such problem when we tried using Polymorphism in our domain, and we solved it this way:
Domain:
We have a Photo model that looks like this:
sealed interface Photo {
val id: Long
data class Empty(
override val id: Long
) : Photo
data class Simple(
override val id: Long,
val hasStickers: Boolean,
val accessHash: Long,
val fileReferenceBase64: String,
val date: Int,
val sizes: List<PhotoSize>,
val dcId: Int
) : Photo
}
Photo has PhotoSize inside, it looks like this:
sealed interface PhotoSize {
val type: String
data class Empty(
override val type: String
) : PhotoSize
data class Simple(
override val type: String,
val location: FileLocation,
val width: Int,
val height: Int,
val size: Int,
) : PhotoSize
data class Cached(
override val type: String,
val location: FileLocation,
val width: Int,
val height: Int,
val bytesBase64: String,
) : PhotoSize
data class Stripped(
override val type: String,
val bytesBase64: String,
) : PhotoSize
}
Data:
There is much work to do in our data module to make this happen. I will decompose the process to three parts to make it look easier:
1. Entity:
So, using Room and SQL in general, it is hard to save such objects, so we had to come up with this idea. Our PhotoEntity (Which is the Local version of Photo from our domain looks like this:
#Entity
data class PhotoEntity(
// Shared columns
#PrimaryKey
val id: Long,
val type: Type,
// Simple Columns
val hasStickers: Boolean? = null,
val accessHash: Long? = null,
val fileReferenceBase64: String? = null,
val date: Int? = null,
val dcId: Int? = null
) {
enum class Type {
EMPTY,
SIMPLE,
}
}
And our PhotoSizeEntity looks like this:
#Entity
data class PhotoSizeEntity(
// Shared columns
#PrimaryKey
#Embedded
val identity: Identity,
val type: Type,
// Simple columns
#Embedded
val locationLocal: LocalFileLocation? = null,
val width: Int? = null,
val height: Int? = null,
val size: Int? = null,
// Cached and Stripped columns
val bytesBase64: String? = null,
) {
data class Identity(
val photoId: Long,
val sizeType: String
)
enum class Type {
EMPTY,
SIMPLE,
CACHED,
STRIPPED
}
}
Then we have this compound class to unite PhotoEntity and PhotoSizeEntity together, so we can retrieve all data required by our domain's model:
data class PhotoCompound(
#Embedded
val photo: PhotoEntity,
#Relation(entity = PhotoSizeEntity::class, parentColumn = "id", entityColumn = "photoId")
val sizes: List<PhotoSizeEntity>? = null,
)
2. Dao
So our dao should be able to store and retrieve this data. You can have two daos for PhotoEntity and PhotoSizeEntity instead of one, for the sake of flexibility, but in this example we will use a shared one, it looks like this:
#Dao
interface IPhotoDao {
#Transaction
#Query("SELECT * FROM PhotoEntity WHERE id = :id")
suspend fun getPhotoCompound(id: Long): PhotoCompound
#Transaction
suspend fun insertOrUpdateCompound(compound: PhotoCompound) {
compound.sizes?.let { sizes ->
insertOrUpdate(sizes)
}
insertOrUpdate(compound.photo)
}
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertOrUpdate(entity: PhotoEntity)
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertOrUpdate(entities: List<PhotoSizeEntity>)
}
3. Adapter:
After solving the problem of saving data to SQL database, we now need to solve the problem of converting between domain and local entities. Our Photo's converter aka adapter looks like this:
fun Photo.toCompound() = when(this) {
is Photo.Empty -> this.toCompound()
is Photo.Simple -> this.toCompound()
}
fun PhotoCompound.toModel() = when (photo.type) {
PhotoEntity.Type.EMPTY -> Photo.Empty(photo.id)
PhotoEntity.Type.SIMPLE -> this.toSimpleModel()
}
private fun PhotoCompound.toSimpleModel() = photo.run {
Photo.Simple(
id,
hasStickers!!,
accessHash!!,
fileReferenceBase64!!,
date!!,
sizes?.toModels()!!,
dcId!!
)
}
private fun Photo.Empty.toCompound(): PhotoCompound {
val photo = PhotoEntity(
id,
PhotoEntity.Type.EMPTY
)
return PhotoCompound(photo)
}
private fun Photo.Simple.toCompound(): PhotoCompound {
val photo = PhotoEntity(
id,
PhotoEntity.Type.SIMPLE,
hasStickers = hasStickers,
accessHash = accessHash,
fileReferenceBase64 = fileReferenceBase64,
date = date,
dcId = dcId,
)
val sizeEntities = sizes.toEntities(id)
return PhotoCompound(photo, sizeEntities)
}
And for the PhotoSize, it looks like this:
fun List<PhotoSize>.toEntities(photoId: Long) = map { photoSize ->
photoSize.toEntity(photoId)
}
fun PhotoSize.toEntity(photoId: Long) = when(this) {
is PhotoSize.Cached -> this.toEntity(photoId)
is PhotoSize.Empty -> this.toEntity(photoId)
is PhotoSize.Simple -> this.toEntity(photoId)
is PhotoSize.Stripped -> this.toEntity(photoId)
}
fun List<PhotoSizeEntity>.toModels() = map { photoSizeEntity ->
photoSizeEntity.toModel()
}
fun PhotoSizeEntity.toModel() = when(type) {
PhotoSizeEntity.Type.EMPTY -> this.toEmptyModel()
PhotoSizeEntity.Type.SIMPLE -> this.toSimpleModel()
PhotoSizeEntity.Type.CACHED -> this.toCachedModel()
PhotoSizeEntity.Type.STRIPPED -> this.toStrippedModel()
}
private fun PhotoSizeEntity.toEmptyModel() = PhotoSize.Empty(identity.sizeType)
private fun PhotoSizeEntity.toCachedModel() = PhotoSize.Cached(
identity.sizeType,
locationLocal?.toModel()!!,
width!!,
height!!,
bytesBase64!!
)
private fun PhotoSizeEntity.toSimpleModel() = PhotoSize.Simple(
identity.sizeType,
locationLocal?.toModel()!!,
width!!,
height!!,
size!!
)
private fun PhotoSizeEntity.toStrippedModel() = PhotoSize.Stripped(
identity.sizeType,
bytesBase64!!
)
private fun PhotoSize.Cached.toEntity(photoId: Long) = PhotoSizeEntity(
PhotoSizeEntity.Identity(photoId, type),
PhotoSizeEntity.Type.CACHED,
locationLocal = location.toEntity(),
width = width,
height = height,
bytesBase64 = bytesBase64
)
private fun PhotoSize.Simple.toEntity(photoId: Long) = PhotoSizeEntity(
PhotoSizeEntity.Identity(photoId, type),
PhotoSizeEntity.Type.SIMPLE,
locationLocal = location.toEntity(),
width = width,
height = height,
size = size
)
private fun PhotoSize.Stripped.toEntity(photoId: Long) = PhotoSizeEntity(
PhotoSizeEntity.Identity(photoId, type),
PhotoSizeEntity.Type.STRIPPED,
bytesBase64 = bytesBase64
)
private fun PhotoSize.Empty.toEntity(photoId: Long) = PhotoSizeEntity(
PhotoSizeEntity.Identity(photoId, type),
PhotoSizeEntity.Type.EMPTY
)
That's it!
Conclusion:
To save a sealed class to Room or SQL, whether as an Entity, or as an Embedded object, you need to have one big data class with all the properties, from all the sealed variants, and use an Enum type to indicate variant type to use later for conversion between domain and data, or for indication in your code if you don't use Clean Architecture. Hard, but solid and flexible. I hope Room will have some annotations that can generate such code to get rid of the boilerplate code.
PS: This class is taken from Telegram's scheme, they also solve the problem of polymorphism when it comes to communication with a server. Checkout their TL Language here: https://core.telegram.org/mtproto/TL
PS2: If you like Telegram's TL language, you can use this generator to generate Kotlin classes from scheme.tl files: https://github.com/tamimattafi/mtproto
EDIT: You can use this code generating library to automatically generate Dao for compound classes, to make it easier to insert, which removes a lot of boilerplate to map things correctly.
Link: https://github.com/tamimattafi/android-room-compound
Happy Coding!
In my case I did the following:
sealed class Status2() {
object Online : Status2()
object Offline : Status2()
override fun toString(): String {
return when (this) {
is Online ->"Online"
is Offline -> "Offline"
}
}
}
class StatusConverter{
#TypeConverter
fun toHealth(value: Boolean): Status2 {
return if (value){
Status2.Online
} else{
Status2.Offline
}
}
#TypeConverter
fun fromHealth(value: Status2):Boolean {
return when(value){
is Status2.Offline -> false
is Status2.Online -> true
}
}
}
#Dao
interface CourierDao2 {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertStatus(courier: CourierCurrentStatus)
#Query("SELECT * FROM CourierCurrentStatus")
fun getCourierStatus(): Flow<CourierCurrentStatus>
}
#Entity
data class CourierCurrentStatus(
#PrimaryKey
val id: Int = 0,
var status: Status2 = Status2.Offline
)
and it works like a charm
Extension functions are great for the SharedPreference api in android. Jake Wharton has an interesting implementation at time code 32:30 of this video tutorial where he implements SharedPreferences extension function like so:
preferences.edit{
set(USER_ID /*some string key constant somewhere*/, 42)
//...
}
while this is ok, its kind of verbose.
This tutorial by Krupal Shah explains how you can reduce the getter/setter extension functions of SharedPreferences to:
preferences[USER_ID] = 42
Log.i("User Id", preferences[USER_ID]) //User Id: 42
This is pretty good, but the brackets imply iterable semantics, IMO. While not the worst thing in the world, you just wish that you could implement a field extension of a SharedPreferences value by the key constant itself.
My question is, is there any way to implement this type of extension on SharedPreferences?
preferences.USER_ID = 42
Log.i("User Id", preferences.USER_ID) //User Id: 42
First, let's create general interface for providing instance of SharedPreferences:
interface SharedPreferencesProvider {
val sharedPreferences: SharedPreferences
}
After we have to create delegate for property which will read/write value to preferences:
object PreferencesDelegates {
fun string(
defaultValue: String = "",
key: String? = null
): ReadWriteProperty<SharedPreferencesProvider, String> =
StringPreferencesProperty(defaultValue, key)
}
private class StringPreferencesProperty(
private val defaultValue: String,
private val key: String?
) : ReadWriteProperty<SharedPreferencesProvider, String> {
override fun getValue(
thisRef: SharedPreferencesProvider,
property: KProperty<*>
): String {
val key = key ?: property.name
return thisRef.sharedPreferences.getString(key, defaultValue)
}
override fun setValue(
thisRef: SharedPreferencesProvider,
property: KProperty<*>,
value: String
) {
val key = key ?: property.name
thisRef.sharedPreferences.save(key, value)
}
}
PreferencesDelegates needed to hide implementation and add some readability to code. In the end it can be used like this:
class AccountRepository(
override val sharedPreferences: SharedPreferences
) : SharedPreferencesProvider {
var currentUserId by PreferencesDelegates.string()
var currentUserName by string() //With import
var currentUserNickname by string(key = "CUSTOM_KEY", defaultValue = "Unknown")
fun saveUser(id: String, name: String) {
this.currentUserId = id
this.currentUserName = name
}
}
Similar can be implemented int, float or even custom type:
open class CustomPreferencesProperty<T>(
defaultValue: T,
private val key: String?,
private val getMapper: (String) -> T,
private val setMapper: (T) -> String = { it.toString() }
) : ReadWriteProperty<SharedPreferencesProvider, T> {
private val defaultValueRaw: String = setMapper(defaultValue)
override fun getValue(
thisRef: SharedPreferencesProvider,
property: KProperty<*>
): T {
val key = property.name
return getMapper(thisRef.sharedPreferences.getString(key, defaultValueRaw))
}
override fun setValue(
thisRef: SharedPreferencesProvider,
property: KProperty<*>,
value: T
) {
val key = property.name
thisRef.sharedPreferences.save(key, setMapper(value))
}
}
I wrote small library which covers such case. You can find rest of implemented preferences here
EDIT. In case if you are using dagger:
class AccountRepository #Injcet constructor() : SharedPreferencesProvider {
#Inject
override lateinit var sharedPreferences: SharedPreferences
var currentUserId by PreferencesDelegates.string()
...
}
You could define a simple extension property with a getter and a setter
var SharedPreferences.userId
get() = getInt(USER_ID, 0)
set(value: Int) { edit().putInt(USER_ID, value).apply() }
In my activity I have a field that should be non-nullable and has a custom setter. I want to initialize the field in my onCreate method so I added lateinit to my variable declaration. But, apparently you cannot do that (at the moment): https://discuss.kotlinlang.org/t/lateinit-modifier-is-not-allowed-on-custom-setter/1999.
These are the workarounds I can see:
Do it the Java way. Make the field nullable and initialize it with null. I don't want to do that.
Initialize the field with a "default instance" of the type. That's what I currently do. But that would be too expensive for some types.
Can someone recommend a better way (that does not involve removing the custom setter)?
Replace it with a property backed by nullable property:
private var _tmp: String? = null
var tmp: String
get() = _tmp!!
set(value) {_tmp=value; println("tmp set to $value")}
Or this way, if you want it to be consistent with lateinit semantics:
private var _tmp: String? = null
var tmp: String
get() = _tmp ?: throw UninitializedPropertyAccessException("\"tmp\" was queried before being initialized")
set(value) {_tmp=value; println("tmp set to $value")}
This can be achieved by using a backing property (as per Pavlus's answer); however, I prefer to wrap it inside a delegate to avoid exposing it outside of the property's context:
open class LateInit<T: Any> : ReadWriteProperty<Any?, T> {
protected lateinit var field: T
final override fun getValue(thisRef: Any?, property: KProperty<*>) = get()
final override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) = set(value)
open fun get() = field
open fun set(value: T) { field = value }
}
This provides standard getters and setters that can be overridden with a custom implementation:
var upperCaseString by object : LateInit<String>() {
override fun set(value: String) {
field = value.toUpperCase()
}
}
However, since this implementation requires extending the delegate, the generic type cannot be inferred from the property type. This can overcome by taking the custom getter and setter as parameters:
class LateInit<T: Any>(private val getter: FieldHolder<T>.() -> T = { field },
private val setter: FieldHolder<T>.(T) -> Unit = { field = it }) :
ReadWriteProperty<Any?, T> {
private val fieldHolder = FieldHolder<T>()
override fun getValue(thisRef: Any?, property: KProperty<*>) = fieldHolder.getter()
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) =
fieldHolder.setter(value)
class FieldHolder<T: Any> {
lateinit var field: T
}
}
Which can then be used like this:
private var upperCaseString: String by LateInit(setter = { field = it.toUpperCase() })
I realized that you can also make your private property lateinit instead of making it nullable:
var tmp: T
get() = _tmp
set(value) {
_tmp = value
println("tmp set to $value")
}
private lateinit var _tmp: T
In Kotlin, how do i define a var that has a lazy default value ?
for example, a val would be something like this:
val toolbarColor by lazy {color(R.color.colorPrimary)}
What i want to do is, have a default value for some property (toolbarColor), and i can change that value for anything else. Is it possible?
EDIT: This does the partial trick.
var toolbarColor = R.color.colorPrimary
get() = color(field)
set(value){
field = value
}
Is it possible to ease this by writing
var toolbarColor = color(R.color.colorPrimary)
set(value){
field = value
}
in a way that the default value is computed lazily? At the moment it won't work because color() needs a Context that is only initialized later.
You can create your own delegate method:
private class ColorDelegate<T>(initializer: () -> T) : ReadWriteProperty<Any?, T> {
private var initializer: (() -> T)? = initializer
private var value: T? = null
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return value ?: initializer!!()
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value = value
}
}
Declare in some delegate:
object DelegatesExt {
fun <T> lazyColor(initializer: () -> T): ReadWriteProperty<Any?, T> = ColorDelegate(initializer)
}
And use as follow:
var toolbarColor by DelegatesExt.lazyColor {
// you can have access to your current context here.
// return the default color to be used
resources.getColor(R.color.your_color)
}
...
override fun onCreate(savedInstanceState: Bundle?) {
// some fun code
// toolbarColor at this point will be R.color.your_color
// but you can set it a new value
toolbarColor = resources.getColor(R.color.new_color)
// now toolbarColor has the new value that you provide.
}
I think this could be a cleaner way to do, but I don't know yet (starting with kotlin few days ago). I will take a look and see if this could be done with less code.
You can store your property in a map to basically create a mutable lazy. You need a mutable map (like a HashMap<K, V>) with a default function to delegate to:
var toolbarColor by hashMapOf<String, Any?>()
.withDefault { toolbarColor = R.color.colorPrimary; toolbarColor }
You'll also need to import some extension functions: import kotlin.properties.getValue and import kotlin.properties.setValue.
It would be nice if Kotlin provided something built-in and optimized for this (like a mutableLazy or something). As such, I've created KT-10451.