Duplicate code due to different list objects - kotlin

So I got a function in each of my classes which does the same but with diffrent objects in the list, for example I got the methode for Streets and HouseNumbers.
Here the example of the two nearly identical functions, first for Streets:
fun batchInsert(import: List<ImportStreet>, source: String) {
var part : MutableList<ImportStreet> = mutableListOf()
for(i in import)
{
part.add(i)
if(part.size % 25000 == 0)
{
batchUpdate(part, source)
part = mutableListOf()
}
}
if (part.size < 25000) {
batchUpdate(part, source)
}
}
Nearly the same but now for HouseNumbers:
fun batchInsert(import: List<ImportHouseNumber>, source: String) {
var part : MutableList<ImportHouseNumber> = mutableListOf()
for(i in import)
{
part.add(i)
if(part.size % 25000 == 0)
{
batchUpdate(part, source)
part = mutableListOf()
}
}
if (part.size < 25000) {
batchUpdate(part, source)
}
}
Is there a easy or efficent way to get rid of the duplicate code?

Using Kotlin Generics - generic functions, we can create a single function that will work with both of your classes. Any class actually, as long as we are not looking to access class specific functions.
fun <T> batchInsert(import: List<T>, source: String) {
var part: MutableList<T> = mutableListOf()
for (i in import) {
part.add(i)
if (part.size % 25000 == 0) {
batchUpdate(part, source)
part = mutableListOf()
}
}
if (part.size < 25000) {
batchUpdate(part, source)
}
}
I don't know what your batchUpdate fun does, or how it does it, but it can similarly be changed in the same manner:
fun <T> batchUpdate(batch: List<T>, source: String) {
//I don't know what this function does
}
We could also take advantage of Kotlin's chunked function and make the whole process a bit more efficient (we don't need to create a new list ourselfs).
fun <T> batchInsert(import: List<T>, source: String) {
import
.chunked(25000)
.forEach { currentBatch ->
batchUpdate(currentBatch, source)
}
}
Another "trick" that we can use would be to make the fun a Kotlin extension function (note it doesn't always make sense to use this, it depends on the rest of the project, I'll recommend that you read the docs from the link)
fun <T> List<T>.batchInsert(source:String){
chunked(25000)
.forEach { currentBatch ->
batchUpdate(currentBatch, source)
}
}
Which can be called like this:
fun main() {
val list = listOf<ImportHouseNumber>() //empty list just to show how the extension works
val secondList = listOf<ImportStreet>() //empty list just to show how the extension works
val source = "source"
list.batchInsert(source)
secondList.batchInsert(source)
}
A quick and dirty solution for the case when batchUpdate can't be a generic (see comments), would be to still have a generic fun between them that will reroute each class into a different direction. Something like this:
fun <T> batchUpdate(batch: List<T>, source: String) {
val first = batch.firstOrNull() ?: return
when (first) {
is ImportStreet -> { /* do stuff with the batch and source */ }
is ImportHouseNumber -> { /* do stuff with the batch and source */ }
}
}
Note: I'm not a big fan of doing things like this, and it is a code smell. But in some cases it is enough to get through an issue. If anybody else has other ideas please advise.

Related

kotlin variance works strangely in generic function

Here is the code
fun main() {
val sr = mutableListOf<Int>(1, 2, 3)
val des = mutableListOf<Number>()
copyData(sr, des)
des.forEach { println(it) }
}
// A:
fun <T> copyData(source: MutableList<out T>,
destination: MutableList< T>) {
for (item in source) {
destination.add(item)
}
}
In the above code, MutableList<out T> makes MutableList covariant. OK, I understand this. But if I change the function signature and have this:
// B:
fun <T> copyData(source: MutableList<T>,
destination: MutableList<in T>) {
for (item in source) {
destination.add(item)
}
}
destination: MutableList<in T> makes MutableList contravariant on T. OK, I understand. But what I cannot understand is how we can return item from source now that source is not allowed to produce any item. Regardless, the code works fine.
I reckon that by
Putting in in the last parameter, we leave the T in the first parameter unaffected.
Putting a variance modifier in the first parameter, all the following parameters are affected.
I would appreciate it if you could shed some light on this.

What is the type of a Kotlin 'data class'?

