Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 28 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ on the platforms. Converts the exception class to an error object to display. Th
## Versions
- kotlin 1.3.72
- 0.1.0
- 0.2.0

## Installation
root build.gradle
Expand All @@ -49,7 +50,7 @@ allprojects {
project build.gradle
```groovy
dependencies {
commonMainApi("dev.icerock.moko:errors:0.1.0")
commonMainApi("dev.icerock.moko:errors:0.2.0")
}
```

Expand All @@ -61,7 +62,7 @@ E.g. declare `ExceptionHandler` property in some `ViewModel` class:

```kotlin
class SimpleViewModel(
val exceptionHandler: ExceptionHandler
val exceptionHandler: ExceptionHandler<StringDesc>
) : ViewModel() {
// ...
}
Expand All @@ -84,12 +85,16 @@ On iOS in a `ViewController`:
viewModel.exceptionHandler.bind(viewController: self)
```

Creating instances of `ExceptionHandler` class:
Creating instances of `ExceptionHandler` class which works with `(Throwable) -> StringDesc`
mappers:

```kotlin
ExceptionHandler(
errorEventsDispatcher = eventsDispatcherInstance, // moko-mvvm EventsDispatcher instance
errorPresenter = errorsPresenterInstance // concrete ErrorPresenter implementation
ExceptionHandler<StringDesc>(
errorEventsDispatcher = eventsDispatcherInstance, // moko-mvvm EventsDispatcher instance
errorPresenter = errorsPresenterInstance, // Concrete ErrorPresenter implementation
onCatch = { // Optional global catcher
println("Got exception: $it") // E.g. here we can log all exceptions that are handled by ExceptionHandler
}
)
```

Expand All @@ -99,10 +104,10 @@ And use it to safe requests in `ViewModel`:
fun onSendRequest() {
viewModelScope.launch {
exceptionHandler.handle {
serverRequest() // Some dangerous code that can throw an exception
}.finally {
// Optional finally block
}.execute() // Executes handler block
serverRequest() // Some dangerous code that can throw an exception
}.finally { // Optional finally block
// Some code
}.execute() // Executes handler block
}
}
```
Expand All @@ -114,10 +119,10 @@ fun onSendRequest() {
viewModelScope.launch {
exceptionHandler.handle {
serverRequest()
}.catch<IllegalArgumentException> { // Specifying a specific exception class
}.catch<IllegalArgumentException> { // Specifying a specific exception class
// Some custom handler code
false // true - cancels ErrorPresenter; false - allows execution of ErrorsPresenter
}.execute()
false // true - cancels ErrorPresenter; false - allows execution of ErrorsPresenter
}.execute() // Starts code execution in `handle` lambda
}
}
```
Expand All @@ -128,20 +133,20 @@ Registration of simple custom exceptions mapper:

```kotlin
ExceptionMappersStorage
.register<IllegalArgumentException, StringDesc> { // Maps IllegalArgumentException instances to StringDesc
MR.strings.illegalArgumentText.desc()
.register<IllegalArgumentException, StringDesc> { // Maps IllegalArgumentException instances to StringDesc
"Illegal argument was passed!".desc()
}
.register<HttpException, Int> { // Maps HttpException instances to Int
.register<HttpException, Int> { // Maps HttpException instances to Int
it.code
}
```

Registration of custom exception mapper with condition:

```kotlin
ExceptionMappersStorage.condition<StringDesc>( // Registers exception mapper Throwable -> StringDesc
ExceptionMappersStorage.condition<StringDesc>( // Registers exception mapper Throwable -> StringDesc
condition = { it is CustomException && it.code == 10 }, // Condition that maps Throwable -> Boolean
mapper = { MR.strings.myExceptionText.desc() } // Mapper for Throwable that matches to the condition
mapper = { "Custom error happened!".desc() } // Mapper for Throwable that matches to the condition
)
```

