Skip to content

Commit 8440b0a

Browse files
committed
feat: add FlowUseCase
1 parent 67321d0 commit 8440b0a

File tree

7 files changed

+161
-18
lines changed

7 files changed

+161
-18
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package io.github.shinhyo.brba.app.di
2+
3+
import dagger.Module
4+
import dagger.Provides
5+
import dagger.hilt.InstallIn
6+
import dagger.hilt.components.SingletonComponent
7+
import io.github.shinhyo.brba.domain.di.DefaultDispatcher
8+
import io.github.shinhyo.brba.domain.di.IoDispatcher
9+
import io.github.shinhyo.brba.domain.di.MainDispatcher
10+
import io.github.shinhyo.brba.domain.di.MainImmediateDispatcher
11+
import kotlinx.coroutines.CoroutineDispatcher
12+
import kotlinx.coroutines.Dispatchers
13+
14+
@InstallIn(SingletonComponent::class)
15+
@Module
16+
internal object CoroutinesModule {
17+
@DefaultDispatcher
18+
@Provides
19+
fun providesDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default
20+
21+
@IoDispatcher
22+
@Provides
23+
fun providesIoDispatcher(): CoroutineDispatcher = Dispatchers.IO
24+
25+
@MainDispatcher
26+
@Provides
27+
fun providesMainDispatcher(): CoroutineDispatcher = Dispatchers.Main
28+
29+
@MainImmediateDispatcher
30+
@Provides
31+
fun providesMainImmediateDispatcher(): CoroutineDispatcher = Dispatchers.Main.immediate
32+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package io.github.shinhyo.brba.domain.di
2+
3+
import javax.inject.Qualifier
4+
5+
@Retention(AnnotationRetention.BINARY)
6+
@Qualifier
7+
annotation class DefaultDispatcher
8+
9+
@Retention(AnnotationRetention.BINARY)
10+
@Qualifier
11+
annotation class IoDispatcher
12+
13+
@Retention(AnnotationRetention.BINARY)
14+
@Qualifier
15+
annotation class MainDispatcher
16+
17+
@Retention(AnnotationRetention.BINARY)
18+
@Qualifier
19+
annotation class MainImmediateDispatcher
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package io.github.shinhyo.brba.domain.result
2+
3+
import io.github.shinhyo.brba.domain.result.Result.Success
4+
import kotlinx.coroutines.flow.Flow
5+
import kotlinx.coroutines.flow.map
6+
7+
/**
8+
* A generic class that holds a value with its loading status.
9+
* @param <T>
10+
*/
11+
sealed class Result<out R> {
12+
13+
data class Success<out T>(val data: T) : Result<T>()
14+
data class Error(val exception: Exception) : Result<Nothing>()
15+
16+
override fun toString(): String {
17+
return when (this) {
18+
is Success<*> -> "Success[data=$data]"
19+
is Error -> "Error[exception=$exception]"
20+
}
21+
}
22+
}
23+
24+
/**
25+
* `true` if [Result] is of type [Success] & holds non-null [Success.data].
26+
*/
27+
val Result<*>.succeeded
28+
get() = this is Success && data != null
29+
30+
fun <T> Result<T>.successOr(fallback: T): T {
31+
return (this as? Success<T>)?.data ?: fallback
32+
}
33+
34+
inline fun <R, T> Result<T>.mapTransform(transform: (T) -> R): Result<R> = when (this) {
35+
is Result.Success -> Result.Success(transform(data))
36+
is Result.Error -> Result.Error(exception)
37+
}
38+
39+
inline fun <R, T> Flow<Result<T>>.mapTransform(crossinline transform: (T) -> R): Flow<Result<R>> =
40+
this.map {
41+
when (it) {
42+
is Result.Success -> Result.Success(transform(it.data))
43+
is Result.Error -> Result.Error(it.exception)
44+
}
45+
}

domain/src/main/java/io/github/shinhyo/brba/domain/usecase/CharactersUseCase.kt

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,7 @@ package io.github.shinhyo.brba.domain.usecase
1818
import io.github.shinhyo.brba.domain.model.Character
1919
import io.github.shinhyo.brba.domain.repository.CharactersRepository
2020
import kotlinx.coroutines.flow.Flow
21-
import kotlinx.coroutines.flow.map
22-
import java.util.Calendar
2321
import javax.inject.Inject
24-
import kotlin.random.Random
25-
26-
class GetCharacterListUseCase @Inject constructor(
27-
private val repo: CharactersRepository
28-
) {
29-
private val random: Random by lazy { Random(Calendar.getInstance().timeInMillis) }
30-
31-
fun execute(): Flow<List<Character>> = repo.getCharacterList()
32-
.map { it.map { c -> c.copy(ratio = random.nextInt(4).let { r -> 1.4f + r * 0.15f }) } }
33-
}
3422

3523
class GetFavoriteListUseCase @Inject constructor(
3624
private val repo: CharactersRepository
@@ -44,12 +32,6 @@ class GetCharacterUseCase @Inject constructor(
4432
fun execute(id: Long): Flow<Character> = repo.getCharacterById(id)
4533
}
4634

47-
class AddFavoriteToListUseCase @Inject constructor(
48-
private val repo: CharactersRepository
49-
) {
50-
fun execute(list: List<Character>): Flow<List<Character>> = repo.addFavoriteStatus(list)
51-
}
52-
5335
class UpdateFavoriteUseCase @Inject constructor(
5436
private val repo: CharactersRepository
5537
) {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package io.github.shinhyo.brba.domain.usecase
2+
3+
import io.github.shinhyo.brba.domain.result.Result
4+
import kotlinx.coroutines.CoroutineDispatcher
5+
import kotlinx.coroutines.flow.Flow
6+
import kotlinx.coroutines.flow.catch
7+
import kotlinx.coroutines.flow.flowOn
8+
9+
/**
10+
* Executes business logic in its execute method and keep posting updates to the result as
11+
* [Result<R>].
12+
* Handling an exception (emit [Result.Error] to the result) is the subclasses's responsibility.
13+
*/
14+
abstract class FlowUseCase<in P, R>(private val coroutineDispatcher: CoroutineDispatcher) {
15+
operator fun invoke(parameters: P): Flow<Result<R>> = execute(parameters)
16+
.catch { e -> emit(Result.Error(Exception(e))) }
17+
.flowOn(coroutineDispatcher)
18+
19+
20+
protected abstract fun execute(parameters: P): Flow<Result<R>>
21+
}

presentation/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ dependencies {
2525
implementation(Dep.AndroidX.Compose.material)
2626
implementation(Dep.AndroidX.Compose.materialIcons)
2727

28+
implementation(Dep.AndroidX.Lifecycle.runtime)
2829
implementation(Dep.AndroidX.Lifecycle.viewModelCompose)
2930
implementation(Dep.AndroidX.Navigation.navigationCompose)
3031
implementation(Dep.AndroidX.ConstraintLayout.compose)
@@ -39,6 +40,8 @@ dependencies {
3940
implementation(Dep.AndroidX.Hilt.navigationFragment)
4041
implementation(Dep.AndroidX.Hilt.navigationCompose)
4142

43+
implementation(Dep.timber)
44+
4245
testImplementation(Dep.Test.junit)
4346
androidTestImplementation(Dep.Test.junitExt)
4447
androidTestImplementation(Dep.Test.espresso)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package io.github.shinhyo.brba.presentation.utils
2+
3+
import android.annotation.SuppressLint
4+
import androidx.compose.runtime.Composable
5+
import androidx.compose.runtime.State
6+
import androidx.compose.runtime.collectAsState
7+
import androidx.compose.runtime.remember
8+
import androidx.compose.ui.platform.LocalLifecycleOwner
9+
import androidx.lifecycle.Lifecycle
10+
import androidx.lifecycle.LifecycleOwner
11+
import androidx.lifecycle.flowWithLifecycle
12+
import kotlinx.coroutines.flow.Flow
13+
import kotlinx.coroutines.flow.StateFlow
14+
import kotlin.coroutines.CoroutineContext
15+
import kotlin.coroutines.EmptyCoroutineContext
16+
17+
@Composable
18+
fun <T> rememberFlow(
19+
flow: Flow<T>,
20+
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current
21+
): Flow<T> {
22+
return remember(
23+
key1 = flow,
24+
key2 = lifecycleOwner
25+
) { flow.flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED) }
26+
}
27+
28+
@Composable
29+
fun <T : R, R> Flow<T>.collectAsStateLifecycleAware(
30+
initial: R,
31+
context: CoroutineContext = EmptyCoroutineContext
32+
): State<R> {
33+
val lifecycleAwareFlow = rememberFlow(flow = this)
34+
return lifecycleAwareFlow.collectAsState(initial = initial, context = context)
35+
}
36+
37+
@SuppressLint("StateFlowValueCalledInComposition")
38+
@Composable
39+
fun <T> StateFlow<T>.collectAsStateLifecycleAware(
40+
context: CoroutineContext = EmptyCoroutineContext
41+
): State<T> = collectAsStateLifecycleAware(value, context)

0 commit comments

Comments
 (0)