I have a situation where I need to create a copy of data class object. I don't know in advance which of the many data classes I have will come in into the function. I do know, however, that only data classes will be used as input to this function.
This is what didn't work:
fun doSomething(obj: Any): Any {
obj.copy(...) // <- there's no 'copy' on Any
...
}
This is what I really like to do:
fun doSomething(obj: KAnyDataClass): KAnyDataClass {
obj.copy(...) // <- works, data classes have a 'copy' method
...
}
I'm not a Kotlin developer, but it looks like the language does not support dynamic dispatch or traits. You might find success with the dynamic type, which just turns off the type-checker so it won't yell at you for using a method that it doesn't know about. However this opens up the possibility of a runtime error if you pass an argument that actually doesn't have that method.
There is no class or interface for data classes, but we know from the documentation of data classes that there are derived functions componentN and copy in each data class.
We can use that knowledge to write an abstract copy method that calls the copy method of a given arbitrary data class using reflection:
fun <T : Any> copy(data: T, vararg override: Pair<Int, Any?>): T {
val kClass = data::class
if (!kClass.isData) error("expected a data class")
val copyFun = kClass.functions.first { it.name == "copy" }
checkParameters(override, kClass)
val vals = determineComponentValues(copyFun, kClass, override, data)
#Suppress("UNCHECKED_CAST")
return copyFun.call(data, *vals) as T
}
/** check if override of parameter has the right type and nullability */
private fun <T : Any> checkParameters(
override: Array<out Pair<Int, Any?>>,
kClass: KClass<out T>
) {
override.forEach { (index, value) ->
val expectedType = kClass.functions.first { it.name == "component${index + 1}" }.returnType
if (value == null) {
if (!kClass.functions.first { it.name == "component${index + 1}" }.returnType.isMarkedNullable) {
error("value for parameter $index is null but parameter is not nullable")
}
} else {
if (!expectedType.jvmErasure.isSuperclassOf(value::class))
error("wrong type for parameter $index: expected $expectedType but was ${value::class}")
}
}
}
/** determine for each componentN the value from override or data element */
private fun <T : Any> determineComponentValues(
copyFun: KFunction<*>,
kClass: KClass<out T>,
override: Array<out Pair<Int, Any?>>,
data: T
): Array<Any?> {
val vals = (1 until copyFun.parameters.size)
.map { "component$it" }
.map { name -> kClass.functions.first { it.name == name } }
.mapIndexed { index, component ->
override.find { it.first == index }.let { if (it !== null) it.second else component.call(data) }
}
.toTypedArray()
return vals
}
Since this copy function is generic and not for a specific data class, it is not possible to specify overloads in the usual way, but I tried to support it in another way.
Let's say we have a data class and element
data class Example(
val a: Int,
val b: String,
)
val example: Any = Example(1, "x")
We can create a copy of example with copy(example) that has the same elements as the original.
If we want to override the first element, we cannot write copy(example, a = 2), but we can write copy(example, 0 to 2), saying that we want to override the first component with value 2.
Analogously we can write copy(example, 0 to 3, 1 to "y") to specify that we want to change the first and the second component.
I am not sure if this works for all cases since I just wrote it, but it should be a good start to work with.

ArrayList cannot be cast to java.lang.Object[]

I used one of the answers from this question to get help with previous error but now I'm getting another one. There's a suggested answer to this question but I'm unable to get solution out of it for my problem.
java.lang.ClassCastException: java.util.ArrayList cannot be cast to
java.lang.Object[]
private var data: Any? // fixed, can't change data type as it's in a compiled library to accept all kinds of data.
fun users() : ArrayList<User> {
return (data as Array<*>).filterIsInstance<User>() as ArrayList<User>
}
After the suggestions in the comment, the working code looks like this but I've another side effect, I can't add items to the array, the ArrayList remains empty.
fun users() : ArrayList<User> {
return (data as ArrayList<*>).filterIsInstance<User>() as ArrayList<User>
}
fun addItem(userVO: User) {
users().add(user)
}
Edit 2
val users: ArrayList<User> get() = ((data as? ArrayList<Any>)?.filterIsInstance<User>() ?: emptyList()) as ArrayList<User>
fun addItem(user: User) {
users.add(user)
}
Having untyped data as a source is not the best idea but if you are working with a third-party library you might have no choice. In that case, you may try the following:
private var data: Any? = null
#Suppress("UNCHECKED_CAST")
val users: List<User> get() = (data as? ArrayList<Any>)?.filterIsInstance<User>() ?: emptyList()
fun addUser(user: User) {
#Suppress("UNCHECKED_CAST")
(data as? ArrayList<Any>)?.add(user)
}
In the above, I suppose that the data list may contain not only users but other entities also.

