Alejandro Agüero
Alejandro Agüero

Reputation: 31

How to call specific named argument which is a result of another function? Kotlin

In order to make a call to an API I'm collecting two values from the user:

  1. The input text
  2. A default string which lives in a variable and changes when the user select a filter in a radio group. Eg: "name", "status", "gender"

Once a got these values I need to call the next function.

    fun getCharacter(name: String? = null, status: String? = null, species: String? = null, type: String? = null, gender: String? = null) {
            //code
}

Manually I would call viewModel.getCharacter(status = "alive") but I need a way to specify the named argument but itself because it depends what the user selected.

Any ideas?

edit: At the end this function calls a suspend function handled by retrofit

@GET("character/")
   suspend fun getCharacter(
           @Query ("name") name: String?,
           @Query("status") status: String?,
           @Query("species") species: String?,
           @Query("type") type: String?,
           @Query("gender") gender: String?,

   ): CharacterResponse

Upvotes: 0

Views: 256

Answers (3)

lukas.j
lukas.j

Reputation: 7163

Using reflection:

import kotlin.reflect.KParameter.Kind.INSTANCE
import kotlin.reflect.full.memberFunctions

fun callFunction(instance: Any, functionName: String, arguments: Map<String, String?>) {
  val function = instance::class.memberFunctions.first { it.name == functionName }
  function.callBy(function.parameters.associateWith { if (it.kind == INSTANCE) instance else arguments[it.name] })
}

class ViewModel {
  fun getCharacter(name: String? = null, status: String? = null, species: String? = null, type: String? = null, gender: String? = null) {
    println("name: $name")
    println("status: $status")
    println("species: $species")
    println("type: $type")
    println("gender: $gender")
  }
}

val viewModel = ViewModel()

callFunction(viewModel, "getCharacter", mapOf("species" to "animal", "type" to "mammal"))

Output:

name: null
status: null
species: animal
type: mammal
gender: null

Upvotes: 0

cactustictacs
cactustictacs

Reputation: 19534

Are you trying to call the function and use the default values, but provide one of them that's decided at runtime? As far as I'm aware you can't do that unfortunately - I've run into that limitation myself

Honestly for what you're doing, I wouldn't use default parameters - you basically have some type of filter, and some value for it, right? I'd just pass that into your function:

// I'm not entirely sure what string data you're passing but hopefully this
// makes enough sense that you can work out how to apply it to your thing
data class Filter(val type: FilterType, val value: String = type.defaultString)

enum class FilterType(val defaultString: String) {
    NAME("name"), STATUS("alive")
}

fun getCharacter(filter: Filter) {
   // do the thing
}

The benefit of this is because you're defining all your options in the enum, your radio button can use that enum internally too - you can put the display text as a property on the enum, and use that as a label in the UI, but you're actually working with and passing a FilterType that can be used directly, instead of a string that needs to be converted to a type or a property reference.

You could make getCharacter take a list of Filters if you want too, in case you ever want to implement multiple filters!

Upvotes: 1

Tenfour04
Tenfour04

Reputation: 93609

It would be very difficult to specify optional function parameters by name (using reflection).

Instead, I would use a @QueryMap for this GET request, so you can specify the param name as a function parameter:

@GET("character/")
suspend fun getCharacter(
    @QueryMap params: Map<String, String>
): CharacterResponse
suspend fun getCharacter(paramName: String, paramValue: String): CharacterResponse {
    // ...
    return service.getCharacter(mapOf(paramName to paramValue))
}
viewModel.getCharacter("status", "alive")

Upvotes: 1

Related Questions