Kotlin way in conditional flows - kotlin

What would be the best kotlin way to have the following logic?
if (it.records.isNotEmpty()) {
if (it.records[0].fields.isNotEmpty()) {
if (it.records[0].fields["lastModifiedDate"] != null) {
RECORD_DATA_LAST_MODIFIED_DATE_FORMAT.parse(
it.records[0].fields["lastModifiedDate"])
} else {
Date(0)
}
} else {
Date(0)
}
} else {
Date(0)
}

Since you didn't provide all the code necessary to run your code I decided to create 2 classes and a function
data class Musician(
val records: List<Record>
)
data class Record(
val fields: Map<String, String>
)
fun test(mus: Musician): Date { }
Only by using the elvis operator and some common syntax you could get something like this:
fun test(mus: Musician): Date {
val sdf = SimpleDateFormat("dd/MM/yyyy")
return if(mus.records.isNotEmpty() &&
mus.records[0].fields.isNotEmpty())
sdf.parse(mus.records[0].fields["lastModifiedDate"]) ?: Date(0)
else
Date(0)
}
And if you want to use even more Kotlin fun stuff you could create somethig like:
fun test(mus: Musician): Date {
val sdf = SimpleDateFormat("dd/MM/yyyy")
return mus.records.firstOrNull()?.fields?.get("lasModifiedDate")?.let {
sdf.parse(it)
} ?: Date(0)
}
This is probably not the best way to handle this situation, but these are some options of what you can do with Kotlin

Your question is missing some detail, but probably something like this:
val date = it.records[0]?.fields["lastModifiedDate"]?.let { RECORD_DATA_LAST_MODIFIED_DATE.parse(it)} ?: Date(0)

Related

Kotlin idiomatic way for extracting logic that gets executed when a value is null

