3

I am parsing a Firebase DataSnapshot (json) object into a data class. Instead of the following, can I combine them and return if any one of them is null? Something like Swift's guard let ..., let ... else { return }

func parse(snapshot: DataSnapshot) {
    val type = snapshot.child("type").value as? String ?: return
    val unitType = UnitEnumType.values().firstOrNull { it.abbrv == type } ?: return
    val imageUrl = snapshot.child("image_url").value as? String ?: return
    ...
}
Ramesh R
  • 7,009
  • 4
  • 25
  • 38
Jack Guo
  • 3,959
  • 8
  • 39
  • 60
  • Possible duplicate of [Multiple variable let in Kotlin](https://stackoverflow.com/questions/35513636/multiple-variable-let-in-kotlin) – ElegyD Jul 12 '18 at 13:17
  • Is this real sample or just some pseudo-code? Because it does nothing (assigns local properties that are discarded when constructor ends). Personally I would `throw IllegalArgumentException()` instead of returning constuctor in the middle and leaving object in undefined state. That will let you catch it and You will know why object failed to be created. – Pawel Jul 12 '18 at 18:09
  • @Pawel, edited, doesn't really matter, I just want to know how to combine successive elvis operators. – Jack Guo Jul 12 '18 at 18:49

2 Answers2

1

You can write

val (type, unitType, imageUrl) = Triple(
    snapshot.child("type").value as? String ?: return,
    UnitEnumType.values().firstOrNull { it.abbrv == "a" } ?: return,
    snapshot.child("image_url").value as? String ?: return
)

However, you can't refer to type (the result of the first expression) in the second expression. This is an all-or-nothing assignment.

Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
1

I mean technically you could set up some crazy function like this:

inline fun <A, R> guardLet(pairA: Pair<A?, (A) -> Boolean>, block: (A) -> R): R? {
    val (a, aPredicate) = pairA
    if(a != null && aPredicate(a)) {
        return block(a)
    }
    return null
}

inline fun <A, B, R> guardLet(pairA: Pair<A?, (A) -> Boolean>, pairB: Pair<B?, (B) -> Boolean>, block: (A, B) -> R): R? {
    val (a, aPredicate) = pairA
    val (b, bPredicate) = pairB
    if(a != null && b != null && aPredicate(a) && bPredicate(b)) {
        return block(a, b)
    }
    return null
}

And call it as

guardLet(someProperty to { it != "blah" }, otherProperty to { it.something() }) { somePropertyByCondition, otherPropertyByCondition ->
    ...
} ?: return

Although in this particular case you could use this:

inline fun <R, A> ifNotNull(a: A?, block: (A) -> R): R? =
    if (a != null) {
        block(a)
    } else null

inline fun <R, A, B> ifNotNull(a: A?, b: B?, block: (A, B) -> R): R? =
    if (a != null && b != null) {
        block(a, b)
    } else null

inline fun <R, A, B, C> ifNotNull(a: A?, b: B?, c: C?, block: (A, B, C) -> R): R? =
    if (a != null && b != null && c != null) {
        block(a, b, c)
    } else null

inline fun <R, A, B, C, D> ifNotNull(a: A?, b: B?, c: C?, d: D?, block: (A, B, C, D) -> R): R? =
    if (a != null && b != null && c != null && d != null) {
        block(a, b, c, d)
    } else null

And just go with

ifNotNull(
    snapshot.child("type").value as? String,
    UnitEnumType.values().firstOrNull { it.abbrv == type },
    snapshot.child("image_url").value as? String) { type, unitType, imageUrl -> 
    ...
} ?: return

I dunno, I'm just throwing possibilities at you.

EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428