Expand All @@ -151,10 +156,10 @@ The registration can be done in the form of an endless chain:
ExceptionMappersStorage
.condition<StringDesc>(
condition = { it is CustomException && it.code == 10 },
mapper = { MR.strings.myExceptionText.desc() }
mapper = { "Custom error happened!".desc() }
)
.register<IllegalArgumentException, StringDesc> {
MR.strings.illegalArgumentText.desc()
"Illegal argument was passed!".desc()
}
.register<HttpException, Int> {
it.code
Expand All @@ -175,9 +180,9 @@ Or you can create your own mapper using extensions:

```kotlin
fun <E : Throwable> ExceptionMappersStorage.throwableToInt(e: E): Int {
return find<Int, E>(e) // Tries to find mapper (Throwable) -> Int in the registry
?.invoke(e) // If it was found - invokes it
?: 0 // Or default value
return find<E, Int>(e) // Tries to find mapper (Throwable) -> Int in the registry
?.invoke(e) // If it was found - invokes it
?: 0 // Or default value
}
```

Expand Down
4 changes: 3 additions & 1 deletion buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ object Versions {
const val minSdk = 16
}

const val mokoErrors = "0.2.0"

const val kotlin = "1.3.72"
private const val androidArch = "2.0.0"
private const val mokoResources = "0.11.0"
Expand All @@ -30,7 +32,7 @@ object Versions {

object MultiPlatform {
const val coroutines = "1.3.4"
const val mokoErrors = "0.1.0"
const val mokoErrors = Versions.mokoErrors
const val mokoMvvm = "0.7.0"
const val mokoResources = Versions.mokoResources
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,27 @@ package dev.icerock.moko.errors.handler
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.LifecycleOwner
import dev.icerock.moko.errors.ErrorEventListener
import dev.icerock.moko.errors.ErrorPresenter
import dev.icerock.moko.errors.presenters.ErrorPresenter
import dev.icerock.moko.mvvm.dispatcher.EventsDispatcher

actual interface ExceptionHandlerBinder {
fun bind(lifecycleOwner: LifecycleOwner, activity: FragmentActivity)
}

actual class ExceptionHandlerBinderImpl actual constructor(
private val errorPresenter: ErrorPresenter,
private val eventsDispatcher: EventsDispatcher<ErrorEventListener>
actual class ExceptionHandlerBinderImpl<T : Any> actual constructor(
private val errorPresenter: ErrorPresenter<T>,
private val eventsDispatcher: EventsDispatcher<ErrorEventListener<T>>
) : ExceptionHandlerBinder {
override fun bind(lifecycleOwner: LifecycleOwner, activity: FragmentActivity) {
eventsDispatcher.bind(lifecycleOwner, EventsListener(activity, errorPresenter))
}

class EventsListener(
class EventsListener<T : Any>(
private val activity: FragmentActivity,
private val errorPresenter: ErrorPresenter
) : ErrorEventListener {
override fun showError(exception: Throwable) {
errorPresenter.show(exception, activity)
private val errorPresenter: ErrorPresenter<T>
) : ErrorEventListener<T> {
override fun showError(throwable: Throwable, data: T) {
errorPresenter.show(throwable, activity, data)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,23 @@ package dev.icerock.moko.errors.presenters

import android.os.Bundle
import androidx.fragment.app.FragmentActivity
import dev.icerock.moko.errors.ErrorPresenter
import dev.icerock.moko.resources.desc.StringDesc

actual class AlertErrorPresenter actual constructor(
private val exceptionMapper: (Throwable) -> StringDesc,
override val exceptionMapper: (Throwable) -> StringDesc,
private val alertTitle: StringDesc,
private val positiveButtonText: StringDesc
) : ErrorPresenter {
) : ErrorPresenter<StringDesc>() {

override fun show(exception: Throwable, activity: FragmentActivity) {
override fun show(throwable: Throwable, activity: FragmentActivity, data: StringDesc) {
AlertDialogFragment().apply {
arguments = Bundle().apply {
putParcelable(
AlertDialogFragment.ARGS_KEY,
AlertDialogFragment.DialogSettings(
title = alertTitle.toString(activity),
positiveButtonText = positiveButtonText.toString(activity),
messageText = exceptionMapper(exception).toString(activity)
messageText = data.toString(activity)
)
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.moko.errors.presenters

import androidx.fragment.app.FragmentActivity

actual abstract class ErrorPresenter<T : Any> : ErrorPresenterBase<T>() {
abstract fun show(throwable: Throwable, activity: FragmentActivity, data: T)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,20 @@ package dev.icerock.moko.errors.presenters
import android.view.View
import androidx.fragment.app.FragmentActivity
import com.google.android.material.snackbar.Snackbar
import dev.icerock.moko.errors.ErrorPresenter
import dev.icerock.moko.resources.desc.StringDesc

actual class SnackBarErrorPresenter actual constructor(
private val exceptionMapper: (Throwable) -> StringDesc,
override val exceptionMapper: (Throwable) -> StringDesc,
private val duration: SnackBarDuration
) : ErrorPresenter {
) : ErrorPresenter<StringDesc>() {

override fun show(exception: Throwable, activity: FragmentActivity) {
override fun show(throwable: Throwable, activity: FragmentActivity, data: StringDesc) {
val rootView = activity.findViewById<View>(android.R.id.content)?.rootView
?: activity.window?.decorView?.findViewById<View>(android.R.id.content)
if (rootView != null) {
Snackbar.make(
rootView,
exceptionMapper(exception).toString(activity),
data.toString(activity),
duration.toAndroidCode()
).show()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,17 @@ package dev.icerock.moko.errors.presenters

import android.widget.Toast
import androidx.fragment.app.FragmentActivity
import dev.icerock.moko.errors.ErrorPresenter
import dev.icerock.moko.resources.desc.StringDesc

actual class ToastErrorPresenter actual constructor(
private val exceptionMapper: (Throwable) -> StringDesc,
override val exceptionMapper: (Throwable) -> StringDesc,
private val duration: ToastDuration
) : ErrorPresenter {
) : ErrorPresenter<StringDesc>() {

override fun show(exception: Throwable, activity: FragmentActivity) {
override fun show(throwable: Throwable, activity: FragmentActivity, data: StringDesc) {
Toast.makeText(
activity,
exceptionMapper(exception).toString(activity),
data.toString(activity),
duration.toAndroidCode()
).show()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@

package dev.icerock.moko.errors

interface ErrorEventListener {
fun showError(exception: Throwable)
interface ErrorEventListener<T> {
fun showError(throwable: Throwable, data: T)
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,19 @@
package dev.icerock.moko.errors.handler

import dev.icerock.moko.errors.ErrorEventListener
import dev.icerock.moko.errors.ErrorPresenter
import dev.icerock.moko.errors.presenters.ErrorPresenter
import dev.icerock.moko.mvvm.dispatcher.EventsDispatcher

class ExceptionHandler(
errorPresenter: ErrorPresenter,
private val errorEventsDispatcher: EventsDispatcher<ErrorEventListener>
) : ExceptionHandlerBinder by ExceptionHandlerBinderImpl(
class ExceptionHandler<T : Any>(
private val errorPresenter: ErrorPresenter<T>,
private val errorEventsDispatcher: EventsDispatcher<ErrorEventListener<T>>,
private val onCatch: ((Throwable) -> Unit)? = null
) : ExceptionHandlerBinder by ExceptionHandlerBinderImpl<T>(
Comment thread
Alex009 marked this conversation as resolved.
Outdated
errorPresenter,
errorEventsDispatcher
) {

fun <T> handle(block: suspend () -> T): ExceptionHandlerContext<T> {
return ExceptionHandlerContext(errorEventsDispatcher, block)
fun <R> handle(block: suspend () -> R): ExceptionHandlerContext<T, R> {
return ExceptionHandlerContext(errorPresenter, errorEventsDispatcher, onCatch, block)
}
}
Comment thread
Alex009 marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
package dev.icerock.moko.errors.handler

import dev.icerock.moko.errors.ErrorEventListener
import dev.icerock.moko.errors.ErrorPresenter
import dev.icerock.moko.errors.presenters.ErrorPresenter
import dev.icerock.moko.mvvm.dispatcher.EventsDispatcher

expect interface ExceptionHandlerBinder

expect class ExceptionHandlerBinderImpl(
errorPresenter: ErrorPresenter,
eventsDispatcher: EventsDispatcher<ErrorEventListener>
expect class ExceptionHandlerBinderImpl<T : Any>(
errorPresenter: ErrorPresenter<T>,
eventsDispatcher: EventsDispatcher<ErrorEventListener<T>>
) : ExceptionHandlerBinder
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,42 @@ package dev.icerock.moko.errors.handler

import dev.icerock.moko.errors.ErrorEventListener
import dev.icerock.moko.errors.HandlerResult
import dev.icerock.moko.errors.presenters.ErrorPresenter
import dev.icerock.moko.mvvm.dispatcher.EventsDispatcher
import kotlin.reflect.KClass

typealias Catcher = (Throwable) -> Boolean

class ExceptionHandlerContext<T> internal constructor(
private val eventsDispatcher: EventsDispatcher<ErrorEventListener>,
private val block: suspend () -> T
class ExceptionHandlerContext<T : Any, R> internal constructor(
private val errorPresenter: ErrorPresenter<T>,
private val eventsDispatcher: EventsDispatcher<ErrorEventListener<T>>,
private val onCatch: ((Throwable) -> Unit)?,
private val block: suspend () -> R
) {
val catchersMap = mutableMapOf<KClass<*>, Catcher>()

private var finallyBlock: (() -> Unit)? = null

inline fun <reified E : Throwable> catch(
noinline catcher: (E) -> Boolean
): ExceptionHandlerContext<T> {
): ExceptionHandlerContext<T, R> {
catchersMap[E::class] = catcher as Catcher
return this
}

fun finally(block: () -> Unit): ExceptionHandlerContext<T> {
fun finally(block: () -> Unit): ExceptionHandlerContext<T, R> {
finallyBlock = block
return this
}

suspend fun execute(): HandlerResult<T, Throwable> {
suspend fun execute(): HandlerResult<R, Throwable> {
return try {
HandlerResult.Success(block())
} catch (e: Throwable) {
onCatch?.invoke(e)
val isHandled = catchersMap[e::class]?.invoke(e)
if (isHandled == null || isHandled == false) {
eventsDispatcher.dispatchEvent {
showError(e)
}
errorPresenter.sendErrorEvent(eventsDispatcher, e)
}

HandlerResult.Failure(e)
Expand Down
Loading