Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tidy up Interactor class #1291

Merged
merged 4 commits into from
May 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

package app.tivi.appinitializers

import app.tivi.domain.executeSync
import app.tivi.domain.interactors.UpdateTmdbConfig
import app.tivi.domain.invoke
import app.tivi.util.AppCoroutineDispatchers
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
Expand All @@ -19,7 +19,7 @@ class TmdbInitializer(
override fun init() {
@OptIn(DelicateCoroutinesApi::class)
GlobalScope.launch(dispatchers.main) {
updateTmdbConfig.executeSync()
updateTmdbConfig.invoke()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ package app.tivi.home
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.tivi.data.traktauth.TraktAuthState
import app.tivi.domain.executeSync
import app.tivi.domain.interactors.ClearTraktAuthState
import app.tivi.domain.interactors.UpdateUserDetails
import app.tivi.domain.invoke
import app.tivi.domain.observers.ObserveTraktAuthState
import app.tivi.domain.observers.ObserveUserDetails
import app.tivi.util.Logger
Expand Down Expand Up @@ -45,11 +45,11 @@ class MainActivityViewModel(
private fun refreshMe() {
viewModelScope.launch {
try {
updateUserDetails.executeSync(UpdateUserDetails.Params("me", false))
updateUserDetails(UpdateUserDetails.Params("me", false))
} catch (e: ResponseException) {
if (e.response.status == HttpStatusCode.Unauthorized) {
// If we got a 401 back from Trakt, we should clear out the auth state
clearTraktAuthState.executeSync()
clearTraktAuthState.invoke()
}
} catch (ce: CancellationException) {
throw ce
Expand Down

This file was deleted.

79 changes: 36 additions & 43 deletions domain/src/commonMain/kotlin/app/tivi/domain/Interactor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,74 +5,67 @@ package app.tivi.domain

import app.cash.paging.PagingConfig
import app.cash.paging.PagingData
import app.tivi.base.InvokeError
import app.tivi.base.InvokeStarted
import app.tivi.base.InvokeStatus
import app.tivi.base.InvokeSuccess
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.TimeoutCancellationException
import java.util.concurrent.atomic.AtomicInteger
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withTimeout

abstract class Interactor<in P> {
operator fun invoke(
params: P,
timeoutMs: Long = defaultTimeoutMs,
): Flow<InvokeStatus> = flow {
try {
withTimeout(timeoutMs) {
emit(InvokeStarted)
doWork(params)
emit(InvokeSuccess)
}
} catch (t: TimeoutCancellationException) {
emit(InvokeError(t))
}
}.catch { t -> emit(InvokeError(t)) }
abstract class Interactor<in P, R> {
private val count = AtomicInteger()
private val loadingState = MutableStateFlow(count.get())

suspend fun executeSync(params: P) = doWork(params)
val inProgress: Flow<Boolean>
get() = loadingState.map { it > 0 }.distinctUntilChanged()

protected abstract suspend fun doWork(params: P)

companion object {
private val defaultTimeoutMs = TimeUnit.MINUTES.toMillis(5)
private fun addLoader() {
loadingState.value = count.incrementAndGet()
}
}

suspend inline fun Interactor<Unit>.executeSync() = executeSync(Unit)

abstract class ResultInteractor<in P, R> {
operator fun invoke(params: P): Flow<R> = flow {
emit(doWork(params))
private fun removeLoader() {
loadingState.value = count.decrementAndGet()
}

suspend fun executeSync(params: P): R = doWork(params)
suspend operator fun invoke(
params: P,
timeoutMs: Long = DefaultTimeoutMs,
): Result<R> {
return try {
addLoader()
runCatching {
withTimeout(timeoutMs) {
doWork(params)
}
}
} finally {
removeLoader()
}
}

protected abstract suspend fun doWork(params: P): R

companion object {
internal val DefaultTimeoutMs = TimeUnit.MINUTES.toMillis(5)
}
}

suspend inline fun <R> ResultInteractor<Unit, R>.executeSync(): R = executeSync(Unit)
suspend fun <R> Interactor<Unit, R>.invoke(
timeoutMs: Long = Interactor.DefaultTimeoutMs,
) = invoke(Unit, timeoutMs)

abstract class PagingInteractor<P : PagingInteractor.Parameters<T>, T : Any> : SubjectInteractor<P, PagingData<T>>() {
interface Parameters<T : Any> {
val pagingConfig: PagingConfig
}
}

abstract class SuspendingWorkInteractor<P : Any, T> : SubjectInteractor<P, T>() {
override fun createObservable(params: P): Flow<T> = flow {
emit(doWork(params))
}

abstract suspend fun doWork(params: P): T
}

@OptIn(ExperimentalCoroutinesApi::class)
abstract class SubjectInteractor<P : Any, T> {
// Ideally this would be buffer = 0, since we use flatMapLatest below, BUT invoke is not
// suspending. This means that we can't suspend while flatMapLatest cancels any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import me.tatarka.inject.annotations.Inject
class AddEpisodeWatch(
private val seasonsEpisodesRepository: SeasonsEpisodesRepository,
private val dispatchers: AppCoroutineDispatchers,
) : Interactor<AddEpisodeWatch.Params>() {
) : Interactor<AddEpisodeWatch.Params, Unit>() {
override suspend fun doWork(params: Params) {
withContext(dispatchers.io) {
seasonsEpisodesRepository.addEpisodeWatch(params.episodeId, params.timestamp)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import me.tatarka.inject.annotations.Inject
class ChangeSeasonFollowStatus(
private val seasonsEpisodesRepository: SeasonsEpisodesRepository,
private val dispatchers: AppCoroutineDispatchers,
) : Interactor<ChangeSeasonFollowStatus.Params>() {
) : Interactor<ChangeSeasonFollowStatus.Params, Unit>() {
override suspend fun doWork(params: Params) {
withContext(dispatchers.io) {
when (params.action) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import me.tatarka.inject.annotations.Inject
class ChangeSeasonWatchedStatus(
private val seasonsEpisodesRepository: SeasonsEpisodesRepository,
private val dispatchers: AppCoroutineDispatchers,
) : Interactor<ChangeSeasonWatchedStatus.Params>() {
) : Interactor<ChangeSeasonWatchedStatus.Params, Unit>() {

override suspend fun doWork(params: Params) {
withContext(dispatchers.io) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class ChangeShowFollowStatus(
private val followedShowsRepository: FollowedShowsRepository,
private val showStore: ShowStore,
private val dispatchers: AppCoroutineDispatchers,
) : Interactor<ChangeShowFollowStatus.Params>() {
) : Interactor<ChangeShowFollowStatus.Params, Unit>() {
override suspend fun doWork(params: Params) {
withContext(dispatchers.io) {
params.showIds.forEach { showId ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
package app.tivi.domain.interactors

import app.tivi.data.traktauth.TraktAuthRepository
import app.tivi.domain.ResultInteractor
import app.tivi.domain.Interactor
import me.tatarka.inject.annotations.Inject

@Inject
class ClearTraktAuthState(
private val traktAuthRepository: TraktAuthRepository,
) : ResultInteractor<Unit, Unit>() {
) : Interactor<Unit, Unit>() {
override suspend fun doWork(params: Unit) {
traktAuthRepository.clearAuth()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import me.tatarka.inject.annotations.Inject
class ClearUserDetails(
private val userDao: UserDao,
private val dispatchers: AppCoroutineDispatchers,
) : Interactor<ClearUserDetails.Params>() {
) : Interactor<ClearUserDetails.Params, Unit>() {
override suspend fun doWork(params: Params) {
withContext(dispatchers.io) {
when (params.username) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import me.tatarka.inject.annotations.Inject
@Inject
class DeleteFolder(
private val dispatchers: AppCoroutineDispatchers,
) : Interactor<DeleteFolder.Params>() {
) : Interactor<DeleteFolder.Params, Unit>() {
override suspend fun doWork(params: Params) {
withContext(dispatchers.io) {
if (params.directory.exists()) {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ package app.tivi.domain.interactors

import app.tivi.data.traktauth.TraktAuthRepository
import app.tivi.data.traktauth.TraktAuthState
import app.tivi.domain.ResultInteractor
import app.tivi.domain.Interactor
import me.tatarka.inject.annotations.Inject

@Inject
class GetTraktAuthState(
private val traktAuthRepository: TraktAuthRepository,
) : ResultInteractor<Unit, TraktAuthState>() {
) : Interactor<Unit, TraktAuthState>() {
override suspend fun doWork(params: Unit): TraktAuthState {
return traktAuthRepository.state.value
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import me.tatarka.inject.annotations.Inject
class RemoveEpisodeWatch(
private val seasonsEpisodesRepository: SeasonsEpisodesRepository,
private val dispatchers: AppCoroutineDispatchers,
) : Interactor<RemoveEpisodeWatch.Params>() {
) : Interactor<RemoveEpisodeWatch.Params, Unit>() {
override suspend fun doWork(params: Params) {
withContext(dispatchers.io) {
seasonsEpisodesRepository.removeEpisodeWatch(params.episodeWatchId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import me.tatarka.inject.annotations.Inject
class RemoveEpisodeWatches(
private val seasonsEpisodesRepository: SeasonsEpisodesRepository,
private val dispatchers: AppCoroutineDispatchers,
) : Interactor<RemoveEpisodeWatches.Params>() {
) : Interactor<RemoveEpisodeWatches.Params, Unit>() {
override suspend fun doWork(params: Params) {
withContext(dispatchers.io) {
seasonsEpisodesRepository.removeAllEpisodeWatches(params.episodeId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ package app.tivi.domain.interactors
import app.tivi.data.daos.TiviShowDao
import app.tivi.data.models.TiviShow
import app.tivi.data.search.SearchRepository
import app.tivi.domain.SuspendingWorkInteractor
import app.tivi.domain.Interactor
import app.tivi.util.AppCoroutineDispatchers
import kotlinx.coroutines.withContext
import me.tatarka.inject.annotations.Inject
Expand All @@ -16,7 +16,7 @@ class SearchShows(
private val searchRepository: SearchRepository,
private val showDao: TiviShowDao,
private val dispatchers: AppCoroutineDispatchers,
) : SuspendingWorkInteractor<SearchShows.Params, List<TiviShow>>() {
) : Interactor<SearchShows.Params, List<TiviShow>>() {
override suspend fun doWork(params: Params): List<TiviShow> = withContext(dispatchers.io) {
val remoteResults = searchRepository.search(params.query)
when {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import me.tatarka.inject.annotations.Inject
class UpdateEpisodeDetails(
private val seasonsEpisodesRepository: SeasonsEpisodesRepository,
private val dispatchers: AppCoroutineDispatchers,
) : Interactor<UpdateEpisodeDetails.Params>() {
) : Interactor<UpdateEpisodeDetails.Params, Unit>() {

override suspend fun doWork(params: Params) {
withContext(dispatchers.io) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class UpdateLibraryShows(
private val watchedShowDao: WatchedShowDao,
private val logger: Logger,
private val dispatchers: AppCoroutineDispatchers,
) : Interactor<UpdateLibraryShows.Params>() {
) : Interactor<UpdateLibraryShows.Params, Unit>() {

override suspend fun doWork(params: Params): Unit = withContext(dispatchers.io) {
val watchedShowsDeferred = async {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class UpdatePopularShows(
private val popularDao: PopularDao,
private val showStore: ShowStore,
private val dispatchers: AppCoroutineDispatchers,
) : Interactor<Params>() {
) : Interactor<Params, Unit>() {
override suspend fun doWork(params: Params) {
withContext(dispatchers.io) {
val page = when {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class UpdateRecommendedShows(
private val dispatchers: AppCoroutineDispatchers,
private val traktAuthRepository: TraktAuthRepository,
private val logger: Logger,
) : Interactor<Params>() {
) : Interactor<Params, Unit>() {
override suspend fun doWork(params: Params) {
// If we're not logged in, we can't load the recommended shows
if (traktAuthRepository.state.value != TraktAuthState.LOGGED_IN) return
Expand Down