I have something like this in Kotlin, repeated in several places (and please bear in mind that I'm relatively new to the language and I'm still figuring out what's the best/ more idiomatic way to do things):
class SomeClass {
fun someMethod(c: Context) {
val id: String? = c.someValue?.someId
if(id == null) {
return someResult("some message")
}
doSomething(id)
}
}
I would like to find an idiomatic way of extracting
if(id == null) {
return someResult("some message")
}
and still be able to use the value of id without having to help the compiler determining its value is not null. How can I do this idiomatically in Kotlin?
You can use kotlin elvis operator it works the same as if(id == null) {...} :
class SomeClass {
fun someMethod(c: Context) {
val id: String = c.someValue?.someId ?: return someResult("some message")
doSomething(id)
}
}

Kotlin functional find single element

I am relatively new to Kotlin and I try to overcome a special case.
I am filtering a books store and want to verify that the length of the obtained list is exactly one unit shorter than the original one. Further I need to verify that the discarded element is under a specific state. Here is my example:
fun BookStoreVerified(bookStore: BookStore): Boolean {
val specialChapter = bookStore.stores
.flatMap { it.books }
.flatMap { it.chapters }.filter { it != null && it.state == Chapter.SPECIAL }
val total = bookStore.stores
.flatMap { it.books }
.flatMap { it.chapters }
.filterNotNull()
val finalChapters = book.stores
.flatMap { it.books }
.flatMap { it.chapters }
.filter { it != null && it.state.isCorrect }
return (finalChapters.size + specialChapterFigure.size == total.size) && (specialChapter.size == 1)
}
My question is if there is a smarter way to compute the above operation. I would like to know if ander a scope like filter, map can we make reference to the previous object? ( get the length of the original list for instance ?)
You have Books where each Book contains a list of Chapters. You want to partition chapters from all the books according to some criteria.
With this in mind the partition function can be useful:
data class Chapter(val state: String)
data class Book(val chapters: List<Chapter>? = null)
fun main() {
val books = listOf(
Book(),
Book(chapters = listOf(Chapter("a"), Chapter("SPECIAL"))),
Book(chapters = listOf(Chapter("c"), Chapter("d")))
)
val (specialChs, regularChs) = books
.flatMap { it.chapters ?: emptyList() }
.partition { it.state == "SPECIAL" }
println(specialChs) // [Chapter(state=SPECIAL)]
println(regularChs) // [Chapter(state=a), Chapter(state=c), Chapter(state=d)]
}
Now that you have specialChs and regularChs, you can check whatever invariants you want.
For example:
check(specialChs.size == 1 && specialChs.first().state ==
"SPECIAL")
Edit: It is possible to abstract away the existence of null chapters inside a Book:
data class Book(val chapters: List<Chapter>? = null) {
val safeChapters: List<Chapter>
get() = chapters ?: emptyList()
}
then in your code you can flatMap { it.safeChapters } instead of .flatMap { it.chapters ?: emptyList() }

Kotlin, how can I make my code shorter in this spaghetti code?

I'm working on readlines now and can I make this few if's shorter? I'm making a validation to what user is sending to me. The filed cant be empty or null. I have 3 important things that user has to write in field and every three times I have to check the same... .
fun readlinesToAddEntryAndValidation(): List<String> {
println(ENTER_DESCRIPTION_ID_TEKST)
val entryId: String? = readLine()
if (!entryId.isNullOrEmpty()) {
println(ENTER_DESCRIPTION_NAME_TEKST)
val name: String? = readLine()
if (!name.isNullOrEmpty()) {
println(ENTER_DESCRIPTION_TEKST_TEKST)
val tekst: String? = readLine()
if (!tekst.isNullOrEmpty()) {
return listOf(entryId, name, tekst)
} else {
println(EMPTY_READLINE_ERROR)
return readlinesToAddEntryAndValidation()
}
} else {
println(EMPTY_READLINE_ERROR)
return readlinesToAddEntryAndValidation()
}
} else {
println(EMPTY_READLINE_ERROR)
return readlinesToAddEntryAndValidation()
}
}
Try to avoid cognitive complexity one of the things is avoid nesting. Also when an if always returns something. An else statement is not needed
fun readlinesToAddEntryAndValidation(): List<String> {
println(ENTER_DESCRIPTION_ID_TEKST)
val entryId: String? = readLine()
if (entryId.isNullOrEmpty()) {
println(EMPTY_READLINE_ERROR)
return readlinesToAddEntryAndValidation()
}
println(ENTER_DESCRIPTION_NAME_TEKST)
val name: String? = readLine()
if (!name.isNullOrEmpty()) {
println(ENTER_DESCRIPTION_TEKST_TEKST)
val tekst: String? = readLine()
if (!tekst.isNullOrEmpty()) {
return listOf(entryId, name, tekst)
}
}
println(EMPTY_READLINE_ERROR)
return readlinesToAddEntryAndValidation()
}
You could do something like this:
fun readlinesToAddEntryAndValidation() : List<String> {
fun read(message: String): String? {
println(message)
val line = readLine()
return if (line.isNullOrEmpty()) null else line
}
read(ENTER_DESCRIPTION_ID_TEKST)?.let { entryId ->
read(ENTER_DESCRIPTION_NAME_TEKST)?.let { name ->
read(ENTER_DESCRIPTION_TEKST_TEKST)?.let { tekst ->
return listOf(entryId, name, tekst)
}
}
}
println(EMPTY_READLINE_ERROR)
return readlinesToAddEntryAndValidation()
}
I wouldn't normally recommend nesting too much, but I feel like that's fairly readable with only three parameters, and the null checking means it short-circuits as soon as you run into a problem.
Making user to reenter all previous (independent!) values after his mistake in the middle of the input is a bad UI.
If user failed to correctly input some entry, you need to ask him to reenter only this single item (until he eventually do it right):
fun read(inputMessage: String, errorMessage: String = EMPTY_READLINE_ERROR): String {
println(inputMessage)
var line: String? = readLine()
while (line.isNullOrEmpty()) {
println(errorMessage)
println(inputMessage)
line = readLine()
}
return line
}
With this auxilary function, whole program become a single-liner:
fun readlinesToAddEntryAndValidation() =
listOf(
ENTER_DESCRIPTION_ID_TEKST,
ENTER_DESCRIPTION_NAME_TEKST,
ENTER_DESCRIPTION_TEKST_TEKST
).map { read(it) }

Transform a list, filtering out the items that cause an exception

How to convert this array of String:
"2018-05-08T23:22:49Z"
"n/a"
"2018-05-07T16:37:00Z"
to an array of Date using Higher-Order Functions such as map, flatMap or reduce?
I do know that it's possible to do that using forEach, but I'm interested to involve Kotlin Higher-Order Functions:
val stringArray
= mutableListOf("2018-05-08T23:22:49Z", "n/a", "2018-05-07T16:37:00Z")
val dateArray = mutableListOf<Date>()
stringArray.forEach {
try {
val date = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)
.parse(it)
dateArray.add(date)
} catch (e: ParseException) {
//* Just prevents app from crash */
}
}
Using mapNotNull
val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)
val dates = listOf("2018-05-08T23:22:49Z", "n/a", "2018-05-07T16:37:00Z")
.mapNotNull {
try {
format.parse(it)
} catch (e: ParseException) {
null
}
}
println(dates)
This avoids creating a list for each item in the list, it maps the bad dates to null, and mapNotNull removes the nulls from the list.
Using an extension function
You could also extract the tryOrRemove to an extension function, making the code look like this:
val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)
fun <T, U: Any> Iterable<T>.tryOrRemove(block:(T)->U): List<U> {
return mapNotNull {
try {
block(it)
} catch (ex: Throwable) {
null
}
}
}
val dates = listOf("2018-05-08T23:22:49Z", "n/a", "2018-05-07T16:37:00Z")
.tryOrRemove(format::parse)
println(dates)
Using filter
I have written it based on the only bad dates being n/a, which simplifies it.
val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)
val dates = listOf("2018-05-08T23:22:49Z", "n/a", "2018-05-07T16:37:00Z")
.filter { it != "n/a" }
.map(format::parse)
println(dates)
You're looking for a transformation that can output zero or one element per input element. This is flatMap. The result of a flatmapping function must be an Iterable, so:
val dateArray = stringArray.flatMap {
try {
listOf(SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).parse(it))
} catch (e: ParseException) {
emptyList<Date>()
}
}
Adding the following based on #pwolaq's input:
It's highly recommended to extract the SimpleDateFormat instance because it has heavyweight initialization. Further, a solution with mapNotNull is cleaner than flatMap, I wasn't aware of it. This becomes especially convenient if you add a function that I feel is missing from the Kotlin standard library:
inline fun <T> runOrNull(block: () -> T) = try {
block()
} catch (t: Throwable) {
null
}
With this in your toolbox you can say:
val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)
val dateArray: List<Date> = stringArray.mapNotNull {
runOrNull { formatter.parse(it) }
}

