How To Return Nested Variable? - android

I don't know how to RETURN variable from the following function.
Here is the code...
downloadData.setOnClickListener {
val handler = Handler(Looper.getMainLooper())
handler.post {
val fetchData =
FetchData("http://localhost/xampp/CRM/PHP/show_contacts_db.php")
if (fetchData.startFetch()) {
if (fetchData.onComplete()) {
val result = fetchData.data.toString()
Log.i("FetchData", result)
val companyName = result.substringAfter("Name: ").substringBefore(";")
showContactName.text = "${companyName}"
val companyNumber = result.substringAfter("Number: ").substringBefore(";")
showContactNumber.text = "${companyNumber}"
}
}
}
}
companyName and companyNumber needed to be returned so I can use it in other places.
When I Try to use Return companyNumber I have a message that "return" is not allowed here.

Generally with lambdas, you don't explicitly return a value - the lambda returns the value of the last expression. Using your code as an example (it won't actually work but we'll get to that!):
handler.post {
...
companyNumber
}
which is the same as how things like map calls take a transformation function
listOf(1, 2, 3).map { it * 2 }
that's just doubling each number, but the result is being implicitly returned and stored in the resulting list, right? And it lets you chain lambdas together, since each one evaluates to a value (which might be Unit if it "doesn't return a result")
If you want, you can explicitly use what's called a qualified return:
handler.post {
...
return#post companyNumber
}
where you're naming the function call you're returning to.
Kotlin docs: returning a value from a lambda expression
Also if you want to return two values, you can't do that - so you'll have to bundle them up in a single object. You could just return a Pair, or create a data class that's more readable:
return#post Pair(companyName, companyNumber)
//or
data class CompanyDeets(val name: String, val number: String)
...
return#post CompanyDeets(companyName, companyNumber)
But aside from how you do it in general, why do you want to return anything here? Handler#post takes a Runnable which returns nothing (void in Java), and View.OnClickListener#onClick doesn't return anything either.
Neither of them would do anything with a value you returned - and if you explicitly return a value, that means your lambda's signature doesn't match (right now it's implicitly returning Unit to match what's expected by the caller), and you'll get an error
What you probably want to do instead, is create a function inside your class (Activity or whatever) that uses your data, something like fun doSomethingWith(companyName: String, companyNumber: String) and call that inside your lambda. That's way you're executing code in reaction to a click

just declare var Company Name in global, or create a function with that params
var companyName: String? = null
handler.post {
...
companyName = result.substringAfter("Name: ").substringBefore(";")
}
OR
handler.post {
...
save(result.substringAfter("Name: ").substringBefore(";"))
}
fun save(companyName: String){ ... }

Related

How to use Generics in Kotlin extension function

I had 2 functions that does basically the same thing, so I thought to create an extension function.
The problem is that I need to extract the data of each class in order to get the correct result, I thought of creating a Generic function, and inside this function decide which class member to access inside when statement.
But when I try to call T inside the when statement, I'm getting Type parameter 'T' is not an expression
What am I doing wrong?
My function:
fun <T: Any> List<T>.extractWithSepreation(errorString: String): String {
var errorString = "There is no available $errorString"
if (this.isEmpty()) {
return errorString
}
errorString = ""
this.forEachIndexed { index, item ->
when(T)
}
}
The error message pretty much says it all. T is not an expression, so it cannot be used inside when(...). T just refers to the class of the item of the List.
Didn't you mean using something like:
when(item) {
is ClassA -> doSomething()
is ClassB -> doSomethingElse()
}
?

Kotlin: How can I reduce child arrays into a single array?

