Skip to content

Commit

Permalink
init store on home screen
Browse files Browse the repository at this point in the history
  • Loading branch information
stslex committed Aug 30, 2023
1 parent bc2269c commit a7a6ac9
Show file tree
Hide file tree
Showing 11 changed files with 195 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,28 @@ package com.stslex.aproselection.core.ui.base

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingData
import androidx.paging.cachedIn
import androidx.paging.map
import com.stslex.aproselection.core.core.Logger
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import com.stslex.aproselection.core.ui.base.store.Store
import com.stslex.aproselection.core.ui.base.store.Store.Action
import com.stslex.aproselection.core.ui.base.store.Store.Event
import com.stslex.aproselection.core.ui.base.store.Store.State
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn

open class BaseViewModel : ViewModel() {
open class BaseViewModel<out S : State, out E : Event, in A : Action>(
private val store: Store<S, E, A>
) : ViewModel() {

inline fun <T : Any, R : Any> Pager<Int, T>.mapState(
crossinline transform: suspend (T) -> R
): StateFlow<PagingData<R>> = this
.flow
.map { pagingData ->
pagingData.map { item ->
transform(item)
}
}
.primaryPagingFlow
val state: StateFlow<S> = store.state
val event: SharedFlow<E> = store.event

fun <T> Flow<T>.stateIn(
initialValue: T
): StateFlow<T> = this.stateIn(
scope = viewModelScope,
started = SharingStarted.Lazily,
initialValue = initialValue
)

val <T : Any> Flow<PagingData<T>>.primaryPagingFlow: StateFlow<PagingData<T>>
get() = cachedIn(viewModelScope)
.makeStateFlow(PagingData.empty())
init {
store.init(viewModelScope)
}

private fun <T : Any> Flow<T>.makeStateFlow(initialValue: T): StateFlow<T> =
flowOn(Dispatchers.IO)
.stateIn(
initialValue = initialValue
)
fun sendAction(action: A) {
store.processAction(action)
}

fun handleError(throwable: Throwable) {
Logger.exception(throwable)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.stslex.aproselection.core.ui.base.store

import com.stslex.aproselection.core.ui.base.store.Store.Action
import com.stslex.aproselection.core.ui.base.store.Store.Event
import com.stslex.aproselection.core.ui.base.store.Store.State
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch

abstract class BaseStoreImpl<S : State, E : Event, A : Action> :
Store<S, E, A>,
StoreImpl<S, E, A> {

private var _scope: CoroutineScope? = null
val scope: CoroutineScope
get() = requireNotNull(_scope)

@Suppress("LeakingThis")
override val state: MutableStateFlow<S> = MutableStateFlow(initialState)
override val event: MutableSharedFlow<E> = MutableSharedFlow()

override fun updateState(update: (S) -> S) {
state.update(update)
}

override fun sendEvent(event: E) {
scope.launch {
this@BaseStoreImpl.event.emit(event)
}
}

override fun init(scope: CoroutineScope) {
_scope = scope
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.stslex.aproselection.core.ui.base.store

import com.stslex.aproselection.core.ui.base.store.Store.Action
import com.stslex.aproselection.core.ui.base.store.Store.Event
import com.stslex.aproselection.core.ui.base.store.Store.State
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow

interface Store<out S : State, out E : Event, in A : Action> {

val state: StateFlow<S>
val event: SharedFlow<E>

fun processAction(action: A)

fun init(scope: CoroutineScope)

interface State
interface Event
interface Action
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.stslex.aproselection.core.ui.base.store

import com.stslex.aproselection.core.ui.base.store.Store.Action
import com.stslex.aproselection.core.ui.base.store.Store.Event
import com.stslex.aproselection.core.ui.base.store.Store.State

internal interface StoreImpl<S : State, in E : Event, A : Action> {

val initialState: S

fun sendEvent(event: E)

fun updateState(update: (S) -> S)
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,11 @@ import com.stslex.aproselection.feature.home.ui.model.UserUi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlin.reflect.KProperty0

@Composable
fun HomeScreen(
logOut: () -> Unit,
users: KProperty0<StateFlow<PagingData<UserUi>>>,
users: () -> StateFlow<PagingData<UserUi>>,
modifier: Modifier = Modifier
) {
var isUsersOpen by remember {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,11 @@
package com.stslex.aproselection.feature.home.ui

import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import com.stslex.aproselection.core.ui.base.BasePager.makePager
import com.stslex.aproselection.core.ui.base.BaseViewModel
import com.stslex.aproselection.feature.home.domain.HomeInteractor
import com.stslex.aproselection.feature.home.ui.model.UserUi
import com.stslex.aproselection.feature.home.ui.model.toPresentation
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import com.stslex.aproselection.feature.home.ui.store.HomeScreenStore
import com.stslex.aproselection.feature.home.ui.store.HomeScreenStore.Action
import com.stslex.aproselection.feature.home.ui.store.HomeScreenStore.Event
import com.stslex.aproselection.feature.home.ui.store.HomeScreenStore.State

class HomeViewModel(
private val interactor: HomeInteractor
) : BaseViewModel() {

val users: StateFlow<PagingData<UserUi>>
get() = makePager(interactor::getAllUsers)
.mapState { user -> user.toPresentation() }

fun logOut() {
viewModelScope.launch {
interactor.logOut()
}
}
}
private val store: HomeScreenStore,
) : BaseViewModel<State, Event, Action>(store)
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
package com.stslex.aproselection.feature.home.ui.navigation

import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import com.stslex.aproselection.core.navigation.destination.AppDestination
import com.stslex.aproselection.feature.home.ui.HomeScreen
import com.stslex.aproselection.feature.home.ui.HomeViewModel
import com.stslex.aproselection.feature.home.ui.store.HomeScreenStore.Action
import org.koin.androidx.compose.koinViewModel

fun NavGraphBuilder.homeRouter(
modifier: Modifier = Modifier,
) {
composable(
route = com.stslex.aproselection.core.navigation.destination.AppDestination.HOME.navigationRoute
route = AppDestination.HOME.navigationRoute
) {
val viewModel: HomeViewModel = koinViewModel()

val state by remember {
viewModel.state
}.collectAsState()

HomeScreen(
modifier = modifier,
logOut = viewModel::logOut,
users = remember { viewModel::users }
logOut = remember {
{ viewModel.sendAction(Action.LogOut) }
},
users = remember { state.users }
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.stslex.aproselection.feature.home.ui.store

import androidx.compose.runtime.Stable
import androidx.paging.PagingData
import com.stslex.aproselection.core.ui.base.store.Store
import com.stslex.aproselection.feature.home.ui.model.UserUi
import com.stslex.aproselection.feature.home.ui.store.HomeScreenStore.Action
import com.stslex.aproselection.feature.home.ui.store.HomeScreenStore.Event
import com.stslex.aproselection.feature.home.ui.store.HomeScreenStore.State
import kotlinx.coroutines.flow.StateFlow

interface HomeScreenStore : Store<State, Event, Action> {

@Stable
data class State(
val users: () -> StateFlow<PagingData<UserUi>>
) : Store.State

@Stable
sealed interface Action : Store.Action {

data object LogOut : Action
}

@Stable
sealed interface Event : Store.Event
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.stslex.aproselection.feature.home.ui.store

import androidx.paging.PagingData
import androidx.paging.cachedIn
import androidx.paging.map
import com.stslex.aproselection.core.ui.base.BasePager
import com.stslex.aproselection.core.ui.base.store.BaseStoreImpl
import com.stslex.aproselection.feature.home.domain.HomeInteractor
import com.stslex.aproselection.feature.home.ui.model.toPresentation
import com.stslex.aproselection.feature.home.ui.store.HomeScreenStore.Action
import com.stslex.aproselection.feature.home.ui.store.HomeScreenStore.Event
import com.stslex.aproselection.feature.home.ui.store.HomeScreenStore.State
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch

class HomeScreenStoreImpl(
private val interactor: HomeInteractor,
) : HomeScreenStore, BaseStoreImpl<State, Event, Action>() {


override val initialState: State = State(
users = { users }
)

private val users = BasePager
.makePager(interactor::getAllUsers)
.flow
.map { pagingData ->
pagingData.map { user ->
user.toPresentation()
}
}
.flowOn(Dispatchers.IO)
.cachedIn(scope)
.stateIn(
scope = scope,
started = SharingStarted.Lazily,
initialValue = PagingData.empty()
)

override fun processAction(action: Action) {
when (action) {
Action.LogOut -> onLogOutAction()
}
}

private fun onLogOutAction() {
scope.launch(Dispatchers.IO) {
interactor.logOut()
}
}
}
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
androidDesugarJdkLibs = "2.0.3"
kotlin = "1.9.0"
kotlinLanguage = "1.9"
androidGradlePlugin = "8.1.0"
androidGradlePlugin = "8.1.1"

ktx = "1.10.1"
material = "1.9.0"
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Sun Jul 23 09:02:27 MSK 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-rc-2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

0 comments on commit a7a6ac9

Please sign in to comment.