How to define the map as a constant object, not in a method which will be created again and again - kotlin

This is my method which used to populate a map, and there's other data will be added in the future.
object ConfigurationService {
fun populateCache(client: RedissonClient): RMap<String, String> {
val map = client.getMap("map")
map["k1"] = "v1"
map["k2"] = "v2"
....
return map
}
}
The problem is, each time when I calling this method in my main function, it will re-create the same map content again and again, is there a way to define it as a constant map object, not in a method.
This is what I did, is that correct?
class ConfigurationService(client: RedissonClient) {
val map = client.getMap("map")
fun populateCache(): RMap<String, String> {
map["k1"] = "v1"
map["k2"] = "v2"
....
return map
}
}

One option is to extract the constant part as a property on your object:
object ConfigurationService {
private val fixedMapContent = mapOf(
"k1" to "v1",
"k2" to "v2",
)
fun populateCache(client: RedissonClient): RMap<String, String> {
val map = client.getMap("map")
map.putAll(fixedMapContent)
return map
}
}
But I don't think that's what you're looking for.
If the client to use is always the same, another option is to inject that client into ConfigurationService (which will not be an object anymore, you'll need to inject it as well):
class ConfigurationService(private val client: RedissonClient) {
val cache by lazy {
val map = client.getMap("map")
map["k1"] = "v1"
map["k2"] = "v2"
....
map
}
}
If you want to control when the cache is initially populated (instead of lazily on first access), you can also do this:
class ConfigurationService(client: RedissonClient) {
val cache = client.getMap("map")
fun populateCache() {
cache["k1"] = "v1"
cache["k2"] = "v2"
....
}
}

Related

find value in arraylist in kotlin

Hey I am working in kotlin. I am working on tree data structure. I added the value in list and now I want to find that value and modified their property. But I am getting the error.
VariantNode, StrengthNode, ProductVariant
StrengthNode.kt
class StrengthNode : VariantNode() {
var pricePerUnit: String? = null
var defaultValue = AtomicBoolean(false)
}
ActivityViewModel.kt
class ActivityViewModel : ViewModel() {
var baseNode: VariantNode = VariantNode()
private val defaultValueId = "12643423243324"
init {
createGraph()
}
private fun createGraph() {
val tempHashMap: MutableMap<String, VariantNode> = mutableMapOf()
val sortedList = getSortedList()
sortedList.forEach { productVariant ->
productVariant.strength?.let { strength ->
if (tempHashMap.containsKey("strength_${strength.value}")) {
baseNode.children.contains(VariantNode(strength.value)) // getting error
return#let
}
val tempNode = StrengthNode().apply {
value = strength
pricePerUnit = productVariant.pricePerUnit?.value
if (productVariant.id == defaultValueId) {
defaultValue.compareAndSet(false, true)
}
}
baseNode.children.add(tempNode)
tempHashMap["strength_${strength.value}"] = tempNode
}
productVariant.quantity?.let { quantity ->
if (tempHashMap.containsKey("strength_${productVariant.strength?.value}_quantity_${quantity.value}")) {
return#let
}
val tempNode = QuantityNode().apply {
value = quantity
}
val parent =
tempHashMap["strength_${productVariant.strength?.value}"] ?: baseNode
parent.children.add(tempNode)
tempHashMap["strength_${productVariant.strength?.value}_quantity_${quantity.value}"] =
tempNode
}
productVariant.subscription?.let { subscription ->
val tempNode = SubscriptionNode().apply {
value = subscription
}
val parent =
tempHashMap["strength_${productVariant.strength?.value}_quantity_${productVariant.quantity?.value}"]
?: baseNode
parent.children.add(tempNode)
}
}
baseNode
}
}
I am getting error on this.
I want to find that node value and modified other property.
Your class VariantNode only has a single no-arg constructor, but you're trying to call it with arguments, hence the error
Too many arguments for public constructor VariantNode() defined in com.example.optionsview.VariantNode
Either you have to provide a constructor, that matches your call, e.g.
open class VariantNode(var value: ProductValue?) {
var children: MutableList<VariantNode> = arrayListOf()
}
or you need to adjust your code to use the no-arg constructor instead.
val node = VariantNode()
node.value = strength.value
baseNode.children.contains(node)
Note however, that your call to contains most likely will not work, because you do not provide a custom implementation for equals. This is provided by default, when using a data class.
If you just want to validate whether baseNode.children has any element, where value has the expected value, you can use any instead, e.g.:
baseNode.children.any { it.value == strength.value }

Kotlin on Android: How to use LiveData from a database in a fragment?

I use MVVM and have a list of data elements in a database that is mapped through a DAO and repository to ViewModel functions.
Now, my problem is rather banal; I just want to use the data in fragment variables, but I get a type mismatch.
The MVVM introduces a bit of code, and for completeness of context I'll run through it, but I'll strip it to the essentials:
The data elements are of a data class, "Objects":
#Entity(tableName = "objects")
data class Objects(
#ColumnInfo(name = "object_name")
var objectName: String
) {
#PrimaryKey(autoGenerate = true)
var id: Int? = null
}
In ObjectsDao.kt:
#Dao
interface ObjectsDao {
#Query("SELECT * FROM objects")
fun getObjects(): LiveData<List<Objects>>
}
My database:
#Database(
entities = [Objects::class],
version = 1
)
abstract class ObjectsDatabase: RoomDatabase() {
abstract fun getObjectsDao(): ObjectsDao
companion object {
// create database
}
}
In ObjectsRepository.kt:
class ObjectsRepository (private val db: ObjectsDatabase) {
fun getObjects() = db.getObjectsDao().getObjects()
}
In ObjectsViewModel.kt:
class ObjectsViewModel(private val repository: ObjectsRepository): ViewModel() {
fun getObjects() = repository.getObjects()
}
In ObjectsFragment.kt:
class ObjectsFragment : Fragment(), KodeinAware {
private lateinit var viewModel: ObjectsViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this, factory).get(ObjectsViewModel::class.java)
// I use the objects in a recyclerview; rvObjectList
rvObjectList.layoutManager = GridLayoutManager(context, gridColumns)
val adapter = ObjectsAdapter(listOf(), viewModel)
// And I use an observer to keep the recyclerview updated
viewModel.getObjects.observe(viewLifecycleOwner, {
adapter.objects = it
adapter.notifyDataSetChanged()
})
}
}
The adapter:
class ObjectsAdapter(var objects: List<Objects>,
private val viewModel: ObjectsViewModel):
RecyclerView.Adapter<ObjectsAdapter.ObjectsViewHolder>() {
// Just a recyclerview adapter
}
Now, all the above works fine - but my problem is that I don't want to use the observer to populate the recyclerview; in the database I store some objects, but there are more objects that I don't want to store.
So, I try to do this instead (in the ObjectsFragment):
var otherObjects: List<Objects>
// ...
if (condition) {
adapter.objects = viewModel.getObjects()
} else {
adapter.objects = otherObjects
}
adapter.notifyDataSetChanged()
And, finally, my problem; I get type mismatch for the true condition assignment:
Type mismatch: inferred type is LiveData<List> but List was expected
I am unable to get my head around this. Isn't this pretty much what is happening in the observer? I know about backing properties, such as explained here, but I don't know how to do that when my data is not defined in the ViewModel.
We need something to switch data source. We pass switching data source event to viewModel.
mySwitch.setOnCheckedChangeListener { _, isChecked ->
viewModel.switchDataSource(isChecked)
}
In viewModel we handle switching data source
(To use switchMap include implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0")
class ObjectsViewModel(private val repository: ObjectsRepository) : ViewModel() {
// Best practice is to keep your data in viewModel. And it is useful for us in this case too.
private val otherObjects = listOf<Objects>()
private val _loadDataFromDataBase = MutableLiveData<Boolean>()
// In case your repository returns liveData of favorite list
// from dataBase replace MutableLiveData(otherObjects) with repository.getFavorite()
fun getObjects() = _loadDataFromDataBase.switchMap {
if (it) repository.getObjects() else MutableLiveData(otherObjects)
}
fun switchDataSource(fromDataBase: Boolean) {
_loadDataFromDataBase.value = fromDataBase
}
}
In activity/fragment observe getObjects()
viewModel.getObjects.observe(viewLifecycleOwner, {
adapter.objects = it
adapter.notifyDataSetChanged()
})
You can do something like this:
var displayDataFromDatabase = true // Choose whatever default fits your use-case
var databaseList = emptyList<Objects>() // List we get from database
val otherList = // The other list that you want to show
toggleSwitch.setOnCheckedChangeListener { _, isChecked ->
displayDataFromDatabase = isChecked // Or the negation of this
// Update adapter to use databaseList or otherList depending upon "isChecked"
}
viewModel.getObjects.observe(viewLifecycleOwner) { list ->
databaseList = list
if(displayDataFromDatabase)
// Update adapter to use this databaseList
}

Map Key Values to Dataclass in Kotlin

how can I set properties of a dataclass by its name. For example, I have a raw HTTP GET response
propA=valueA
propB=valueB
and a data class in Kotlin
data class Test(var propA: String = "", var propB: String = ""){}
in my code i have an function that splits the response to a key value array
val test: Test = Test()
rawResp?.split('\n')?.forEach { item: String ->
run {
val keyValue = item.split('=')
TODO
}
}
In JavaScript I can do the following
response.split('\n').forEach(item => {
let keyValue = item.split('=');
this.test[keyValue[0]] = keyValue[1];
});
Is there a similar way in Kotlin?
You cannot readily do this in Kotlin the same way you would in JavaScript (unless you are prepared to handle reflection yourself), but there is a possibility of using a Kotlin feature called Delegated Properties (particularly, a use case Storing Properties in a Map of that feature).
Here is an example specific to code in your original question:
class Test(private val map: Map<String, String>) {
val propA: String by map
val propB: String by map
override fun toString() = "${javaClass.simpleName}(propA=$propA,propB=$propB)"
}
fun main() {
val rawResp: String? = """
propA=valueA
propB=valueB
""".trimIndent()
val props = rawResp?.split('\n')?.map { item ->
val (key, value) = item.split('=')
key to value
}?.toMap() ?: emptyMap()
val test = Test(props)
println("Property 'propA' of test is: ${test.propA}")
println("Or using toString: $test")
}
This outputs:
Property 'propA' of test is: valueA
Or using toString: Test(propA=valueA,propB=valueB)
Unfortunately, you cannot use data classes with property delegation the way you would expect, so you have to 'pay the price' and define the overridden methods (toString, equals, hashCode) on your own if you need them.
By the question, it was not clear for me if each line represents a Test instance or not. So
If not.
fun parse(rawResp: String): Test = rawResp.split("\n").flatMap { it.split("=") }.let { Test(it[0], it[1]) }
If yes.
fun parse(rawResp: String): List<Test> = rawResp.split("\n").map { it.split("=") }.map { Test(it[0], it[1]) }
For null safe alternative you can use nullableString.orEmpty()...

Singleton serialization in Kotlin

I'm wondering if it's possible in Kotlin to deserialize (restore property values) of a declared object, without having to manually assign the properties or resorting to reflection. The following snippet further explains:
object Foo: Serializable {
var propOne: String = ""
// ...
fun persist() {
serialize(this)
// no problem with serialization
}
fun restore(bytes: ByteArray) {
val fooObj: Foo = deserialize(bytes) as Foo
// It seems Kotlin allows us to use singleton as type!
// obvioulsly either of the following is wrong:
// this = fooObj
// Foo = fooObj
// ... is there a way to 'recover' the singleton (object) other than
// manual assignment of properties (or reflection) ???
}
}
There is no way to reassign the global reference to a singleton with a new instance. At most you can write out the properties during serialization, and then on deserialization directly read the properties and mutate the state in the original object. It will require custom code for you to assign the properties into the object either by direct assignment or reflection. It would be better if you make your own singleton mechanism that holds an instance that you can swap out to be another instance that you deserialize.
have faced with the same issue, and want to share with you my solution:
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature
import java.io.File
import java.lang.reflect.Modifier
typealias ObjMap = HashMap<String, Any?>
fun <T : Any> T.getInstance() : Any? {
val target = if(this is Class<*>) this else javaClass
return target.getDeclaredField("INSTANCE")?.get(null)
}
class ObjectHelper {
companion object {
val mapper = ObjectMapper().apply {
enable(SerializationFeature.INDENT_OUTPUT)
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
}
fun objectToMap(obj: Any): ObjMap {
var res = ObjMap()
val instance = obj.getInstance()
val o = instance ?: obj
o.javaClass.declaredFields.forEach {
if(it.name != "INSTANCE") {
it.isAccessible = true
val value = if(Modifier.isStatic(it.modifiers)) it.get(null) else it.get(o)
res[it.name] = value
}
}
o.javaClass.classes.forEach {
res[it.simpleName] = objectToMap(it)
}
return res
}
fun saveObject(path: String, obj: Any) {
mapper.writeValue(File(path), objectToMap(obj))
}
fun loadObject(path: String, obj: Any) {
val json = mapper.readValue<HashMap<*,*>>(File(path), HashMap::class.java) as ObjMap
loadObject(obj, json)
}
fun loadObject(obj: Any, props: ObjMap) {
val objectParam = mapper.writeValueAsString(props)
mapper.readValue(objectParam, obj::class.java)
obj.javaClass.classes.forEach {
val instance = it.getInstance()
val map = props[it.simpleName]
if(map != null && instance != null) {
loadObject(instance, map as ObjMap)
}
}
}
}
}
Usage example:
object TestObj {
var f1: String = "f1"
var f2: String = "f2"
object TestObj_2 {
var f1: String = "f1_1"
var f2: String = "f2_2"
}
}
TestObj.f1 = "aaa"
saveObject("out.json", TestObj)
TestObj.f1 = "bbb"
loadObject("out.json", TestObj)
println(TestObj.f1)
Result will be "aaa".

Ignore setter and set property directly

I have a class that writes a user to SharedPreferences every time it is set:
class UserManager #Inject constructor(
val prefs: SharedPreferences,
val jsonAdapter: JsonAdapter<User>
) {
companion object {
val USER = "user"
}
var user: User = User()
set(value) {
field = value
prefs.edit().putString(USER, jsonAdapter.toJson(user)).apply()
}
init {
val userString = prefs.getString(USER, null)
if (userString != null) {
user = jsonAdapter.fromJson(userString)
}
}
}
Problem: If the user is set in the init block, it calls the setter and writes the user that we just got from the shared prefs... to the shared prefs.
Question 1: How can I directly set the property from the init block?
Question 2: Why do I have to initialize the User when I define a custom setter, but can omit the initialization when the default setter is used?
You need to directily initiliaze the property with the correct value. You can do this using the run function from the stdlib:
class UserManager #Inject constructor(
val prefs: SharedPreferences,
val jsonAdapter: JsonAdapter<User>
) {
companion object {
val USER = "user"
}
var user: User = run {
val userString = prefs.getString(USER, null)
if (userString != null) {
jsonAdapter.fromJson(userString)
} else {
User()
}
}
set(value) {
field = value
prefs.edit().putString(USER, jsonAdapter.toJson(user)).apply()
}
}
Shorter syntax proposed by Ilya Ryzhenkov on the Kotlin Slack:
var user: User = prefs.getString(USER, null)?.let { jsonAdapter.fromJson(it) } ?: User()
set(value) {
field = value
prefs.edit().putString(USER, jsonAdapter.toJson(user)).apply()
}
I believe the best solution is to use the 'backing property' concept described here: https://kotlinlang.org/docs/reference/properties.html#backing-properties
private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
if (_table == null)
_table = HashMap() // Type parameters are inferred
return _table ?: throw AssertionError("Set to null by another thread")
}
Then initialize the backing property in the constructor and do <backingproperty> = value instead of field = value as well as point the getter to the backing property.
Take a look at by map delegate, seems like this is the pattern you want:
class User(val map: MutableMap<String, Any?>) {
var name: String by map
var age: Int by map
}
https://kotlinlang.org/docs/reference/delegated-properties.html#storing-properties-in-a-map