I have a pice of code in Swift that reduces a list of TVSchedule objects into an array of TVMatch pobjects. Each TVSchedule, has a property called events, that is a list of TVMatches.
The code in swift is the following:
var matches: [TVMatch] {
let slots = timeSlots.reduce(into: [TVMatch]()) { (result, schedule) in
result.append(contentsOf: schedule.events)
}
return slots
}
I'm trying to do the same reduce in Kotlin and the code I have is the following:
val matches: ArrayList<TVMatch>
get() {
val slots = timeSlots.fold(arrayListOf<TVMatch>()) { result, schedule ->
result.addAll(schedule.events)
}
return slots
}
However, the Kotlin code gives me a type error, and does not compile. What is the problem here?
addAll returns a boolean, but the return value of the fold-operation should be of same type as the given initial object (in this case ArrayList).
You can solve that one easily by just adding result after your addAll-statement, e.g.:
result.addAll(schedule.events)
result // this is now the actual return value of the fold-operation
Alternatively just use apply or similar instead:
result.apply {
addAll(schedule.events)
} // result is the return value then
Note that you can actually simplify altogether using flatMap to just (side-note: if you use this approach the matches are evaluated only once of course, but flatMap is the star here anyway ;-))):
val matches = timeSlots.flatMap { it.events } // this is a new list! (note, if you do several mappings in a row, you may want to use timeSlots.asSequence().flatMap { }.map { }.toList() / or .toMutableList() instead
Alternatively if you really require the matches to be of type ArrayList, use flatMapTo instead:
val matches = timeSlots.flatMapTo(ArrayList()) { it.events }
You can of course keep the get() if you must, or just move the getting of the matches to its own function, e.g.:
fun getMatches() = timeSlots.flatMapTo(ArrayList()) { it.events }
Am I crazy, or can't you just replace the code with
val matches: List<TVMatch>
get() = timeSlots.flatMap { schedule -> schedule.events }
?

Why can I invoke a fun without passing parameter name in Kotlin?

