Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

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. ·ͣ͸ચ͍ग़ͦ͏ Πϯϑϥ૚ ϓϨθϯςʔγϣϯ૚ "DUJWJUZ'SBHNFOU 7JFX.PEFM 3FQPTJUPSZ "1*$MJFOU %# Ϣʔεέʔε૚ 6TF$BTF

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

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

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

    $BUDI͢Δ͠ͳ͍ͷڥքઢΛܾΊΔ ঢ়ଶҟৗͳͲͷΤϥʔ DBUDI͢Δ DBUDI͠ͳ͍ ࢲͷ৔߹͸ "OESPJEʹؔ͢ΔΤϥʔ͸ DBUDI͠ͳ͍ɻ
  5. ϢʔβʔϏϦςΟ΋ߟ͑Δ w ͜ͷ࣌ʹ௨৴ෆྑͰશͯͷ௨ ৴͕ࣦഊͨ͠ͱ͢Δ w ࣍ͷμΠΞϩά͕Ξλονͯ͠ ͍ΔμΠΞϩάͷ਺͚ͩͰΔ ͷ͸๷͍͗ͨ "Ϟδϡʔϧͷ"'SBHNFOU #Ϟδϡʔϧͷ#'SBHNFOU

    $Ϟδϡʔϧͷ$'SBHNFOU ௨৴Τϥʔ͕ൃੜ͠·ͨ͠ɻ ࣌ؒΛ͓͍͓ͯࢼ͍ͩ͘͠͞ɻ ௨৴Τϥʔ͕ൃੜ͠·ͨ͠ɻ ࣌ؒΛ͓͍͓ͯࢼ͍ͩ͘͠͞ɻ ௨৴Τϥʔ͕ൃੜ͠·ͨ͠ɻ ࣌ؒΛ͓͍͓ͯࢼ͍ͩ͘͠͞ɻ
  6. ΤϥʔΛΩϟονͯ͠-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 } } }
  7. ΤϥʔΛॲཧ͢Δ 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() { … } }
  8. ΤϥʔΛॲཧ͢Δʢݸผͷ৔߹͸ݸผʹʣ 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() { … } }
  9. ΤϥʔΛॲཧ͢Δʢڞ௨ͷ৔߹͸Τϥʔϋϯυϥʔʹʣ 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() { … } }
  10. &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) } } }
  11. &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
  12. &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) } } }
  13. &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) } } }
  14. &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) } } }
  15. &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(…) {…} … }
  16. &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(…) {…} … }
  17. &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) } } }