Is there a simple code for fun addDetail in Kotlin?

The following code can work well, but the code of fun addDetail(...) is too complex, is there a simple way to do that ? Thanks!
BTW, in the fun addDetail(...), aMListDetail maybe null, and aMListDetail?.innerListDetail maybe null.
data class MDetail (
val _id: Long
)
class DetailsHandler(mContext: Context = UIApp.instance) {
data class MListDetail(val innerListDetail: MutableList<MDetail>)
private var aMListDetail: MListDetail?
var mJson: String by PreferenceTool(mContext,"mySavedJson", "")
init {
aMListDetail= Gson().fromJson(mJson,MListDetail::class.java)
}
fun addDetail(aMDetail:MDetail){
if (aMListDetail==null){
aMListDetail=MListDetail(mutableListOf(aMDetail))
}else{
if (aMListDetail?.innerListDetail==null){
aMListDetail=MListDetail(mutableListOf(aMDetail))
}else {
aMListDetail?.innerListDetail?.add(aMDetail)
}
}
mJson = Gson().toJson(aMListDetail)
}
}
fun addDetail(aMDetail: MDetail) {
if (aMListDetail?.innerListDetail == null) {
aMListDetail = MListDetail(mutableListOf(aMDetail))
} else {
aMListDetail.innerListDetail.add(aMDetail)
}
mJson = Gson().toJson(aMListDetail)
}
Alternative:
fun addDetail(aMDetail: MDetail) {
if (aMListDetail?.innerListDetail == null) {
aMListDetail = MListDetail(mutableListOf())
}
aMListDetail.innerListDetail.add(aMDetail)
mJson = Gson().toJson(aMListDetail)
}
You don't need null-safe ?. operators in your add() call, since at that point you've already checked that aMListDetail != null and innerListDetail != null.
BTW, in the fun addDetail(...), aMListDetail maybe null,
Why not fix the problem at the source? You initialize it in the constructor, then tell Kotlin it could be set to null, but actually you never do this!
If you remove the unused nullability, the code simplifies to:
class DetailsHandler(mContext: Context = UIApp.instance) {
data class MListDetail(val innerListDetail: MutableList<MDetail>)
var mJson: String by PreferenceTool(mContext,"mySavedJson", "")
// can even be val
private var aMListDetail: MListDetail
init {
aMListDetail= Gson().fromJson(mJson,MListDetail::class.java)
}
fun addDetail(aMDetail:MDetail){
aMListDetail.innerListDetail.add(aMDetail)
mJson = Gson().toJson(aMListDetail)
}
}
If your real code doesn't initialize it at the beginning, consider by lazy or by notNull.
and aMListDetail?.innerListDetail maybe null.
Only is aMListDetail is null, which you should avoid as above.
Finally, if you really need aMListDetail to be null sometimes, you can write
aMListDetail?.let {
it.innerListDetail.add(aMDetail)
}
(which does nothing if aMListDetail is null)
fun addDetail(aMDetail:MDetail){
if (aMListDetail?.innerListDetail==null){
aMListDetail=MListDetail(mutableListOf(aMDetail))
}else {
aMListDetail?.innerListDetail?.add(aMDetail)
}
mJson = Gson().toJson(aMListDetail)
}
8-)