Upgrade to Pro — share decks privately, control downloads, hide ads and more …

android app error handling

android app error handling

Shinnosuke Kugimiya

January 26, 2021
Tweet

More Decks by Shinnosuke Kugimiya

Other Decks in Programming

Transcript

  1. !LHNZTIJO
    ΤϥʔϋϯυϦϯά

    View full-size slide

  2. ࣗݾ঺հ
    w ఝٶͰ͢
    w ߹ಉձࣾ%..DPN$50ࣨॴଐ

    View full-size slide

  3. ͜͏͍͏͜ͱ͋Γ·ͤΜ͔ʁ

    View full-size slide

  4. w ѲΓ௵͢ͳͬͯݴΘΕͯΔ͚Ͳɺ͡Ό͊Ͳ͏͢Ε͹͍͍ͷʁͬͯࢥͬͯΔਓɺ
    ࣮͸݁ߏ͍ΔΜ͡Όͳ͍ΜͰ͔͢ʁ

    View full-size slide

  5. w ѲΓ௵͢ͳͬͯݴΘΕͯΔ͚Ͳɺ͡Ό͊Ͳ͏͢Ε͹͍͍ͷʁͬͯࢥͬͯΔਓɺ
    ࣮͸݁ߏ͍ΔΜ͡Όͳ͍ΜͰ͔͢ʁ
    w ద౰ʹ΍Δͱɺ$SBTI'SFF཰͕ͱΜͰ΋ͳ͍͜ͱʹʜ

    View full-size slide

  6. ߟ͑Δ΂͖͜ͱ

    View full-size slide

  7. ߟ͑Δ΂͖͜ͱ
    w ·ͣ͸ચ͍ग़ͦ͏
    w ΧςΰϥΠζ͠·͠ΐ͏
    w ॲཧͷ࢓ํΛચ͍ग़͠·͠ΐ͏
    w ΧςΰϥΠζͨ͠ΤϥʔͱॲཧΛϚοϐϯά͠·͠ΐ͏
    w ϢʔβϏϦςΟ΋ߟ͑Α͏

    View full-size slide

  8. ·ͣ͸ચ͍ग़ͦ͏
    Πϯϑϥ૚
    ϓϨθϯςʔγϣϯ૚
    "DUJWJUZ'SBHNFOU
    7JFX.PEFM
    3FQPTJUPSZ
    "1*$MJFOU %#
    Ϣʔεέʔε૚ 6TF$BTF
    υϝΠϯ૚ ϞσϦϯάͨ͠Ϋϥε܈

    View full-size slide

  9. ·ͣ͸ચ͍ग़ͦ͏
    Πϯϑϥ૚
    ϓϨθϯςʔγϣϯ૚
    "DUJWJUZ'SBHNFOU
    7JFX.PEFM
    3FQPTJUPSZ
    "1*$MJFOU %#
    Ϣʔεέʔε૚ 6TF$BTF
    υϝΠϯ૚ ϞσϦϯάͨ͠Ϋϥε܈
    ೝূܥͷΤϥʔ
    αʔϏεܥͷΤϥʔ
    %#ʹؔ͢ΔΤϥʔ
    ঢ়ଶҟৗͳͲͷΤϥʔ
    ঢ়ଶҟৗͳͲͷΤϥʔ
    "OESPJEʹؔ͢ΔΤϥʔ
    ௨৴ෆೳܥͷΤϥʔ

    View full-size slide

  10. Πϯϑϥ૚
    ϓϨθϯςʔγϣϯ૚
    "DUJWJUZ'SBHNFOU
    7JFX.PEFM
    3FQPTJUPSZ
    "1*$MJFOU %#
    Ϣʔεέʔε૚ 6TF$BTF
    υϝΠϯ૚ ϞσϦϯάͨ͠Ϋϥε܈
    ೝূܥͷΤϥʔ
    αʔϏεܥͷΤϥʔ
    %#ʹؔ͢ΔΤϥʔ
    ঢ়ଶҟৗͳͲͷΤϥʔ
    ঢ়ଶҟৗͳͲͷΤϥʔ
    "OESPJEʹؔ͢ΔΤϥʔ
    ௨৴ෆೳܥͷΤϥʔ
    ΧςΰϥΠζ͠Α͏
    w αʔϏεʹؔΘΔΤϥʔ
    w ೝূΤϥʔ
    w αʔϏεಠࣗͷΤϥʔ
    w αʔϏεʹΑΒͳ͍Τϥʔ
    w ௨৴Τϥʔ
    w %#઀ଓෆྑΤϥʔ
    w "OESPJEʹؔ͢ΔΤϥʔ

    View full-size slide

  11. Τϥʔͷॲཧͷ࢓ํΛߟ͑Δ
    w ݸผॲཧʢͦͷը໘Ͱ͔͠ൃੜ͠ͳ͍Τϥʔͷॲཧͷ࢓ํʣ
    w ڞ௨ॲཧʢͲͷը໘Ͱ΋ى͖ΔΤϥʔͷॲཧͷ࢓ํʣ
    w μΠΞϩά
    w εφοΫόʔ
    w ڧ੍ը໘ભҠʢϩάΠϯը໘΍ϝϯςφϯεը໘ʹඈ͹͢ͳͲʣ
    w $SBTIMZUJDTʹUISPXBCMFΛͱΓ͋͑ͣ౤͓͛ͯ͘
    w ॲཧ͠ͳ͍ʢͦͷ··$SBTIͤ͞Δʣ

    View full-size slide

  12. ϓϨθϯςʔγϣϯ૚
    "DUJWJUZ'SBHNFOU
    7JFX.PEFM
    Ϣʔεέʔε૚ 6TF$BTF
    υϝΠϯ૚ ϞσϦϯάͨ͠Ϋϥε܈
    ঢ়ଶҟৗͳͲͷΤϥʔ
    ঢ়ଶҟৗͳͲͷΤϥʔ
    "OESPJEʹؔ͢ΔΤϥʔ
    $BUDI͢Δ͠ͳ͍ͷڥքઢΛܾΊΔ
    ঢ়ଶҟৗͳͲͷΤϥʔ
    DBUDI͢Δ
    DBUDI͠ͳ͍
    ࢲͷ৔߹͸
    "OESPJEʹؔ͢ΔΤϥʔ͸
    DBUDI͠ͳ͍ɻ

    View full-size slide

  13. ΧςΰϥΠζͨ͠ΤϥʔͱॲཧΛϚοϐϯά͢Δ
    ೝূΤϥʔ
    ະ஌ͷΤϥʔ
    ঢ়ଶҟৗܥΤϥʔ
    ௨৴Τϥʔ
    %#઀ଓෆྑΤϥʔ
    ϩάΠϯը໘ʹભҠ
    ϝϯςφϯεը໘ʹભҠ
    ϝϯςφϯε
    μΠΞϩάදࣔ
    4OBDLCBSදࣔ
    $SBTIMJUZDTʹ౤͛Δ

    View full-size slide

  14. ϢʔβʔϏϦςΟ΋ߟ͑Δ
    w ྫ͑͹ϚϧνϞδϡʔϧΛ࠾
    ༻͍ͯ͠ΔϓϩδΣΫτͰɺ
    .BJOʹͳΔΑ͏ͳը໘͕࣍ͷ
    Α͏ʹෳ਺ͷ'SBHNFOUΛΞλ
    ον͍ͯ͠Δ࣌
    "Ϟδϡʔϧͷ"'SBHNFOU
    #Ϟδϡʔϧͷ#'SBHNFOU
    $Ϟδϡʔϧͷ$'SBHNFOU

    View full-size slide

  15. ϢʔβʔϏϦςΟ΋ߟ͑Δ
    w ͜ͷ࣌ʹ௨৴ෆྑͰશͯͷ௨
    ৴͕ࣦഊͨ͠ͱ͢Δ
    w ࣍ͷμΠΞϩά͕Ξλονͯ͠
    ͍ΔμΠΞϩάͷ਺͚ͩͰΔ
    ͷ͸๷͍͗ͨ
    "Ϟδϡʔϧͷ"'SBHNFOU
    #Ϟδϡʔϧͷ#'SBHNFOU
    $Ϟδϡʔϧͷ$'SBHNFOU
    ௨৴Τϥʔ͕ൃੜ͠·ͨ͠ɻ
    ࣌ؒΛ͓͍͓ͯࢼ͍ͩ͘͠͞ɻ
    ௨৴Τϥʔ͕ൃੜ͠·ͨ͠ɻ
    ࣌ؒΛ͓͍͓ͯࢼ͍ͩ͘͠͞ɻ
    ௨৴Τϥʔ͕ൃੜ͠·ͨ͠ɻ
    ࣌ؒΛ͓͍͓ͯࢼ͍ͩ͘͠͞ɻ

    View full-size slide

  16. ΤϥʔΛΩϟονͯ͠-JWF%BUBʹ٧ΊΔ
    class UserViewModel : ViewModel() {

    private val _error = MutableLiveData()
    val error: LiveData = _error
    fun onCreateView() = viewModelScope.launch {
    try {
    _user.value = useCase.execute()
    } catch (t: Throwable) {
    _error.value = t
    }
    }
    }

    View full-size slide

  17. ΤϥʔΛॲཧ͢Δ
    class UserFragment : Fragment() {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

    viewModel.error.observe(viewLifecycleOwner) {
    if (it is NotFoundUserException) {
    showNotFound()
    return@observe
    }
    errorHandler.handle(
    requireContext(),
    view.rootView,
    it
    )
    }
    }
    private fun showNotFound() {

    }
    }

    View full-size slide

  18. ΤϥʔΛॲཧ͢Δʢݸผͷ৔߹͸ݸผʹʣ
    class UserFragment : Fragment() {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

    viewModel.error.observe(viewLifecycleOwner) {
    if (it is NotFoundUserException) {
    showNotFound()
    return@observe
    }
    errorHandler.handle(
    requireContext(),
    view.rootView,
    it
    )
    }
    }
    private fun showNotFound() {

    }
    }

    View full-size slide

  19. ΤϥʔΛॲཧ͢Δʢڞ௨ͷ৔߹͸Τϥʔϋϯυϥʔʹʣ
    class UserFragment : Fragment() {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

    viewModel.error.observe(viewLifecycleOwner) {
    if (it is NotFoundUserException) {
    showNotFound()
    return@observe
    }
    errorHandler.handle(
    requireContext(),
    view.rootView,
    it
    )
    }
    }
    private fun showNotFound() {

    }
    }

    View full-size slide

  20. &SSPS)BOEMFS
    class ErrorHandler {
    private val queue = LinkedList()
    private val handler = Handler(Looper.getMainLooper())
    fun handle(
    context: Context, view: View, throwable: Throwable
    ) {
    val runnable = ErrorRunnable(
    Error.convert(context, view, throwable)
    )
    if (queue.none { it.error == runnable.error }) {
    queue.add(runnable)
    handler.post(runnable)
    }
    }
    inner class ErrorRunnable(val error: Error) : Runnable {
    override fun run() {
    error.handle()
    val running = this
    handler.postDelayed({
    queue.remove(running)
    }, 200)
    }
    }
    }

    View full-size slide

  21. &SSPS)BOEMFS
    class ErrorHandler {
    private val queue = LinkedList()
    private val handler = Handler(Looper.getMainLooper())
    fun handle(
    context: Context, view: View, throwable: Throwable
    ) {
    val runnable = ErrorRunnable(
    Error.convert(context, view, throwable)
    )
    if (queue.none { it.error == runnable.error }) {
    queue.add(runnable)
    handler.post(runnable)
    }
    }
    inner class ErrorRunnable(val error: Error) : Runnable {
    override fun run() {
    error.handle()
    val running = this
    handler.postDelayed({
    queue.remove(running)
    }, 200)
    }
    }
    }
    5ISPXBCMF&SSPS

    View full-size slide

  22. &SSPS)BOEMFS
    class ErrorHandler {
    private val queue = LinkedList()
    private val handler = Handler(Looper.getMainLooper())
    fun handle(
    context: Context, view: View, throwable: Throwable
    ) {
    val runnable = ErrorRunnable(
    Error.convert(context, view, throwable)
    )
    if (queue.none { it.error == runnable.error }) {
    queue.add(runnable)
    handler.post(runnable)
    }
    }
    inner class ErrorRunnable(val error: Error) : Runnable {
    override fun run() {
    error.handle()
    val running = this
    handler.postDelayed({
    queue.remove(running)
    }, 200)
    }
    }
    }

    View full-size slide

  23. &SSPS)BOEMFS
    class ErrorHandler {
    private val queue = LinkedList()
    private val handler = Handler(Looper.getMainLooper())
    fun handle(
    context: Context, view: View, throwable: Throwable
    ) {
    val runnable = ErrorRunnable(
    Error.convert(context, view, throwable)
    )
    if (queue.none { it.error == runnable.error }) {
    queue.add(runnable)
    handler.post(runnable)
    }
    }
    inner class ErrorRunnable(val error: Error) : Runnable {
    override fun run() {
    error.handle()
    val running = this
    handler.postDelayed({
    queue.remove(running)
    }, 200)
    }
    }
    }

    View full-size slide

  24. &SSPS
    sealed class Error {
    abstract fun handle()
    class MaintenanceError(
    private val context: Context
    ) : Error() {
    override fun handle() {
    context.startActivity(
    MaintenanceActivity.createIntent(context).apply {
    addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
    }
    )
    }
    override fun equals(other: Any?): Boolean = other is MaintenanceError
    override fun hashCode(): Int {
    return context.hashCode()
    }
    }
    class UnknownError(
    private val view: View,
    private val throwable: Throwable
    ) : Error() {
    override fun handle() {
    FirebaseCrashlytics.getInstance().recordException(throwable)
    Snackbar.make(view, "ෆ໌ͳΤϥʔͰ͢", Snackbar.LENGTH_SHORT).show()
    }
    }
    companion object {
    fun convert(
    context: Context,
    view: View,
    throwable: Throwable
    ): Error = when {
    throwable is HttpException && throwable.code() == 503 -> MaintenanceError(context)
    else -> UnknownError(view, throwable)
    }
    }
    }

    View full-size slide

  25. &SSPS
    sealed class Error {
    abstract fun handle()
    class MaintenanceError(
    private val context: Context
    ) : Error() {
    override fun handle() {
    context.startActivity(
    MaintenanceActivity.createIntent(context).apply {
    addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
    }
    )
    }
    override fun equals(other: Any?): Boolean = other is MaintenanceError
    override fun hashCode(): Int {
    return context.hashCode()
    }
    }
    class UnknownError(…) {…}

    }

    View full-size slide

  26. &SSPS
    sealed class Error {
    abstract fun handle()
    class MaintenanceError(
    private val context: Context
    ) : Error() {
    override fun handle() {
    context.startActivity(
    MaintenanceActivity.createIntent(context).apply {
    addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
    }
    )
    }
    override fun equals(other: Any?): Boolean = other is MaintenanceError
    override fun hashCode(): Int {
    return context.hashCode()
    }
    }
    class UnknownError(…) {…}

    }

    View full-size slide

  27. &SSPS
    sealed class Error {

    companion object {
    fun convert(
    context: Context,
    view: View,
    throwable: Throwable
    ): Error = when {
    throwable is HttpException && throwable.code() == 503 -> MaintenanceError(context)

    else -> UnknownError(view, throwable)
    }
    }
    }

    View full-size slide

  28. ·ͱΊ
    w ΤϥʔΛચ͍ग़ͯ͠ΧςΰϥΠζ͠Α͏
    w ॲཧͷ࢓ํΛચ͍ग़ͦ͏
    w ΤϥʔͱॲཧΛϚοϐϯά͠Α͏
    w ΞϓϦ͸Ϣʔβʔ͕৮Δ΋ͷͳͷͰɺϢʔβʔϏϦςΟ΋ߟ͑Α͏
    w ࣌ʹ͸$SBTIͤ͞Δ΂͖ͱ͍͏൑அ΋͠Α͏

    View full-size slide