Is there a simple way to get a object by _id in Kotlin?

I'm a beginner of Kotlin, I hope to get a object by _id in the following data structure, so I write the fun getMDetailByID(aMDetailsList:MDetailsList, _id:Long)... which is a traditional method.
But I think the fun is too complex, is there a simple way do that? such as use Lambda expression.
class UIMain : AppCompatActivity() {
data class BluetoothDef(val Status:Boolean=false)
data class WiFiDef(val Name:String, val Status:Boolean=false)
data class MDetail (
val _id: Long,
val bluetooth: BluetoothDef,
val wiFi:WiFiDef
)
data class MDetailsList(val mListMetail: MutableList<MDetail>)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.layout_main)
var mBluetoothDef1=BluetoothDef()
var mWiFiDef1=WiFiDef("MyConnect 1",true)
var aMDetail1= MDetail(5L,mBluetoothDef1,mWiFiDef1)
var mBluetoothDef2=BluetoothDef(true)
var mWiFiDef2=WiFiDef("MyConnect 2")
var aMDetail2= MDetail(6L,mBluetoothDef2,mWiFiDef2)
val mListMetail:MutableList<MDetail> = mutableListOf(aMDetail1,aMDetail2)
var aMDetailsList=MDetailsList(mListMetail)
var c=getMDetailByID(aMDetailsList,5L)
}
fun getMDetailByID(aMDetailsList:MDetailsList, _id:Long):MDetail?{
var aMDetail: MDetail?=null
var a=aMDetailsList.mListMetail
for (b in a){
if (b._id==_id){
aMDetail=b
}
}
return aMDetail
}
}
A faster and simpler alternative to current answers (it's also better than your original code, since it doesn't always need to traverse the entire list):
fun getMDetailByID(aMDetailsList: MDetailsList, _id: Long) =
aMDetailsList.mListMetail.findLast { it._id == _id }
Also, consider whether you actually benefit from defining MDetailsList as a class instead of directly using (Mutable)List<MDetail> or making it a typealias. There are cases in which you do need such a type, but they aren't that common.
There is a simpler way using lambdas, indeed. You could replace your function code by:
fun getMDetailByID(aMDetailsList: MDetailsList, _id: Long): MDetail? {
return aMDetailsList.mListMetail.filter { it._id == _id }.lastOrNull()
}
It will, like your implementation did, return the last matching element or null if there is none.
Simpler code can be like:
fun getMDetailByID(aMDetailsList: MDetailsList, _id: Long) = aMDetailsList.mListMetail.filter { it._id == _id }.lastOrNull()

How do I write to a file in Kotlin?

