14

New at Kotlin here and trying to learn the best way to use the higher order functions and passing lambdas. I've created this method to call an API and return an object created from a string OR return a failure if something went wrong.

    fun getDeviceStatus(onSuccess: (Device) -> Unit, onFailure: ((String) -> Unit)? = null) {

        FuelClient.get(DEVICE_URL,
                success = { responseString ->

                    val adapter = MoshiUtil.moshi.adapter(Device::class.java)

                    val deivce= adapter.fromJson(responseString)!!

                    onSuccess(device)
                },
                failure = { onFailure?.invoke(it.message!!)})


    }

I can use this function fine like so:

DeviceService.getDeviceStatus(
                { w ->
                    print("device")
                },
                { e -> print(e) })

But it bothers me a bit that I can't see the name of the functions to see what each function does. I"m wondering if there is a cleaner/better way to do this, like

DeviceService.getDeviceStatus(){
            onSuccess{print("device")}
            onFailure{print("error")}
        }

or maybe

DeviceService.getDeviceStatus()
                .onSuccess{print("device")}
                .onFailure{print("error")}

But those gives errors. Any thoughts on how to best handle the onSuccess/onFailure use case that is very common? Thx

7200rpm
  • 440
  • 3
  • 14
  • You can do as suggested in Suhaib's answer, or also use something like Promises from Kovenant library which specifically has nice ways of dealing with success/failure. – Jayson Minard May 23 '18 at 23:36

2 Answers2

19

You can attach a name to each variable in kotlin. Change your code like this

DeviceService.getDeviceStatus(
                onSuccess = { w ->
                    print("device")
                },
                onFailure = { e -> print(e) })
Suhaib Roomy
  • 2,137
  • 1
  • 13
  • 19
  • Thanks. It seems there are multiple ways to handle this and I'm not sure which one I like best, but this is a good start. – 7200rpm May 25 '18 at 02:50
  • If anyone is looking for a better explanation on different ways to handle this, I found this article particularly helpful: https://antonioleiva.com/listeners-several-functions-kotlin/ – 7200rpm May 26 '18 at 00:14
  • The problem with named arguments is that it doesn't force the developer to use them, making things a big ambigous – 7200rpm May 26 '18 at 00:15
2

For this specific case, when the second lambda is optional, infix functions work very well:

sealed class DeviceStatusResult {
    abstract infix fun onFailure(handler: (String) -> Unit)
}

class DeviceStatusSuccess(val device: Device) : DeviceStatusResult() {
    override fun onFailure(handler: (String) -> Unit) = Unit
}

class DeviceStatusFailure(val errorMessage: String) : DeviceStatusResult() {
    override fun onFailure(handler: (String) -> Unit) = handler(errorMessage)
}

fun getDeviceStatus(onSuccess: (Device) -> Unit): DeviceStatusResult {
    // get device status
    // if (success)
    val device = Device()
    onSuccess(device)
    return DeviceStatusSuccess(device)
    // else
    // return DeviceStatusFailure(message)
}

Then it can used like

getDeviceStatus { device ->
    println(device)
} onFailure { errorMessage ->
    System.err.println(errorMessage)
}

Maybe onFailure should be called orFail or something like that.

It is good when the second argument is optional, but not so much otherwise because it doesn't force the user to actually supply a failure handler. And I don't think it's a good idea because it will be too easy to accidentally omit a failure handler. It's much better to force the user to provide one, even if it happens to be an empty one. Therefore, it is better to use named arguments for this case, even though nothing forces to actually name them.

Sergei Tachenov
  • 22,431
  • 8
  • 51
  • 68