Skip to content
This repository has been archived by the owner on Nov 12, 2024. It is now read-only.

Start scoping operations back to the ViewModel again #644

Merged
merged 3 commits into from
Jun 19, 2020
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,6 @@ dependencies {

implementation Libs.AndroidX.Lifecycle.livedata
implementation Libs.AndroidX.Lifecycle.viewmodel
implementation Libs.AndroidX.Lifecycle.process

implementation Libs.AndroidX.appcompat
implementation Libs.AndroidX.browser
Expand Down
19 changes: 15 additions & 4 deletions app/src/main/java/app/tivi/home/HomeActivityViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import app.tivi.trakt.TraktAuthState
import app.tivi.util.Logger
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch

class HomeActivityViewModel @ViewModelInject constructor(
observeTraktAuthState: ObserveTraktAuthState,
Expand All @@ -46,16 +47,26 @@ class HomeActivityViewModel @ViewModelInject constructor(
observeUserDetails(ObserveUserDetails.Params("me"))

viewModelScope.launchObserve(observeTraktAuthState) { flow ->
flow.distinctUntilChanged().onEach {
if (it == TraktAuthState.LOGGED_IN) {
updateUserDetails(UpdateUserDetails.Params("me", false))
flow.distinctUntilChanged()
.onEach {
if (it == TraktAuthState.LOGGED_IN) {
refreshMe()
}
}
.execute {
copy(authState = it() ?: TraktAuthState.LOGGED_OUT)
}
}.execute { copy(authState = it() ?: TraktAuthState.LOGGED_OUT) }
}
observeTraktAuthState()

selectSubscribe(HomeActivityViewState::user) { user ->
logger.setUserId(user?.username ?: "")
}
}

private fun refreshMe() {
viewModelScope.launch {
updateUserDetails.executeSync(UpdateUserDetails.Params("me", false))
}
}
}
9 changes: 0 additions & 9 deletions app/src/main/java/app/tivi/inject/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.coroutineScope
import androidx.navigation.ui.AppBarConfiguration
import androidx.preference.PreferenceManager
import app.tivi.BuildConfig
Expand All @@ -36,7 +34,6 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ApplicationComponent
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import org.threeten.bp.format.DateTimeFormatter
import org.threeten.bp.format.FormatStyle
Expand Down Expand Up @@ -142,12 +139,6 @@ object AppModule {
return DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withLocale(context)
}

@Provides
@ProcessLifetime
fun provideLongLifetimeScope(): CoroutineScope {
return ProcessLifecycleOwner.get().lifecycle.coroutineScope
}

@Provides
@Singleton
fun provideAppBarConfiguration() = AppBarConfiguration.Builder(
Expand Down
5 changes: 0 additions & 5 deletions base/src/main/java/app/tivi/inject/Annotations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,3 @@ annotation class ShortTime
@Qualifier
@MustBeDocumented
annotation class ApplicationId

@Retention(AnnotationRetention.RUNTIME)
@Qualifier
@MustBeDocumented
annotation class ProcessLifetime
1 change: 0 additions & 1 deletion buildSrc/src/main/java/app/tivi/buildsrc/dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,6 @@ object Libs {
private const val version = "2.2.0"
const val extensions = "androidx.lifecycle:lifecycle-extensions:$version"
const val livedata = "androidx.lifecycle:lifecycle-livedata-ktx:$version"
const val process = "androidx.lifecycle:lifecycle-process:$version"
const val viewmodel = "androidx.lifecycle:lifecycle-viewmodel-ktx:$version"
}

Expand Down
40 changes: 18 additions & 22 deletions common-entrygrid/src/main/java/app/tivi/util/EntryViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -105,21 +105,18 @@ abstract class EntryViewModel<LI : EntryWithShow<out Entry>, PI : PagingInteract
}

fun onListScrolledToEnd() {
callLoadMore().also {
viewModelScope.launch {
it.catch {
messages.send(UiError(it))
}.map {
viewModelScope.launch {
callLoadMore()
.catch { messages.send(UiError(it)) }
.map {
when (it) {
InvokeSuccess -> UiSuccess
InvokeStarted -> UiLoading(false)
is InvokeError -> UiError(it.throwable)
else -> UiIdle
}
}.collect {
messages.send(it)
}
}
.collect { messages.send(it) }
}
}

Expand All @@ -138,31 +135,30 @@ abstract class EntryViewModel<LI : EntryWithShow<out Entry>, PI : PagingInteract
}

fun followSelectedShows() {
changeShowFollowStatus(
ChangeShowFollowStatus.Params(
showSelection.getSelectedShowIds(),
ChangeShowFollowStatus.Action.FOLLOW,
deferDataFetch = true
viewModelScope.launch {
changeShowFollowStatus.executeSync(
ChangeShowFollowStatus.Params(
showSelection.getSelectedShowIds(),
ChangeShowFollowStatus.Action.FOLLOW,
deferDataFetch = true
)
)
)
}
showSelection.clearSelection()
}

protected fun refresh(fromUser: Boolean) {
callRefresh(fromUser).also {
viewModelScope.launch {
it.catch {
messages.send(UiError(it))
}.map {
viewModelScope.launch {
callRefresh(fromUser)
.catch { messages.send(UiError(it)) }
.map {
when (it) {
InvokeSuccess -> UiSuccess
InvokeStarted -> UiLoading(true)
else -> UiIdle
}
}.collect {
messages.send(it)
}
}
.collect { messages.send(it) }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@ class ObservableLoadingCounter {
}
}

suspend fun ObservableLoadingCounter.collectFrom(statuses: Flow<InvokeStatus>) {
statuses.collect {
suspend fun Flow<InvokeStatus>.collectInto(counter: ObservableLoadingCounter) {
collect {
if (it == InvokeStarted) {
addLoader()
counter.addLoader()
} else if (it == InvokeSuccess || it is InvokeError) {
removeLoader()
counter.removeLoader()
}
}
}
25 changes: 9 additions & 16 deletions domain/src/main/java/app/tivi/domain/Interactor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package app.tivi.domain

import androidx.paging.PagedList
import app.tivi.base.InvokeError
import app.tivi.base.InvokeIdle
import app.tivi.base.InvokeStarted
import app.tivi.base.InvokeStatus
import app.tivi.base.InvokeSuccess
Expand All @@ -32,36 +31,30 @@ import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
import java.util.concurrent.TimeUnit

abstract class Interactor<in P> {
protected abstract val scope: CoroutineScope

operator fun invoke(params: P, timeoutMs: Long = defaultTimeoutMs): Flow<InvokeStatus> {
val channel = ConflatedBroadcastChannel<InvokeStatus>(InvokeIdle)
scope.launch {
return flow {
try {
withTimeout(timeoutMs) {
channel.send(InvokeStarted)
emit(InvokeStarted)
try {
doWork(params)
channel.send(InvokeSuccess)
emit(InvokeSuccess)
} catch (t: Throwable) {
channel.send(InvokeError(t))
emit(InvokeError(t))
}
}
} catch (t: TimeoutCancellationException) {
channel.send(InvokeError(t))
emit(InvokeError(t))
}
}
return channel.asFlow()
}

suspend fun executeSync(params: P) = withContext(scope.coroutineContext) { doWork(params) }
suspend fun executeSync(params: P) = doWork(params)

protected abstract suspend fun doWork(params: P)

Expand All @@ -71,10 +64,10 @@ abstract class Interactor<in P> {
}

abstract class ResultInteractor<in P, R> {
abstract val dispatcher: CoroutineDispatcher

operator fun invoke(params: P): Flow<R> {
return flow { emit(doWork(params)) }.flowOn(dispatcher)
return flow {
emit(doWork(params))
}
}

protected abstract suspend fun doWork(params: P): R
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,21 @@ package app.tivi.domain.interactors

import app.tivi.data.repositories.episodes.SeasonsEpisodesRepository
import app.tivi.domain.Interactor
import app.tivi.inject.ProcessLifetime
import app.tivi.util.AppCoroutineDispatchers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.plus
import kotlinx.coroutines.withContext
import org.threeten.bp.OffsetDateTime
import javax.inject.Inject

class AddEpisodeWatch @Inject constructor(
private val seasonsEpisodesRepository: SeasonsEpisodesRepository,
dispatchers: AppCoroutineDispatchers,
@ProcessLifetime val processScope: CoroutineScope
) : Interactor<AddEpisodeWatch.Params>() {
override val scope: CoroutineScope = processScope + dispatchers.io
private val dispatchers: AppCoroutineDispatchers

) : Interactor<AddEpisodeWatch.Params>() {
override suspend fun doWork(params: Params) {
seasonsEpisodesRepository.addEpisodeWatch(params.episodeId, params.timestamp)
withContext(dispatchers.io) {
seasonsEpisodesRepository.addEpisodeWatch(params.episodeId, params.timestamp)
}
}

data class Params(val episodeId: Long, val timestamp: OffsetDateTime)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,28 @@ package app.tivi.domain.interactors

import app.tivi.data.repositories.episodes.SeasonsEpisodesRepository
import app.tivi.domain.Interactor
import app.tivi.inject.ProcessLifetime
import app.tivi.util.AppCoroutineDispatchers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.plus
import kotlinx.coroutines.withContext
import javax.inject.Inject

class ChangeSeasonFollowStatus @Inject constructor(
private val seasonsEpisodesRepository: SeasonsEpisodesRepository,
dispatchers: AppCoroutineDispatchers,
@ProcessLifetime val processScope: CoroutineScope
private val dispatchers: AppCoroutineDispatchers
) : Interactor<ChangeSeasonFollowStatus.Params>() {
override val scope: CoroutineScope = processScope + dispatchers.io

override suspend fun doWork(params: Params) = when (params.action) {
Action.FOLLOW -> {
seasonsEpisodesRepository.markSeasonFollowed(params.seasonId)
}
Action.IGNORE -> {
seasonsEpisodesRepository.markSeasonIgnored(params.seasonId)
}
Action.IGNORE_PREVIOUS -> {
seasonsEpisodesRepository.markPreviousSeasonsIgnored(params.seasonId)
override suspend fun doWork(params: Params) {
return withContext(dispatchers.io) {
when (params.action) {
Action.FOLLOW -> {
seasonsEpisodesRepository.markSeasonFollowed(params.seasonId)
}
Action.IGNORE -> {
seasonsEpisodesRepository.markSeasonIgnored(params.seasonId)
}
Action.IGNORE_PREVIOUS -> {
seasonsEpisodesRepository.markPreviousSeasonsIgnored(params.seasonId)
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,30 @@ package app.tivi.domain.interactors
import app.tivi.data.entities.ActionDate
import app.tivi.data.repositories.episodes.SeasonsEpisodesRepository
import app.tivi.domain.Interactor
import app.tivi.inject.ProcessLifetime
import app.tivi.util.AppCoroutineDispatchers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.plus
import kotlinx.coroutines.withContext
import javax.inject.Inject

class ChangeSeasonWatchedStatus @Inject constructor(
private val seasonsEpisodesRepository: SeasonsEpisodesRepository,
dispatchers: AppCoroutineDispatchers,
@ProcessLifetime val processScope: CoroutineScope
private val dispatchers: AppCoroutineDispatchers
) : Interactor<ChangeSeasonWatchedStatus.Params>() {
override val scope: CoroutineScope = processScope + dispatchers.io

override suspend fun doWork(params: Params) = when (params.action) {
Action.WATCHED -> {
seasonsEpisodesRepository.markSeasonWatched(
params.seasonId,
params.onlyAired,
params.actionDate
)
}
Action.UNWATCH -> {
seasonsEpisodesRepository.markSeasonUnwatched(params.seasonId)
override suspend fun doWork(params: Params) {
return withContext(dispatchers.io) {
when (params.action) {
Action.WATCHED -> {
seasonsEpisodesRepository.markSeasonWatched(
params.seasonId,
params.onlyAired,
params.actionDate
)
}
Action.UNWATCH -> {
seasonsEpisodesRepository.markSeasonUnwatched(params.seasonId)
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,20 @@ import app.tivi.data.repositories.followedshows.FollowedShowsRepository
import app.tivi.data.repositories.showimages.ShowImagesStore
import app.tivi.data.repositories.shows.ShowStore
import app.tivi.domain.Interactor
import app.tivi.inject.ProcessLifetime
import app.tivi.util.AppCoroutineDispatchers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.plus
import kotlinx.coroutines.withContext
import javax.inject.Inject

class ChangeShowFollowStatus @Inject constructor(
private val followedShowsRepository: FollowedShowsRepository,
private val seasonsEpisodesRepository: SeasonsEpisodesRepository,
private val showStore: ShowStore,
private val showImagesStore: ShowImagesStore,
dispatchers: AppCoroutineDispatchers,
private val showTasks: ShowTasks,
@ProcessLifetime val processScope: CoroutineScope
private val dispatchers: AppCoroutineDispatchers,
private val showTasks: ShowTasks
) : Interactor<ChangeShowFollowStatus.Params>() {
override val scope: CoroutineScope = processScope + dispatchers.io

override suspend fun doWork(params: Params) {
override suspend fun doWork(params: Params) = withContext(dispatchers.io) {
params.showIds.forEach { showId ->
when (params.action) {
Action.TOGGLE -> {
Expand Down
Loading