Kotlin. Find element by param in nested lists - kotlin

I have some data clases
data class Data(val docNumber: Int?,
val docType: Int?,
val fiscprops: List<FiscProp>,
val name: String?
) {
fun getFiscProp(tag: Int) = fiscprops.firstOrNull { it.tag == tag }
}
data class FiscProp(val caption: String?,
val printable: String?,
val tag: Int?,
val value: Any?,
val fiscprops: List<FiscProp>?)
I need to find FiscProp by tag in nested lists. If I use getFiscProp I can find FiscProp only if it is on first level of hierarchy.
How can I find element on the all levels? I don't know how many levels will be.

val FiscProp.allProps: Sequence<FiscProp>
get() = sequence {
yield(this#allProps)
fiscprops?.forEach {
yieldAll(it.allProps)
}
}
class Data(...) {
fun getFiscProp(tag: Int) = fiscprops.asSequence()
.flatMap { it.allProps}
.firstOrNull { it.tag == tag }
}

You can use a basic recursive search for this:
fun List<FiscProp>.getFiscProp(tag: Int): FiscProp? {
for (fiscProp in this) {
if (fiscProp.tag == tag)
return fiscProp
val found = fiscProp.fiscprops?.getFiscProp(tag)
if (found != null)
return found
}
return null
}
I think this functional version might work too, but I didn't test it:
fun List<FiscProp>.getFiscProp(tag: Int): FiscProp? = firstNotNullOfOrNull {
if (it.tag == tag) it else it.fiscprops?.getFiscProp(tag)
}

Here's a tail-recursive version.
data class Data(...) {
fun findByTag(tag: Int) = findByTag(fiscprops, tag)
private tailrec fun findByTag(fiscprops: List<FiscProp>, tag: Int): FiscProp? =
if (fiscprops.isEmpty()) null
else {
val fiscprop = fiscprops.first()
if (fiscprop.tag == tag) fiscprop
else findByTag(fiscprops.drop(1) + fiscprop.fiscprops.orEmpty(), tag)
}
}

Related

Filter searchView from RecycleView with Adapter

Adapter class
class AppListAdapter(private val context: Context, initialChecked: ArrayList<String> = arrayListOf()) : RecyclerView.Adapter<AppListAdapter.AppViewHolder>() {
public val appList = arrayListOf<ApplicationInfo>()
private val checkedAppList = arrayListOf<Boolean>()
private val packageManager: PackageManager = context.packageManager
init {
context.packageManager.getInstalledApplications(PackageManager.GET_META_DATA).sortedBy { it.loadLabel(packageManager).toString() }.forEach { info ->
if (info.packageName != context.packageName) {
if (info.flags and ApplicationInfo.FLAG_SYSTEM == 0) {
appList.add(info)
checkedAppList.add(initialChecked.contains(info.packageName))
}
}
}
}
inner class AppViewHolder(private val item: ItemAppBinding) : RecyclerView.ViewHolder(item.root) {
fun bind(data: ApplicationInfo, position: Int) {
item.txApp.text = data.loadLabel(packageManager)
item.imgIcon.setImageDrawable(data.loadIcon(packageManager))
item.cbApp.isChecked = checkedAppList[position]
item.cbApp.setOnCheckedChangeListener { _, checked ->
checkedAppList[position] = checked
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppViewHolder {
return AppViewHolder(ItemAppBinding.inflate(LayoutInflater.from(context), parent, false))
}
override fun onBindViewHolder(holder: AppViewHolder, position: Int) {
holder.bind(appList[position], position)
}
override fun getItemCount(): Int {
return appList.size
}
on MainActivity
binding.searchView2.setOnQueryTextListener(object : SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(query: String?): Boolean {
binding.searchView2.clearFocus()
// how to write code filtered by query?
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
// how to write code filtered by newText?
return false
}
})
I'm newbie in kotlin..anyone can help?
I believe you want to filter items with "ApplicationInfo.txApp", so i will write the code for it.
First you need your adapter class to extend Filterable like below, and add one more list to hold all items:
class AppListAdapter(private val context: Context, initialChecked: ArrayList<String> = arrayListOf()) : RecyclerView.Adapter<AppListAdapter.AppViewHolder>(), Filterable {
public val appList = arrayListOf<ApplicationInfo>()
public val appListFull = ArrayList<ApplicationInfo>(appList)
// This full list because of when you delete all the typing to searchView
// so it will get back to that full form.
Then override it's function and write your own filter to work, paste this code to your adapter:
override fun getFilter(): Filter {
return exampleFilter
}
private val exampleFilter = object : Filter() {
override fun performFiltering(constraint: CharSequence?): FilterResults? {
val filteredList: ArrayList<ApplicationInfo> = ArrayList()
if (constraint == null || constraint.isEmpty()) {
// when searchview is empty
filteredList.addAll(appListFull)
} else {
// when you type something
// it also uses Locale in case capital letters different.
val filterPattern = constraint.toString().lowercase(Locale.getDefault()).trim()
for (item in appListFull) {
val txApp = item.txApp
if (txApp.lowercase(Locale.getDefault()).contains(filterPattern)) {
filteredList.add(item)
}
}
}
val results = FilterResults()
results.values = filteredList
return results
}
#SuppressLint("NotifyDataSetChanged") #Suppress("UNCHECKED_CAST")
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
appList.clear()
appList.addAll(results!!.values as ArrayList<ApplicationInfo>)
notifyDataSetChanged()
}
}
And finally call this filter method in your searchview:
binding.searchView2.setOnQueryTextListener(object : SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(query: String?): Boolean {
yourAdapter.filter.filter(query)
yourAdapter.notifyDataSetChanged()
binding.searchView2.clearFocus()
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
yourAdapter.filter.filter(newText)
yourAdapter.notifyDataSetChanged()
return false
}
})
These should work i'm using something similar to that, if not let me know with the problem.

How to use Either monad and avoid nested flatMap

I'm in a situation where I'm trying to setup some data and then call a service. Each step can fail, so I'm trying to use Arrow's Either to manage this.
But I'm ending up with a lot of nested flatMaps.
The following code snippet illustrates what I'm trying to do:
import arrow.core.Either
import arrow.core.flatMap
typealias ErrorResponse = String
typealias SuccessResponse = String
data class Foo(val userId: Int, val orderId: Int, val otherField: String)
data class User(val userId: Int, val username: String)
data class Order(val orderId: Int, val otherField: String)
interface MyService {
fun doSomething(foo: Foo, user: User, order: Order): Either<ErrorResponse, SuccessResponse> {
return Either.Right("ok")
}
}
fun parseJson(raw: String): Either<ErrorResponse, Foo> = TODO()
fun lookupUser(userId: Int): Either<ErrorResponse, User> = TODO()
fun lookupOrder(orderId: Int): Either<ErrorResponse, Order> = TODO()
fun start(rawData: String, myService: MyService): Either<ErrorResponse, SuccessResponse> {
val foo = parseJson(rawData)
val user = foo.flatMap {
lookupUser(it.userId)
}
//I want to lookupOrder only when foo and lookupUser are successful
val order = user.flatMap {
foo.flatMap { lookupOrder(it.orderId) }
}
//Only when all 3 are successful, call the service
return foo.flatMap { f ->
user.flatMap { u ->
order.flatMap { o ->
myService.doSomething(f, u, o)
}
}
}
}
I'm sure there is a better way to do this. Can someone help me with an idiomatic approach?
You can use the either { } DSL, this is available in a suspend manner or in a non-suspend manner through the either.eager { } builder.
That way you can use suspend fun <E, A> Either<E, A>.bind(): A.
Rewriting your code example:
fun start(rawData: String, myService: MyService): Either<ErrorResponse, SuccessResponse> =
either.eager {
val foo = parseJson(rawData).bind()
val user = lookupUser(foo.userId).bind()
val order = lookupOrder(foo.orderId).bind()
myService.doSomething(foo, user, order).bind()
}
If you run into an Either.Left, then bind() will short-circuit the either.eager block and return with the encountered Either.Left value.

Kotlinx Serialization - Custom serializer to ignore null value

Let's say I'm having a class like:
#Serializable
data class MyClass(
#SerialName("a") val a: String?,
#SerialName("b") val b: String
)
Assume the a is null and b's value is "b value", then Json.stringify(MyClass.serializer(), this) produces:
{ "a": null, "b": "b value" }
Basically if a is null, I wanted to get this:
{ "b": "b value" }
From some research I found this is currently not doable out of the box with Kotlinx Serialization so I was trying to build a custom serializer to explicitly ignore null value. I followed the guide from here but couldn't make a correct one.
Can someone please shed my some light? Thanks.
You can use explicitNulls = false
example:
#OptIn(ExperimentalSerializationApi::class)
val format = Json { explicitNulls = false }
#Serializable
data class Project(
val name: String,
val language: String,
val version: String? = "1.3.0",
val website: String?,
)
fun main() {
val data = Project("kotlinx.serialization", "Kotlin", null, null)
val json = format.encodeToString(data)
println(json) // {"name":"kotlinx.serialization","language":"Kotlin"}
}
https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/json.md#explicit-nulls
Use encodeDefaults = false property in JsonConfiguration and it won't serialize nulls (or other optional values)
Try this (not tested, just based on adapting the example):
#Serializable
data class MyClass(val a: String?, val b: String) {
#Serializer(forClass = MyClass::class)
companion object : KSerializer<MyClass> {
override val descriptor: SerialDescriptor = object : SerialClassDescImpl("MyClass") {
init {
addElement("a")
addElement("b")
}
}
override fun serialize(encoder: Encoder, obj: MyClass) {
encoder.beginStructure(descriptor).run {
obj.a?.let { encodeStringElement(descriptor, 0, obj.a) }
encodeStringElement(descriptor, 1, obj.b)
endStructure(descriptor)
}
}
override fun deserialize(decoder: Decoder): MyClass {
var a: String? = null
var b = ""
decoder.beginStructure(descriptor).run {
loop# while (true) {
when (val i = decodeElementIndex(descriptor)) {
CompositeDecoder.READ_DONE -> break#loop
0 -> a = decodeStringElement(descriptor, i)
1 -> b = decodeStringElement(descriptor, i)
else -> throw SerializationException("Unknown index $i")
}
}
endStructure(descriptor)
}
return MyClass(a, b)
}
}
}
Since I was also struggling with this one let me share with you the solution I found that is per property and does not require to create serializer for the whole class.
class ExcludeIfNullSerializer : KSerializer<String?> {
override fun deserialize(decoder: Decoder): String {
return decoder.decodeString()
}
override val descriptor: SerialDescriptor
get() = PrimitiveSerialDescriptor("ExcludeNullString", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: String?) {
if (value != null) {
encoder.encodeString(value)
}
}
}
will work as expected with the following class
#Serializable
class TestDto(
#SerialName("someString")
val someString: String,
#SerialName("id")
#EncodeDefault(EncodeDefault.Mode.NEVER)
#Serializable(with = ExcludeIfNullSerializer::class)
val id: String? = null
)
Note the #EncodeDefault(EncodeDefault.Mode.NEVER) is crucial here in case you using JsonBuilder with encodeDefaults = true, as in this case the serialization library will still add the 'id' json key even if the value of id field is null unless using this annotation.
JsonConfiguration is deprecated in favor of Json {} builder since kotlinx.serialization 1.0.0-RC according to its changelog.
Now you have to code like this:
val json = Json { encodeDefaults = false }
val body = json.encodeToString(someSerializableObject)
As of now, for anyone seeing this pos today, default values are not serialized (see https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/basic-serialization.md#defaults-are-not-encoded-by-default)
So you simply add to set a default null value, and it will not be serialized.

how to get the data from an getParcelableArrayListExtra<Parcelable> in kotlin

How to get the data from an getParcelableArrayListExtra in kotlin?.
I have a Parceable data class called FoodParceable
data class FoodParceable(val idCover: Int, val name: String, val
price: Double):Parcelable {
constructor(parcel: Parcel) : this(
parcel.readInt(),
parcel.readString(),
parcel.readDouble()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(idCover)
parcel.writeString(name)
parcel.writeDouble(price)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<FoodParceable> {
override fun createFromParcel(parcel: Parcel): FoodParceable {
return FoodParceable(parcel)
}
override fun newArray(size: Int): Array<FoodParceable?> {
return arrayOfNulls(size)
}
}
}
In the current Acitvity A, fill in the arrayListParceable and send it to activity B.
val arrayListParceable = ArrayList<FoodParceable>()
for (Food in listFood) {
arrayListParceable.add(FoodParceable(R.mipmap.ic_food_meat, "Carne", 9.99))
arrayListParceable.add(FoodParceable(R.mipmap.ic_food_meat, "Vegetales", 29.99))
arrayListParceable.add(FoodParceable(R.mipmap.ic_food_meat, "Frutas", 39.99))
}
val intent = Intent()
intent.putParcelableArrayListExtra(LIST_PRODUCT,arrayListParceable)
activity?.setResult(Activity.RESULT_OK, intent)
When I get the value in activity B, in list item, I can not enter its content.
if(resultCode == Activity.RESULT_OK){
if(data != null){
val listItems = data.getParcelableArrayListExtra<Parcelable>(LIST_PRODUCT)
listItems[0]. //ERROR
}
}
I am new to kotlin, thank you for your comments. Regards
You have to give Kotlin more information about the type of your object list. Try this:
val listItems : ArrayList<FoodParceable> = data.getParcelableArrayListExtra(LIST_PRODUCT)

Invoking Action by reference in Kotlin

I've a Map of (key, value) where the value is a predefined function.
I want to iterate the input param in the Mp and check where the key is matching with the input parameter, then invoke the equivalent function, something like this
My code required to be something like below:
fun fn1: Unit { // using Unit is optional
println("Hi there!")
}
fun fn2 {
println("Hi again!")
}
fun MainFun(x: int){
val map: HashMap<Int, String> = hashMapOf(1 to fn1, 2 to fn2)
for ((key, value) in map) {
// if key = x then run/invoke the function mapped with x, for example if x = 1 then invoke fn1
}
}
Notes: I read something like below, but could not know how to us them:
inline fun <K, V> Map<out K, V>.filter(
predicate: (Entry<K, V>) -> Boolean
): Map<K, V> (source)
val russianNames = arrayOf("Maksim", "Artem", "Sophia", "Maria", "Maksim")
val selectedName = russianNames
.filter { it.startsWith("m", ignoreCase = true) }
.sortedBy { it.length }
.firstOrNull()
Hi I hope this would help you.
fun fn1() {
println("Hi there!")
}
fun fn2() {
println("Hi again!")
}
fun main(args: IntArray){
val map = hashMapOf(
1 to ::fn1,
2 to ::fn2)
map.filterKeys { it == args[0] } // filters the map by comparing the first int arg passed and the key
.map { it.value.invoke() } // invoke the function that passed the filter.
}
If the keyis RegEx then map.filterKeys { Regex(it).matches(x) } can be used, below full example of it Try Kotlin:
data class Person(val name: String,
val age: Int? = null)
val persons = listOf(Person("Alice"),
Person("Bob", age = 23))
fun old() {
val oldest = persons.maxBy { it.age ?: 0 }
println("The oldest is: $oldest")
}
fun young() {
val youngest = persons.minBy { it.age ?: 0 }
println("The youngest is: $youngest")
}
fun selection(x: String) {
val map = mapOf(
"old|big" to ::old,
"new|young" to ::young)
map.filterKeys { Regex(it).matches(x) }
.map { it.value.invoke() }
}
fun main(args: Array<String>) {
selection("new")
}
fun fn1() {
println("Hi there!")
}
fun fn2() {
println("Hi again!")
}
fun main(args: Array<Int>){
val map = hashMapOf(1 to ::fn1, 2 to ::fn2)
map.forEach { key, function -> function.invoke() }
}
This will do the work but your code does not even have the correct syntax. You should learn the basic first.