kiwicom/navigation-compose-typed

ResultDestination with sealed interface as result acting unpredictably

Closed this issue · 5 comments

I have the following destination/result setup:

  @Serializable
  data class AddItem(
    val listUrlLocator: ListUrlLocator,
  ) : ListDestinations, ResultDestination<AddItem.Created> {
    @Serializable
    data object Created
  }

  @Serializable
  data class EditItem(
    val listUrlLocator: ListUrlLocator,
    val itemUrlLocator: ItemUrlLocator,
  ) : ListDestinations, ResultDestination<EditItem.Result> {

    sealed interface Result {

      @Serializable
      data class Updated(
        val itemUrlLocator: ItemUrlLocator,
      ) : Result

      @Serializable
      data object Deleted : Result

      @Serializable
      data class Archived(val itemUrlLocator: ItemUrlLocator) : Result
    }
  }

and the effect side:

      ComposableResultEffect<ListDestinations.AddItem.Created>(
        navController = navController,
        block = {
          dispatch(ListDetailEvents.ItemAdded)
        },
      )

      ComposableResultEffect<ListDestinations.EditItem.Result>(
        navController = navController,
        block = {
          Timber.d("$it")
          when (it) {
            is ListDestinations.EditItem.Result.Updated -> {
              dispatch(ListDetailEvents.ItemUpdated(it.itemUrlLocator))
            }
            is ListDestinations.EditItem.Result.Deleted -> {
              dispatch(ListDetailEvents.ItemDeleted)
            }
            is ListDestinations.EditItem.Result.Archived -> {
              dispatch(ListDetailEvents.ItemArchived(it.itemUrlLocator))
            }
          }
        },
      )

Graph setup:

  addItem(
    onItemCreated = { listUrlLocator ->
      navController.setResult(ListDestinations.AddItem.Created)
      navController.popBackStack()
    },
  )

  editItem(
    onItemEdited = {
      navController.setResult(ListDestinations.EditItem.Result.Updated(it))
      navController.popBackStack()
    },
    onItemDeleted = {
      navController.setResult(ListDestinations.EditItem.Result.Deleted)
      navController.popBackStack()
    },
    onItemArchived = { listUrlLocator: ListUrlLocator, itemUrlLocator: ItemUrlLocator ->
      navController.setResult(ListDestinations.EditItem.Result.Archived(itemUrlLocator))
      navController.popBackStack()
    },
  )

The effect for ListDestinations.AddItem.Created fires without issue every time. The effect for ListDestinations.EditItem.Result sometimes fires, often times not. Unfortunately, attaching the debugger seems to make the effect consistently fire 😅. Not sure whats happening here.

Also just to confirm: I switched to using a non-sealed interface and just testing with the Updated result and its working as intended.

Workaround for now seems to be:

  @Serializable
  data class EditItem(
    val listUrlLocator: ListUrlLocator,
    val itemUrlLocator: ItemUrlLocator,
  ) : ListDestinations, ResultDestination<EditItem.Result> {

    @Serializable
    data class Result(val operation: Operation) {
      @Serializable
      sealed class Operation {
      
        @Serializable
        data class Updated(val itemUrlLocator: ItemUrlLocator) : Operation()

        @Serializable
        data object Deleted : Operation()

        @Serializable
        data class Archived(val itemUrlLocator: ItemUrlLocator) : Operation()
      }
    }

    
  }
hrach commented

That's the problem of KotlinX.Serialization and its limitation on polymorphism. There are two options:

A:

navController.setResult<ListDestinations.AddItem.Result>(ListDestinations.AddItem.Created)

or B:
you found it - #133 (comment) - which is probably the better way as it is less error-prone.

AFAIK: there are no solutions, I spent hours finding something.

maybe it would be worthwhile to add an example to the result sharing part of the readme? either way this can be closed :)