Pass values as parameters if not null or empty Kotlin

Viewed 5330

I have text values I retrieve from text inputs. I want to allow the user to not fill in these inputs. But if the user has not filled one or more values I want to display default values for these inputs.

I have a data class that looks something like this:

@Parcelize
data class Profile(
    val firstName: String = "",
    val lastName: String = "",
    val description: String = "",
    val imageUri: String = ""
) : Parcelable

On click I call a method from my ViewModel class and pass it the current input values which is then persisted using a Repository class:

viewModel.createProfile(
   etFirstName.text.toString(),
   etLastName.text.toString(),
   etProfileDescription.text.toString(),
   profileImageUri.toString()
)


// The createProfile function itself
fun createProfile(
    firstName: String = "John",
    lastName: String = "Doe",
    description: String = "Default Description",
    imageUri: String = ""
) {
    val profile = Profile(firstName, lastName, description, imageUri)
    // Persist data
}

In a another fragment I set some UI data using this persisted data like this:

private fun observeProfile() {
    viewModel.getProfile()

    viewModel.profile.observe(viewLifecycleOwner, Observer {
        val profile = it
        tvName.text = getString(R.string.profile_name, profile.firstName, profile.lastName)
        tvDescription.text = profile.description
        ivProfileImage.setImageURI(Uri.parse(profile.imageUri))
    })
}

So currently createProfile expects 4 arguments. I'm able to pass less because I have optional parameters, but how can I conditionally pass arguments to createProfile based on if the value is non null or empty. I can of course create checks for each value, but what is the best way to approach this?

Update

I don't think I was clear enough in my original question, because I don't only pass values from text inputs to createProfile. profileImageUri is a class variable of type Uri? and is initially set to null. The user can select an image and this variable is updated with the image data. The reason I'm passing and storing the image data as a String is because all the profile data also gets persisted to Firestore so Strings are easier to work with.

3 Answers

Compared to your own answer, I'd create a helper function

fun CharSequence?.ifNullOrEmpty(default: String) = if (this.isNullOrEmpty()) default else this.toString()

And use it as

viewModel.createProfile(
    etFirstName.text.ifNullOrEmpty("John"),
    etLastName.text.ifNullOrEmpty("Doe"),
    etProfileDescription.text.ifNullOrEmpty("Default Description"),
    profileImageUri.ifNullOrEmpty("Default Uri")
)

EDIT: given the update, I'd consider

fun Any?.ifNullOrEmpty(default: String) = 
    if (this == null || (this is CharSequence && this.isEmpty())) 
        default 
    else 
        this.toString()

I have found a workaround.

Turns out it's possible to pass if-else statements as parameters, because if statements are expressions in Kotlin:

viewModel.createProfile(
    if (!etFirstName.text.isNullOrEmpty()) etFirstName.text.toString() else "John",
    if (!etLastName.text.isNullOrEmpty()) etLastName.text.toString() else "Doe",
    if (!etProfileDescription.text.isNullOrEmpty()) etProfileDescription.text.toString() else "Default Description",
    if (profileImageUri != null) profileImageUri.toString() else ""
)

Using this approach I also don't have to set default values for my data class variables and for my createProfile function parameters.

I additionally added a check in my observeProfile function so if profileImageUri is null it won't try to set the image:

// ...
if (profile.imageUri.isNotEmpty()) {
    ivProfileImage.setImageURI(Uri.parse(profile.imageUri))
}
// ...

My initial idea doesn't seem to be possible using a data class. It does seem to be possible using a regular class and varargsbut it has problems:

@Parcelize
class Profile(
    vararg val params: String
) : Parcelable

...

val params = arrayOfValues.filter{ !it.isNullOrBlank() } // filter out all unwanted data
val profile = Profile(*params) // pass every param separately using spread operator

Main problem here is that the parameters themselves are obfuscated. I can still get the reference to individual parameters using an index and do stuff with them, but it doesn't work as nicely.

I think what you want to use is the Elvis Operator in Kotlin: ?:.

val test = exampleExpression ?: "alternative value"

If the expression to the left of ?: is not null, the elvis operator returns it, otherwise it returns the expression to the right. Note that the right-hand side expression is evaluated only if the left-hand side is null.

viewModel.createProfile(
   etFirstName.text.toString() ?: "John",
   etLastName.text.toString() ?: "Doe",
   etProfileDescription.text.toString() ?: "Default Description",
   profileImageUri.toString() ?: "Default Uri"
)
Related