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

android app error handling

android app error handling

3c4e24fd827c789cb67a9f759f057b06?s=128

Shinnosuke Kugimiya

January 26, 2021
Tweet

Transcript

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

  2. ࣗݾ঺հ

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

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

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

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

  7. ߟ͑Δ΂͖͜ͱ

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

    ϢʔβϏϦςΟ΋ߟ͑Α͏
  9. ·ͣ͸ચ͍ग़ͦ͏ Πϯϑϥ૚ ϓϨθϯςʔγϣϯ૚ "DUJWJUZ'SBHNFOU 7JFX.PEFM 3FQPTJUPSZ "1*$MJFOU %# Ϣʔεέʔε૚ 6TF$BTF

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

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

    ϞσϦϯάͨ͠Ϋϥε܈ ೝূܥͷΤϥʔ αʔϏεܥͷΤϥʔ %#ʹؔ͢ΔΤϥʔ ঢ়ଶҟৗͳͲͷΤϥʔ ঢ়ଶҟৗͳͲͷΤϥʔ "OESPJEʹؔ͢ΔΤϥʔ ௨৴ෆೳܥͷΤϥʔ ΧςΰϥΠζ͠Α͏ w αʔϏεʹؔΘΔΤϥʔ w ೝূΤϥʔ w αʔϏεಠࣗͷΤϥʔ w αʔϏεʹΑΒͳ͍Τϥʔ w ௨৴Τϥʔ w %#઀ଓෆྑΤϥʔ w "OESPJEʹؔ͢ΔΤϥʔ
  12. Τϥʔͷॲཧͷ࢓ํΛߟ͑Δ w ݸผॲཧʢͦͷը໘Ͱ͔͠ൃੜ͠ͳ͍Τϥʔͷॲཧͷ࢓ํʣ w ڞ௨ॲཧʢͲͷը໘Ͱ΋ى͖ΔΤϥʔͷॲཧͷ࢓ํʣ w μΠΞϩά w εφοΫόʔ w

    ڧ੍ը໘ભҠʢϩάΠϯը໘΍ϝϯςφϯεը໘ʹඈ͹͢ͳͲʣ w $SBTIMZUJDTʹUISPXBCMFΛͱΓ͋͑ͣ౤͓͛ͯ͘ w ॲཧ͠ͳ͍ʢͦͷ··$SBTIͤ͞Δʣ
  13. ϓϨθϯςʔγϣϯ૚ "DUJWJUZ'SBHNFOU 7JFX.PEFM Ϣʔεέʔε૚ 6TF$BTF υϝΠϯ૚ ϞσϦϯάͨ͠Ϋϥε܈ ঢ়ଶҟৗͳͲͷΤϥʔ ঢ়ଶҟৗͳͲͷΤϥʔ "OESPJEʹؔ͢ΔΤϥʔ

    $BUDI͢Δ͠ͳ͍ͷڥքઢΛܾΊΔ ঢ়ଶҟৗͳͲͷΤϥʔ DBUDI͢Δ DBUDI͠ͳ͍ ࢲͷ৔߹͸ "OESPJEʹؔ͢ΔΤϥʔ͸ DBUDI͠ͳ͍ɻ
  14. ΧςΰϥΠζͨ͠ΤϥʔͱॲཧΛϚοϐϯά͢Δ ೝূΤϥʔ ະ஌ͷΤϥʔ ঢ়ଶҟৗܥΤϥʔ ௨৴Τϥʔ %#઀ଓෆྑΤϥʔ ϩάΠϯը໘ʹભҠ ϝϯςφϯεը໘ʹભҠ ϝϯςφϯε μΠΞϩάදࣔ

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

  16. ϢʔβʔϏϦςΟ΋ߟ͑Δ w ͜ͷ࣌ʹ௨৴ෆྑͰશͯͷ௨ ৴͕ࣦഊͨ͠ͱ͢Δ w ࣍ͷμΠΞϩά͕Ξλονͯ͠ ͍ΔμΠΞϩάͷ਺͚ͩͰΔ ͷ͸๷͍͗ͨ "Ϟδϡʔϧͷ"'SBHNFOU #Ϟδϡʔϧͷ#'SBHNFOU

    $Ϟδϡʔϧͷ$'SBHNFOU ௨৴Τϥʔ͕ൃੜ͠·ͨ͠ɻ ࣌ؒΛ͓͍͓ͯࢼ͍ͩ͘͠͞ɻ ௨৴Τϥʔ͕ൃੜ͠·ͨ͠ɻ ࣌ؒΛ͓͍͓ͯࢼ͍ͩ͘͠͞ɻ ௨৴Τϥʔ͕ൃੜ͠·ͨ͠ɻ ࣌ؒΛ͓͍͓ͯࢼ͍ͩ͘͠͞ɻ
  17. ίʔυྫ

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

    = MutableLiveData<Throwable>() val error: LiveData<Throwable> = _error fun onCreateView() = viewModelScope.launch { try { _user.value = useCase.execute() } catch (t: Throwable) { _error.value = t } } }
  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() { … } }
  20. ΤϥʔΛॲཧ͢Δʢݸผͷ৔߹͸ݸผʹʣ 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() { … } }
  21. ΤϥʔΛॲཧ͢Δʢڞ௨ͷ৔߹͸Τϥʔϋϯυϥʔʹʣ 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() { … } }
  22. &SSPS)BOEMFS class ErrorHandler { private val queue = LinkedList<ErrorRunnable>() 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) } } }
  23. &SSPS)BOEMFS class ErrorHandler { private val queue = LinkedList<ErrorRunnable>() 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
  24. &SSPS)BOEMFS class ErrorHandler { private val queue = LinkedList<ErrorRunnable>() 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) } } }
  25. &SSPS)BOEMFS class ErrorHandler { private val queue = LinkedList<ErrorRunnable>() 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) } } }
  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( 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) } } }
  27. &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(…) {…} … }
  28. &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(…) {…} … }
  29. &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) } } }
  30. ·ͱΊ

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

    ࣌ʹ͸$SBTIͤ͞Δ΂͖ͱ͍͏൑அ΋͠Α͏