1
votes

I have this sealed class PictureEvent:

sealed class PictureEvent {

    data class PictureCreated(val pictureId: String, val url: String) : PictureEvent()

    //more classes extending PictureEvent

}

Now, from a list of PictureEvents, I want to get the first PictureCreated:

fun doSomething(events: List<PictureEvent>) {

  val creationEvent = events.first { isCreationEvent(it) } as PictureEvent.PictureCreated

  //do stuff with the creationEvent

}

private fun isCreationEvent(event: PictureEvent) : Boolean {
  return event is PictureEvent.PictureCreated
}

It works fine. As you see, I'm casting the event to PictureCreated (using as keyword), since first method, returns a PictureEvent. I wonder if it's possible to avoid this casting by using Kotlin contracts.

I have tried this:

private fun isCreationEvent(event: PictureEvent) : Boolean {
  contract {
    returns(true) implies (event is PictureEvent.PictureCreated)
  }
  return event is PictureEvent.PictureCreated
}

But it doesn't work; first method keep returning a PictureEvent, instead of PictureCreated. Is it possible to do this currently?

1
The code should work as expected, can you show how did you called the isCreationEvent()?Animesh Sahu
You can see it in the question. events.first { isCreationEvent(it) }Héctor

1 Answers

4
votes

The contract works fine, however if you take a look at the first method signature, you should be able to understand what is happening and why the found object isn't autocast:

public inline fun <T> Iterable<T>.first(predicate: (T) -> Boolean): T

The return type of the first method is just the same as that defined for all elements in the Iterable instance, PictureEvent in your case, and no autocasting inside the predicate can unfortunately change that.


Instead of contracts, for example, you can filter your list by the desired class type first, and then take the first element:

val creationEvent = events
    .filterIsInstance(PictureEvent.PictureCreated::class.java)
    .first()

or create your own extension similar to first:

inline fun <reified R> Iterable<*>.firstOfInstance(): R {
    val first = first { it is R }
    return first as R
}

// or wrapping filterIsInstance
inline fun <reified R> Iterable<*>.firstOfInstance(): R {
    return filterIsInstance(R::class.java).first()
}

val creationEvent = events.firstOfInstance<PictureEvent.PictureCreated>()