Skip to content

Commit 0d050c5

Browse files
committed
an attempt at UseCase error handling with Flow extensions
1 parent f20c4f5 commit 0d050c5

File tree

6 files changed

+80
-14
lines changed

6 files changed

+80
-14
lines changed

app/src/main/java/com/monstarlab/arch/data/RepositoryExtensions.kt renamed to app/src/main/java/com/monstarlab/arch/extensions/RepositoryExtensions.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.monstarlab.arch.data
1+
package com.monstarlab.arch.extensions
22

33
import com.monstarlab.core.domain.error.ErrorModel
44
import com.monstarlab.core.domain.error.toError
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.monstarlab.arch.extensions
2+
3+
import com.monstarlab.core.domain.error.ErrorModel
4+
import com.monstarlab.core.domain.error.toError
5+
import kotlinx.coroutines.flow.Flow
6+
import kotlinx.coroutines.flow.flow
7+
import kotlinx.coroutines.flow.transform
8+
9+
10+
sealed class UseCaseResult<out T> {
11+
data class Success<T>(val value: T) : UseCaseResult<T>()
12+
data class Error(val error: ErrorModel) : UseCaseResult<Nothing>()
13+
}
14+
15+
suspend inline fun <T> safeUseCase(
16+
crossinline block: suspend () -> RepositoryResult<T>
17+
): UseCaseResult<T> = try {
18+
when (val repoResult = block()) {
19+
is RepositoryResult.Success -> UseCaseResult.Success(repoResult.value)
20+
is RepositoryResult.Error -> UseCaseResult.Error(repoResult.error)
21+
}
22+
} catch (e: Exception) {
23+
UseCaseResult.Error(e.toError())
24+
}
25+
26+
inline fun <T> safeFlow(
27+
crossinline block: suspend () -> RepositoryResult<T>
28+
): Flow<UseCaseResult<T>> = flow {
29+
try {
30+
when (val repoResult = block()) {
31+
is RepositoryResult.Success -> UseCaseResult.Success(repoResult.value)
32+
is RepositoryResult.Error -> UseCaseResult.Error(repoResult.error)
33+
}
34+
} catch (e: Exception) {
35+
UseCaseResult.Error(e.toError())
36+
}
37+
}
38+
39+
fun <T> Flow<UseCaseResult<T>>.onSuccess(action: suspend (T) -> Unit): Flow<UseCaseResult<T>> = transform { result ->
40+
if(result is UseCaseResult.Success<T>) {
41+
action(result.value)
42+
}
43+
return@transform emit(result)
44+
}
45+
46+
fun <T> Flow<UseCaseResult<T>>.onError(action: suspend (ErrorModel) -> Unit): Flow<UseCaseResult<T>> = transform { result ->
47+
if(result is UseCaseResult.Error) {
48+
action(result.error)
49+
}
50+
return@transform emit(result)
51+
}

app/src/main/java/com/monstarlab/core/domain/error/ErrorMapping.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,6 @@ fun Throwable.toError(): ErrorModel {
2626
is SocketTimeoutException -> ErrorModel.Connection.Timeout
2727
is UnknownHostException -> ErrorModel.Connection.UnknownHost
2828
is IOException -> ErrorModel.Connection.IOError
29-
else -> ErrorModel.Unknown
29+
else -> ErrorModel.Unknown(this)
3030
}
3131
}

app/src/main/java/com/monstarlab/core/domain/error/ErrorModel.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,6 @@ sealed class ErrorModel {
2424
object UnknownHost: Connection()
2525
}
2626

27-
object Unknown: ErrorModel()
27+
data class Unknown(val throwable: Throwable): ErrorModel()
2828

2929
}
Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package com.monstarlab.core.usecases.blog
22

3+
import com.monstarlab.arch.extensions.UseCaseResult
4+
import com.monstarlab.arch.extensions.safeCall
5+
import com.monstarlab.arch.extensions.safeFlow
6+
import com.monstarlab.arch.extensions.safeUseCase
37
import com.monstarlab.core.data.repositories.PostRepository
48
import com.monstarlab.core.domain.model.Post
59
import kotlinx.coroutines.flow.Flow
@@ -8,9 +12,7 @@ import javax.inject.Inject
812
class GetPostsUseCase @Inject constructor(
913
private val postRepository: PostRepository
1014
){
11-
12-
fun getPosts(): Flow<List<Post>> {
13-
return postRepository.getPosts()
15+
fun getPosts(): Flow<UseCaseResult<List<Post>>> = safeFlow {
16+
postRepository.getPosts()
1417
}
15-
1618
}

app/src/main/java/com/monstarlab/features/sample/SampleViewModel.kt

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@ import androidx.lifecycle.SavedStateHandle
44
import androidx.lifecycle.ViewModel
55
import androidx.lifecycle.viewModelScope
66
import com.monstarlab.arch.extensions.combineFlows
7+
import com.monstarlab.arch.extensions.onError
8+
import com.monstarlab.arch.extensions.onSuccess
79
import com.monstarlab.core.data.repositories.PostRepository
10+
import com.monstarlab.core.domain.error.ErrorModel
811
import com.monstarlab.core.domain.mock.MockFlows
912
import com.monstarlab.core.domain.mock.MockSuspends
13+
import com.monstarlab.core.usecases.blog.GetPostsUseCase
1014
import kotlinx.coroutines.Dispatchers
1115
import kotlinx.coroutines.flow.*
1216
import kotlinx.coroutines.launch
@@ -15,24 +19,33 @@ import java.io.IOException
1519
import javax.inject.Inject
1620

1721
class SampleViewModel @Inject constructor(
18-
private val postRepository: PostRepository
22+
private val getPostsUseCase: GetPostsUseCase
1923
): ViewModel() {
2024

2125
val clickFlow: MutableStateFlow<Int> = MutableStateFlow(0)
2226
val textFlow: MutableStateFlow<String> = MutableStateFlow("Nothing yet")
23-
val errorFlow = MutableSharedFlow<String>()
27+
val errorFlow = MutableSharedFlow<ErrorModel>()
2428

2529
fun clickedButton() {
2630
clickFlow.value++
2731
}
2832

2933
fun fetchBlogPosts() {
30-
postRepository
31-
.getPosts()
34+
35+
getPostsUseCase.getPosts()
36+
.onStart { /* start loading */ }
37+
.onSuccess { textFlow.value = "Found ${it.size}" }
38+
.onError { errorFlow.emit(it) }
39+
.onCompletion { /* stop loading */ }
40+
.launchIn(viewModelScope)
41+
42+
getPostsUseCase.getPosts()
3243
.onStart { /* start loading */ }
33-
.onEach { blogEntries -> textFlow.value = "Found ${blogEntries.size}" }
34-
.retryWhen { cause, attempt -> cause is IOException && attempt <= 2 }
35-
.catch { _ -> errorFlow.emit("Something went wrong") }
44+
.onSuccess { textFlow.value = "Found ${it.size}" }
45+
.onError { errorFlow.emit(it) }
46+
//.onEach { blogEntries -> textFlow.value = "Found ${blogEntries.size}" }
47+
//.retryWhen { cause, attempt -> cause is IOException && attempt <= 2 }
48+
//.catch { _ -> errorFlow.emit("Something went wrong") }
3649
.onCompletion { /* stop loading */ }
3750
.launchIn(viewModelScope)
3851
}

0 commit comments

Comments
 (0)