Как этот код может генерировать исключение NoWhenBranchMatchedException?

В нашем последнем выпуске приложения мы видим несколько kotlin.NoWhenBranchMatchedExceptions сообщается в Fabric/Crashlytics.

Это фрагмент кода в вопросе:

private lateinit var welcome: Welcome

// ...

welcome.welcomeStateLoginStatus.let {
    val handled = when (it) {
        UnknownUser -> {
            btn_login.visibility = View.VISIBLE
            btn_logout.visibility = View.GONE

            secondButtonFocusedInfoText = getString(R.string.welcome_login_button_info)
            tv_user_description.text = null
        }
        is InternalUser -> {
            btn_login.visibility = View.GONE
            btn_logout.visibility = View.VISIBLE

            secondButtonFocusedInfoText = "Logout"
            tv_user_description.text = "Logged in as internal user"
        }
        ExternalUser -> {
            btn_login.visibility = View.GONE
            btn_logout.visibility = View.VISIBLE

            secondButtonFocusedInfoText = "Logout"
            tv_user_description.text = "Logged in as external user"
        }
    }
}

И определения классов:

data class Welcome(val welcomeStateLoginStatus: WelcomeStateLoginStatus, val userCanBuySubscription: UserCanBuySubscription? = null) : WelcomeState()

sealed class WelcomeStateLoginStatus() : Serializable
object UnknownUser : WelcomeStateLoginStatus()
data class InternalUser(var user: User) : WelcomeStateLoginStatus()
object ExternalUser : WelcomeStateLoginStatus()

Я озадачен тем, как этот код может даже теоретически вызвать это исключение - как вы можете видеть, мы даже представили handled значение просто, чтобы заставить компилятор убедиться, что все случаи обрабатываются...

2 ответа

Сериализация была действительно проблемой:

package com.drei.tv.ui.welcome

import junit.framework.Assert.assertEquals
import org.junit.Test
import java.io.*


class WelcomeStateLoginStatusTest {

    @Test
    fun testSerialization() {
        val original: UnknownUser = UnknownUser

        val copy: UnknownUser = unpickle(pickle(original), UnknownUser::class.java)

        println("singleton: $UnknownUser")
        println("original: $original")
        println("copy: $copy")

        val handled1 = when (copy) {
            original -> println("copy matches original")
            else -> println("copy does not match original")
        }

        val handled2 = when (copy) {
            is UnknownUser -> println("copy is an instance of UnknownUser")
            else -> println("copy is no instance of UnknownUser")
        }

        assertEquals(original, copy)
    }

    private fun <T : Serializable> pickle(obj: T): ByteArray {
        val baos = ByteArrayOutputStream()
        val oos = ObjectOutputStream(baos)
        oos.writeObject(obj)
        oos.close()
        return baos.toByteArray()
    }

    private fun <T : Serializable> unpickle(b: ByteArray, cl: Class<T>): T {
        val bais = ByteArrayInputStream(b)
        val ois = ObjectInputStream(bais)
        val o = ois.readObject()
        return cl.cast(o)
    }
}

Производит следующий вывод:

singleton: com.drei.tv.ui.welcome.UnknownUser@75828a0f
original: com.drei.tv.ui.welcome.UnknownUser@75828a0f
copy: com.drei.tv.ui.welcome.UnknownUser@5f150435
copy does not match original
copy is an instance of UnknownUser

junit.framework.AssertionFailedError: 
Expected :com.drei.tv.ui.welcome.UnknownUser@75828a0f
Actual   :com.drei.tv.ui.welcome.UnknownUser@5f150435

Что касается решений: либо реализовать Serializable правильно или просто использовать is проверка вместо проверки на равенство.

Спасибо Лайонелу Бриану и Хон Дуану за указание нас в правильном направлении и Джейсону С за код pickle а также unpickle опубликовано в этом ответе

Я только что столкнулся с этим вопросом и не вижу, как мы решили эту проблему в ответах. Хотя это старый вопрос, я помещаю здесь решение на тот случай, если оно поможет людям искать.

Что мы делаем, например, для UnknownUser:

      object UnknownUser : WelcomeStateLoginStatus() {
    private fun readResolve(): Any = UnknownUser
}

«Метод readResolve() является частью контракта между самим классом и сериализатором Java. Точно так же, как сериализатор может вызвать частный конструктор или также обнаружить и вызвать (если он есть) метод readResolve() во время десериализации.

В этом случае мы реализуем readResolve() для возврата синглтона. "

Здесь есть подробное объяснение, зачем это нужно:https://blog.stylingandroid.com/kotlin-serializable-objects/

Другие вопросы по тегам