I can't seem to find this question yet, but what is the simplest, most-idiomatic way of opening/creating a file, writing to it, and then closing it? Looking at the kotlin.io reference and the Java documentation I managed to get this:
fun write() {
val writer = PrintWriter("file.txt") // java.io.PrintWriter
for ((member, originalInput) in history) { // history: Map<Member, String>
writer.append("$member, $originalInput\n")
}
writer.close()
}
This works, but I was wondering if there was a "proper" Kotlin way of doing this?
A bit more idiomatic. For PrintWriter, this example:
File("somefile.txt").printWriter().use { out ->
history.forEach {
out.println("${it.key}, ${it.value}")
}
}
The for loop, or forEach depends on your style. No reason to use append(x) since that is basically write(x.toString()) and you already give it a string. And println(x) basically does write(x) after converting a null to "null". And println() does the correct line ending.
If you are using data classes of Kotlin, they can already be output because they have a nice toString() method already.
Also, in this case if you wanted to use BufferedWriter it would produce the same results:
File("somefile.txt").bufferedWriter().use { out ->
history.forEach {
out.write("${it.key}, ${it.value}\n")
}
}
Also you can use out.newLine() instead of \n if you want it to be correct for the current operating system in which it is running. And if you were doing that all the time, you would likely create an extension function:
fun BufferedWriter.writeLn(line: String) {
this.write(line)
this.newLine()
}
And then use that instead:
File("somefile.txt").bufferedWriter().use { out ->
history.forEach {
out.writeLn("${it.key}, ${it.value}")
}
}
And that's how Kotlin rolls. Change things in API's to make them how you want them to be.
Wildly different flavours for this are in another answer: https://stackoverflow.com/a/35462184/3679676
Other fun variations so you can see the power of Kotlin:
A quick version by creating the string to write all at once:
File("somefile.txt").writeText(history.entries.joinToString("\n") { "${it.key}, ${it.value}" })
// or just use the toString() method without transform:
File("somefile.txt").writeText(x.entries.joinToString("\n"))
Or assuming you might do other functional things like filter lines or take only the first 100, etc. You could go this route:
File("somefile.txt").printWriter().use { out ->
history.map { "${it.key}, ${it.value}" }
.filter { ... }
.take(100)
.forEach { out.println(it) }
}
Or given an Iterable, allow writing it to a file using a transform to a string, by creating extension functions (similar to writeText() version above, but streams the content instead of materializing a big string first):
fun <T: Any> Iterable<T>.toFile(output: File, transform: (T)->String = {it.toString()}) {
output.bufferedWriter().use { out ->
this.map(transform).forEach { out.write(it); out.newLine() }
}
}
fun <T: Any> Iterable<T>.toFile(outputFilename: String, transform: (T)->String = {it.toString()}) {
this.toFile(File(outputFilename), transform)
}
used as any of these:
history.entries.toFile(File("somefile.txt")) { "${it.key}, ${it.value}" }
history.entries.toFile("somefile.txt") { "${it.key}, ${it.value}" }
or use default toString() on each item:
history.entries.toFile(File("somefile.txt"))
history.entries.toFile("somefile.txt")
Or given a File, allow filling it from an Iterable, by creating this extension function:
fun <T: Any> File.fillWith(things: Iterable<T>, transform: (T)->String = {it.toString()}) {
this.bufferedWriter().use { out ->
things.map(transform).forEach { out.write(it); out.newLine() }
}
}
with usage of:
File("somefile.txt").fillWith(history.entries) { "${it.key}, ${it.value}" }
or use default toString() on each item:
File("somefile.txt").fillWith(history.entries)
which if you had the other toFile extension already, you could rewrite having one extension call the other:
fun <T: Any> File.fillWith(things: Iterable<T>, transform: (T)->String = {it.toString()}) {
things.toFile(this, transform)
}
It mostly looks ok to me. The only thing different I would do is use the "use" extension defined in ReadWrite to auto close the writer.
PrintWriter("file.txt").use {
for ((member, originalInput) in history) { // history: Map<Member, String>
it.append("$member, $originalInput\n")
}
}
At the very minimum, you could use:
FileWriter(filename).use { it.write(text) }
FileWriter is a convenience class for writing character files (provided by Java, and hence available in Kotlin). It extends Closeable, and hence can be used by Kotlin's ".use" extension method.
The .use extension method automatically closes the calling object once the block exits, thus providing an idiomatic way to close the file after it's written.
Some Kotlin magic allows to omit referencing the stream on each read or write call:
fun <T : Closeable, R> T.useWith(block: T.() -> R): R = use { with(it, block) }
File("a.in").bufferedReader().useWith {
File("a.out").printWriter().useWith {
val (a, b) = readLine()!!.split(' ').map(String::toInt)
println(a + b)
}
}
Scanner(File("b.in")).useWith {
PrintWriter("b.out").useWith {
val a = nextInt()
val b = nextInt()
println(a + b)
}
}
try{
val fileWriter = FileWriter("test.txt", true)
fileWriter.write(string+ "\n")
fileWriter.close()
} catch (exception: Exception){
println(exception.message)
}
Example as easy
val path = context!!.filesDir.absolutePath // => /data/user/0/com.example.test/files
File("$path/filename.txt").writeText("hello")
File(requireContext().filesDir, "TodayTaskListChange.txt").writeText("write your test here...")