There are 4 parameters with default value in function joinToString, in my mind, I should pass parameter value by order when I omit parameter name.
So I think the Code println(letters.joinToString( transform={ it.toLowerCase() } ) ) is right.
But in fact the Code println(letters.joinToString { it.toLowerCase() } ) is right too, why?
fun <T> Collection<T>.joinToString(
separator: String = ", ",
prefix: String = "",
postfix: String = "",
transform: (T) -> String = { it.toString() }
): String {
val result = StringBuilder(prefix)
for ((index, element) in this.withIndex()) {
if (index > 0) result.append(separator)
result.append(transform(element))
}
result.append(postfix)
return result.toString()
}
fun main(args: Array<String>) {
val letters = listOf("Alpha", "Beta")
println(letters.joinToString { it.toLowerCase() } ) //It's Ok
println(letters.joinToString( transform={ it.toLowerCase() } ) ) //It's OK
}
Because you're using a different syntax.
If the last param of a method is a method reference then you can omit the parenthesis and just pass in the function with the { brackets.
it in this case becomes T that you were passing into the function
println(letters.joinToString { it.toLowerCase() } )
Below is what you thought you were entering. This wouldn't compile and would require the named argument or for the params to be in the right order. You would also have to change the syntax from using it to using the regular functional syntax
println(letters.joinToString(it.toLowerCase()))
In addition to #Dan's answer, you don't need to provide a named argument, but if you do so then you're forced to use the named argument for all the following arguments (from the documentation: "all the positional arguments should be placed before the first named one"). In your case the only named argument you're providing is the last one, and all other arguments have default values so you're not forced to provide them, as long as the default value is fine for you.

How can I tell kotlin that a function doesn't return null if the parameter is not null?

I want to write a convenience extension to extract values from a Map while parsing them at the same time. If the parsing fails, the function should return a default value. This all works fine, but I want to tell the Kotlin compiler that when the default value is not null, the result won't be null either. I could to this in Java through the #Contract annotation, but it seems to not work in Kotlin. Can this be done? Do contracts not work for extension functions? Here is the kotlin attempt:
import org.jetbrains.annotations.Contract
private const val TAG = "ParseExtensions"
#Contract("_, !null -> !null")
fun Map<String, String>.optLong(key: String, default: Long?): Long? {
val value = get(key)
value ?: return default
return try {
java.lang.Long.valueOf(value)
} catch (e: NumberFormatException) {
Log.e(TAG, e)
Log.d(TAG, "Couldn't convert $value to long for key $key")
default
}
}
fun test() {
val a = HashMap<String, String>()
val something: Long = a.optLong("somekey", 1)
}
In the above code, the IDE will highlight an error in the assignment to something despite optLong being called with a non null default value of 1. For comparison, here is similar code which tests nullability through annotations and contracts in Java:
public class StackoverflowQuestion
{
#Contract("_, !null -> !null")
static #Nullable Long getLong(#NonNull String key, #Nullable Long def)
{
// Just for testing, no real code here.
return 0L;
}
static void testNull(#NonNull Long value) {
}
static void test()
{
final Long something = getLong("somekey", 1L);
testNull(something);
}
}
The above code doesn't show any error. Only when the #Contract annotation is removed will the IDE warn about the call to testNull() with a potentially null value.
You can do this by making the function generic.
fun <T: Long?> Map<String, String>.optLong(key: String, default: T): T
{
// do something.
return default
}
Which can be used like this:
fun main(args: Array<String>) {
val nullable: Long? = 0L
val notNullable: Long = 0L
someMap.optLong(nullable) // Returns type `Long?`
someMap.optLong(notNullable) // Returns type `Long`
}
This works because Long? is a supertype of Long. The type will normally be inferred in order to return a nullable or non-nullable type based on the parameters.
This will "tell the Kotlin compiler that when the default value is not null, the result won't be null either."
It's a pity that you can't do this, in Kotlin 1.2 or below.
However, Kotlin is working on contract dsl which is unannounced yet, which is not available ATM (since they're declared internal in the stdlib) but you can use some hacks to use them in your codes (by compiling a stdlib yourself, make all of them public).
You can see them in the stdlib ATM:
#kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
Maybe there will be something like
contract {
when(null != default) implies (returnValue != null)
}
in the future that can solve your problem.
Workaround
Personally I'd recommend you to replace default's type with a NotNull Long and call it like
val nullableLong = blabla
val result = nullableLong?.let { oraora.optLong(mudamuda, it) }
result is Long? and it's null only when nullableLong is null.
#Contract does work with Kotlin extension functions, it just needs to be changed to work with the compiled bytecode. An extension function is compiled in bytecode as a static method:
fun ClassA?.someMethod(arg: ClassB): ClassC? {
return this?.let { arg.someMethod(it)!! }
}
Java will see this as nullable, so it will require you to null-check the result. But the real contract is: "if ClassA is null, returns null; otherwise if ClassA is not null, returns non-null". But IntelliJ does not understand that (at least from a Java source).
When that method gets compiled to Java bytecode it's actually:
#Nullable static ClassC someMethod(#Nullable ClassA argA, #NonNull ClassB argB) {}
So you need to account for the synthetic first argument, when writing your #Contract:
#Contract("null, _ -> null; !null, _ -> !null")
fun ClassA?.someMethod(arg: ClassB): ClassC? {...}
After that, IntelliJ will understand the contract of the static method, and will understand that the return value's nullability is dependent on the first argument's nullness.
So the short version, as it pertains to this question is, you just need to add an extra _ argument to the Contract, to represent the "this" argument:
#Contract("_, _, !null -> !null") // args are: ($this: Map, key: String, default: Long?)
fun Map<String, String>.optLong(key: String, default: Long?): Long? {

Kotlin data classes and nullable types

I'm new to Kotlin and I don't know why compiler complains about this code:
data class Test(var data : String = "data")
fun test(){
var test: Test? = Test("")
var size = test?.data.length
}
Compiler complains with test?.data.length, it says that I should do: test?.data?.length. But data variable is String, not String?, so I don't understand why I have to put the ? when I want to check the length.
The expression test?.data.length is equivalent to (test?.data).length, and the test?.data part is nullable: it is either test.data or null. Therefore it is not null-safe to get its length, but instead you should use the safe call operator again: test?.data?.length.
The nullability is propagated through the whole calls chain: you have to write these chains as a?.b?.c?.d?.e (which is, again, equivalent to (((a?.b)?.c)?.d)?.e), because, if one of the left parts is null, the rest of the calls cannot be performed as if the value is not-null.
If you don't want to use safe call before each non-nullable component of the call chain, you can get the result of the first safe call into a new variable with the standard extension functions run or let:
// `this` is non-nullable `Test` inside lambda
val size = test?.run { data.length }
// or: `it` is non-nullable `Test` inside lambda
val size = test?.let { it.data.length }
Note that size is still nullable Int? here.

Categories

Resources