save and restoring state view model
This commit is contained in:
parent
de1aa00715
commit
fe363058b5
|
@ -3,7 +3,11 @@ package app.dapk.st.core
|
|||
import androidx.activity.ComponentActivity
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelLazy
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.ViewModelProvider.*
|
||||
import androidx.lifecycle.ViewModelStore
|
||||
import androidx.lifecycle.viewmodel.CreationExtras
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
inline fun <reified VM : ViewModel> ComponentActivity.viewModel(
|
||||
noinline factory: () -> VM
|
||||
|
@ -17,3 +21,53 @@ inline fun <reified VM : ViewModel> ComponentActivity.viewModel(
|
|||
}
|
||||
return ViewModelLazy(VM::class, { viewModelStore }, { factoryPromise })
|
||||
}
|
||||
|
||||
|
||||
inline fun <reified S, E> ComponentActivity.state(
|
||||
noinline factory: () -> StateViewModel<S, E>
|
||||
): Lazy<State<S, E>> {
|
||||
val factoryPromise = object : Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return when(modelClass) {
|
||||
StateViewModel::class.java -> factory() as T
|
||||
else -> throw Error()
|
||||
}
|
||||
}
|
||||
}
|
||||
return FooViewModelLazy(
|
||||
key = S::class.java.canonicalName!!,
|
||||
StateViewModel::class,
|
||||
{ viewModelStore },
|
||||
{ factoryPromise }
|
||||
) as Lazy<State<S, E>>
|
||||
}
|
||||
|
||||
class FooViewModelLazy<VM : ViewModel> @JvmOverloads constructor(
|
||||
private val key: String,
|
||||
private val viewModelClass: KClass<VM>,
|
||||
private val storeProducer: () -> ViewModelStore,
|
||||
private val factoryProducer: () -> ViewModelProvider.Factory,
|
||||
private val extrasProducer: () -> CreationExtras = { CreationExtras.Empty }
|
||||
) : Lazy<VM> {
|
||||
private var cached: VM? = null
|
||||
|
||||
override val value: VM
|
||||
get() {
|
||||
val viewModel = cached
|
||||
return if (viewModel == null) {
|
||||
val factory = factoryProducer()
|
||||
val store = storeProducer()
|
||||
ViewModelProvider(
|
||||
store,
|
||||
factory,
|
||||
extrasProducer()
|
||||
).get(key, viewModelClass.java).also {
|
||||
cached = it
|
||||
}
|
||||
} else {
|
||||
viewModel
|
||||
}
|
||||
}
|
||||
|
||||
override fun isInitialized(): Boolean = cached != null
|
||||
}
|
|
@ -16,11 +16,11 @@ import kotlinx.coroutines.launch
|
|||
class StateViewModel<S, E>(
|
||||
reducerFactory: ReducerFactory<S>,
|
||||
eventSource: MutableSharedFlow<E>,
|
||||
) : ViewModel(), StateStore<S, E> {
|
||||
) : ViewModel(), State<S, E> {
|
||||
|
||||
private val store: Store<S> = createStore(reducerFactory, viewModelScope)
|
||||
override val events: SharedFlow<E> = eventSource
|
||||
override val state
|
||||
override val current
|
||||
get() = _state!!
|
||||
private var _state: S by mutableStateOf(store.getState())
|
||||
|
||||
|
@ -42,8 +42,8 @@ fun <S, E> createStateViewModel(block: (suspend (E) -> Unit) -> ReducerFactory<S
|
|||
return StateViewModel(reducer, eventSource)
|
||||
}
|
||||
|
||||
interface StateStore<S, E> {
|
||||
interface State<S, E> {
|
||||
fun dispatch(action: Action)
|
||||
val events: SharedFlow<E>
|
||||
val state: S
|
||||
val current: S
|
||||
}
|
||||
|
|
|
@ -35,9 +35,13 @@ import app.dapk.st.design.components.CircleishAvatar
|
|||
import app.dapk.st.design.components.GenericEmpty
|
||||
import app.dapk.st.design.components.GenericError
|
||||
import app.dapk.st.design.components.Toolbar
|
||||
import app.dapk.st.directory.DirectoryEvent.OpenDownloadUrl
|
||||
import app.dapk.st.directory.DirectoryScreenState.Content
|
||||
import app.dapk.st.directory.DirectoryScreenState.EmptyLoading
|
||||
import app.dapk.st.directory.state.DirectoryEvent.OpenDownloadUrl
|
||||
import app.dapk.st.directory.state.DirectoryScreenState.Content
|
||||
import app.dapk.st.directory.state.DirectoryScreenState.EmptyLoading
|
||||
import app.dapk.st.directory.state.ComponentLifecycle
|
||||
import app.dapk.st.directory.state.DirectoryEvent
|
||||
import app.dapk.st.directory.state.DirectoryScreenState
|
||||
import app.dapk.st.directory.state.DirectoryState
|
||||
import app.dapk.st.engine.DirectoryItem
|
||||
import app.dapk.st.engine.RoomOverview
|
||||
import app.dapk.st.engine.Typing
|
||||
|
@ -53,8 +57,8 @@ import java.time.temporal.ChronoUnit
|
|||
import kotlin.math.roundToInt
|
||||
|
||||
@Composable
|
||||
fun DirectoryScreen(directoryViewModel: DirectoryViewModel) {
|
||||
val state = directoryViewModel.state
|
||||
fun DirectoryScreen(directoryViewModel: DirectoryState) {
|
||||
val state = directoryViewModel.current
|
||||
|
||||
val listState: LazyListState = rememberLazyListState(
|
||||
initialFirstVisibleItemIndex = 0,
|
||||
|
@ -101,7 +105,7 @@ fun DirectoryScreen(directoryViewModel: DirectoryViewModel) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun DirectoryViewModel.ObserveEvents(listState: LazyListState, toolbarPosition: MutableState<Float>) {
|
||||
private fun DirectoryState.ObserveEvents(listState: LazyListState, toolbarPosition: MutableState<Float>) {
|
||||
val context = LocalContext.current
|
||||
StartObserving {
|
||||
this@ObserveEvents.events.launch {
|
||||
|
|
|
@ -4,6 +4,9 @@ import android.content.Context
|
|||
import app.dapk.st.core.ProvidableModule
|
||||
import app.dapk.st.core.StateViewModel
|
||||
import app.dapk.st.core.createStateViewModel
|
||||
import app.dapk.st.directory.state.DirectoryEvent
|
||||
import app.dapk.st.directory.state.DirectoryScreenState
|
||||
import app.dapk.st.directory.state.directoryReducer
|
||||
import app.dapk.st.engine.ChatEngine
|
||||
|
||||
class DirectoryModule(
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package app.dapk.st.directory.state
|
||||
|
||||
import app.dapk.st.engine.DirectoryState
|
||||
import app.dapk.state.Action
|
||||
|
||||
sealed interface ComponentLifecycle : Action {
|
||||
object OnVisible : ComponentLifecycle
|
||||
object OnGone : ComponentLifecycle
|
||||
}
|
||||
|
||||
sealed interface DirectorySideEffect : Action {
|
||||
object ScrollToTop : DirectorySideEffect
|
||||
}
|
||||
|
||||
sealed interface DirectoryStateChange : Action {
|
||||
object Empty : DirectoryStateChange
|
||||
data class Content(val content: DirectoryState) : DirectoryStateChange
|
||||
}
|
|
@ -1,15 +1,12 @@
|
|||
package app.dapk.st.directory
|
||||
package app.dapk.st.directory.state
|
||||
|
||||
import app.dapk.st.core.StateStore
|
||||
import app.dapk.st.directory.ShortcutHandler
|
||||
import app.dapk.st.engine.ChatEngine
|
||||
import app.dapk.st.engine.DirectoryState
|
||||
import app.dapk.state.*
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
typealias DirectoryViewModel = StateStore<DirectoryScreenState, DirectoryEvent>
|
||||
|
||||
fun directoryReducer(
|
||||
chatEngine: ChatEngine,
|
||||
shortcutHandler: ShortcutHandler,
|
||||
|
@ -18,6 +15,7 @@ fun directoryReducer(
|
|||
var syncJob: Job? = null
|
||||
return createReducer(
|
||||
initialState = DirectoryScreenState.EmptyLoading,
|
||||
|
||||
multi(ComponentLifecycle::class) { action ->
|
||||
when (action) {
|
||||
ComponentLifecycle.OnVisible -> async { _ ->
|
||||
|
@ -33,28 +31,16 @@ fun directoryReducer(
|
|||
ComponentLifecycle.OnGone -> sideEffect { _, _ -> syncJob?.cancel() }
|
||||
}
|
||||
},
|
||||
|
||||
change(DirectoryStateChange::class) { action, _ ->
|
||||
when (action) {
|
||||
is DirectoryStateChange.Content -> DirectoryScreenState.Content(action.content)
|
||||
DirectoryStateChange.Empty -> DirectoryScreenState.Empty
|
||||
}
|
||||
},
|
||||
|
||||
sideEffect(DirectorySideEffect.ScrollToTop::class) { _, _ ->
|
||||
eventEmitter(DirectoryEvent.ScrollToTop)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
sealed interface ComponentLifecycle : Action {
|
||||
object OnVisible : ComponentLifecycle
|
||||
object OnGone : ComponentLifecycle
|
||||
}
|
||||
|
||||
sealed interface DirectorySideEffect : Action {
|
||||
object ScrollToTop : DirectorySideEffect
|
||||
}
|
||||
|
||||
sealed interface DirectoryStateChange : Action {
|
||||
object Empty : DirectoryStateChange
|
||||
data class Content(val content: DirectoryState) : DirectoryStateChange
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
package app.dapk.st.directory
|
||||
package app.dapk.st.directory.state
|
||||
|
||||
import app.dapk.st.core.State
|
||||
import app.dapk.st.engine.DirectoryState
|
||||
|
||||
sealed interface DirectoryScreenState {
|
||||
typealias DirectoryState = State<DirectoryScreenState, DirectoryEvent>
|
||||
|
||||
sealed interface DirectoryScreenState {
|
||||
object EmptyLoading : DirectoryScreenState
|
||||
object Empty : DirectoryScreenState
|
||||
data class Content(
|
||||
|
@ -15,4 +17,3 @@ sealed interface DirectoryEvent {
|
|||
data class OpenDownloadUrl(val url: String) : DirectoryEvent
|
||||
object ScrollToTop : DirectoryEvent
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
package app.dapk.st.directory
|
||||
|
||||
import ViewModelTest
|
||||
import app.dapk.st.directory.state.DirectoryScreenState
|
||||
import app.dapk.st.directory.state.DirectoryState
|
||||
import app.dapk.st.engine.DirectoryItem
|
||||
import app.dapk.st.engine.UnreadCount
|
||||
import fake.FakeChatEngine
|
||||
|
@ -18,7 +20,7 @@ class DirectoryViewModelTest {
|
|||
private val fakeShortcutHandler = FakeShortcutHandler()
|
||||
private val fakeChatEngine = FakeChatEngine()
|
||||
|
||||
private val viewModel = DirectoryViewModel(
|
||||
private val viewModel = DirectoryState(
|
||||
fakeShortcutHandler.instance,
|
||||
fakeChatEngine,
|
||||
runViewModelTest.testMutableStateFactory(),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package app.dapk.st.home
|
||||
|
||||
import app.dapk.st.core.ProvidableModule
|
||||
import app.dapk.st.directory.DirectoryViewModel
|
||||
import app.dapk.st.directory.state.DirectoryState
|
||||
import app.dapk.st.domain.StoreModule
|
||||
import app.dapk.st.engine.ChatEngine
|
||||
import app.dapk.st.login.LoginViewModel
|
||||
|
@ -13,7 +13,7 @@ class HomeModule(
|
|||
val betaVersionUpgradeUseCase: BetaVersionUpgradeUseCase,
|
||||
) : ProvidableModule {
|
||||
|
||||
fun homeViewModel(directory: DirectoryViewModel, login: LoginViewModel, profileViewModel: ProfileViewModel): HomeViewModel {
|
||||
fun homeViewModel(directory: DirectoryState, login: LoginViewModel, profileViewModel: ProfileViewModel): HomeViewModel {
|
||||
return HomeViewModel(
|
||||
chatEngine,
|
||||
storeModule.credentialsStore(),
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package app.dapk.st.home
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.dapk.st.directory.DirectorySideEffect
|
||||
import app.dapk.st.directory.DirectoryViewModel
|
||||
import app.dapk.st.directory.state.DirectorySideEffect
|
||||
import app.dapk.st.directory.state.DirectoryState
|
||||
import app.dapk.st.domain.StoreCleaner
|
||||
import app.dapk.st.engine.ChatEngine
|
||||
import app.dapk.st.home.HomeScreenState.*
|
||||
|
@ -21,7 +21,7 @@ import kotlinx.coroutines.launch
|
|||
class HomeViewModel(
|
||||
private val chatEngine: ChatEngine,
|
||||
private val credentialsProvider: CredentialsStore,
|
||||
private val directoryViewModel: DirectoryViewModel,
|
||||
private val directoryViewModel: DirectoryState,
|
||||
private val loginViewModel: LoginViewModel,
|
||||
private val profileViewModel: ProfileViewModel,
|
||||
private val cacheCleaner: StoreCleaner,
|
||||
|
|
|
@ -14,8 +14,9 @@ import androidx.lifecycle.lifecycleScope
|
|||
import app.dapk.st.core.DapkActivity
|
||||
import app.dapk.st.core.module
|
||||
import app.dapk.st.core.viewModel
|
||||
import app.dapk.st.core.state
|
||||
import app.dapk.st.directory.DirectoryModule
|
||||
import app.dapk.st.directory.DirectoryViewModel
|
||||
import app.dapk.st.directory.state.DirectoryState
|
||||
import app.dapk.st.login.LoginModule
|
||||
import app.dapk.st.profile.ProfileModule
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
|
@ -23,7 +24,7 @@ import kotlinx.coroutines.flow.onEach
|
|||
|
||||
class MainActivity : DapkActivity() {
|
||||
|
||||
private val directoryViewModel: DirectoryViewModel by viewModel { module<DirectoryModule>().directoryViewModel() }
|
||||
private val directoryViewModel: DirectoryState by state { module<DirectoryModule>().directoryViewModel() }
|
||||
private val loginViewModel by viewModel { module<LoginModule>().loginViewModel() }
|
||||
private val profileViewModel by viewModel { module<ProfileModule>().profileViewModel() }
|
||||
private val homeViewModel by viewModel { module<HomeModule>().homeViewModel(directoryViewModel, loginViewModel, profileViewModel) }
|
||||
|
|
Loading…
Reference in New Issue