1
0
Fork 0

Fix ViewModel States (#2457)

Co-authored-by: Jeff Boek <jeff@jeffboek.com>
master
Colin Lee 2019-05-14 22:49:02 +02:00 committed by Jeff Boek
parent a739d5a9e5
commit 49ac62ab85
13 changed files with 83 additions and 90 deletions

View File

@ -25,7 +25,8 @@
tools:ignore="UnusedAttribute"> tools:ignore="UnusedAttribute">
<activity android:name=".HomeActivity" <activity android:name=".HomeActivity"
android:launchMode="singleInstance" android:launchMode="singleInstance"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize"
android:configChanges="keyboard|keyboardHidden|mcc|mnc|orientation|screenSize|locale|layoutDirection|smallestScreenSize|screenLayout">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>

View File

@ -67,9 +67,9 @@ class CollectionCreationComponent(
override fun initView() = CollectionCreationUIView(container, actionEmitter, changesObservable) override fun initView() = CollectionCreationUIView(container, actionEmitter, changesObservable)
override fun render(): Observable<CollectionCreationState> = override fun render(): Observable<CollectionCreationState> =
ViewModelProvider(owner, CollectionCreationViewModel.Factory(initialState, changesObservable)).get( ViewModelProvider(owner, CollectionCreationViewModel.Factory(initialState)).get(
CollectionCreationViewModel::class.java CollectionCreationViewModel::class.java
).render(uiView) ).render(changesObservable, uiView)
init { init {
render() render()
@ -77,22 +77,19 @@ class CollectionCreationComponent(
} }
class CollectionCreationViewModel( class CollectionCreationViewModel(
initialState: CollectionCreationState, initialState: CollectionCreationState
changesObservable: Observable<CollectionCreationChange>
) : ) :
UIComponentViewModel<CollectionCreationState, CollectionCreationAction, CollectionCreationChange>( UIComponentViewModel<CollectionCreationState, CollectionCreationAction, CollectionCreationChange>(
initialState, initialState,
changesObservable,
reducer reducer
) { ) {
class Factory( class Factory(
private val initialState: CollectionCreationState, private val initialState: CollectionCreationState
private val changesObservable: Observable<CollectionCreationChange>
) : ViewModelProvider.Factory { ) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T = override fun <T : ViewModel?> create(modelClass: Class<T>): T =
CollectionCreationViewModel(initialState, changesObservable) as T CollectionCreationViewModel(initialState) as T
} }
companion object { companion object {

View File

@ -53,7 +53,7 @@ class ToolbarComponent(
override fun render(): Observable<SearchState> = override fun render(): Observable<SearchState> =
ViewModelProviders.of(owner, ToolbarViewModel.Factory(initialState, changesObservable)) ViewModelProviders.of(owner, ToolbarViewModel.Factory(initialState, changesObservable))
.get(ToolbarViewModel::class.java).render(uiView) .get(ToolbarViewModel::class.java).render(changesObservable, uiView)
init { init {
render() render()
@ -99,8 +99,8 @@ sealed class SearchChange : Change {
data class SearchShortcutEngineSelected(val engine: SearchEngine) : SearchChange() data class SearchShortcutEngineSelected(val engine: SearchEngine) : SearchChange()
} }
class ToolbarViewModel(initialState: SearchState, changesObservable: Observable<SearchChange>) : class ToolbarViewModel(initialState: SearchState) :
UIComponentViewModel<SearchState, SearchAction, SearchChange>(initialState, changesObservable, reducer) { UIComponentViewModel<SearchState, SearchAction, SearchChange>(initialState, reducer) {
class Factory( class Factory(
private val initialState: SearchState, private val initialState: SearchState,
@ -108,7 +108,7 @@ class ToolbarViewModel(initialState: SearchState, changesObservable: Observable<
) : ViewModelProvider.Factory { ) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T = override fun <T : ViewModel?> create(modelClass: Class<T>): T =
ToolbarViewModel(initialState, changesObservable) as T ToolbarViewModel(initialState) as T
} }
companion object { companion object {

View File

@ -33,8 +33,8 @@ class ExceptionsComponent(
) { ) {
override fun render(): Observable<ExceptionsState> = override fun render(): Observable<ExceptionsState> =
ViewModelProviders.of(owner, ExceptionsViewModel.Factory(initialState, changesObservable)) ViewModelProviders.of(owner, ExceptionsViewModel.Factory(initialState))
.get(ExceptionsViewModel::class.java).render(uiView) .get(ExceptionsViewModel::class.java).render(changesObservable, uiView)
override fun initView() = ExceptionsUIView(container, actionEmitter, changesObservable) override fun initView() = ExceptionsUIView(container, actionEmitter, changesObservable)
@ -56,20 +56,18 @@ sealed class ExceptionsChange : Change {
data class Change(val list: List<ExceptionsItem>) : ExceptionsChange() data class Change(val list: List<ExceptionsItem>) : ExceptionsChange()
} }
class ExceptionsViewModel(initialState: ExceptionsState, changesObservable: Observable<ExceptionsChange>) : class ExceptionsViewModel(initialState: ExceptionsState) :
UIComponentViewModel<ExceptionsState, ExceptionsAction, ExceptionsChange>( UIComponentViewModel<ExceptionsState, ExceptionsAction, ExceptionsChange>(
initialState, initialState,
changesObservable,
reducer reducer
) { ) {
class Factory( class Factory(
private val initialState: ExceptionsState, private val initialState: ExceptionsState
private val changesObservable: Observable<ExceptionsChange>
) : ViewModelProvider.Factory { ) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T = override fun <T : ViewModel?> create(modelClass: Class<T>): T =
ExceptionsViewModel(initialState, changesObservable) as T ExceptionsViewModel(initialState) as T
} }
companion object { companion object {

View File

@ -155,33 +155,37 @@ class HomeFragment : Fragment(), CoroutineScope {
privateBrowsingButton.setOnClickListener { privateBrowsingButton.setOnClickListener {
val browsingModeManager = (activity as HomeActivity).browsingModeManager val browsingModeManager = (activity as HomeActivity).browsingModeManager
browsingModeManager.mode = when (browsingModeManager.mode) { val newMode = when (browsingModeManager.mode) {
BrowsingModeManager.Mode.Normal -> BrowsingModeManager.Mode.Private BrowsingModeManager.Mode.Normal -> BrowsingModeManager.Mode.Private
BrowsingModeManager.Mode.Private -> BrowsingModeManager.Mode.Normal BrowsingModeManager.Mode.Private -> BrowsingModeManager.Mode.Normal
} }
val mode = if (newMode == BrowsingModeManager.Mode.Private) Mode.Private else Mode.Normal
getManagedEmitter<SessionControlChange>().onNext(SessionControlChange.ModeChange(mode))
browsingModeManager.mode = newMode
} }
// We need the shadow to be above the components. // We need the shadow to be above the components.
homeDividerShadow.bringToFront() homeDividerShadow.bringToFront()
} }
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
// This can get called before onCreateView, before the component is defined
view?.let {
val state = sessionControlComponent.stateObservable.blockingFirst()
val modeInt = if (state.mode is Mode.Private) 0 else 1
outState.putInt(KEY_MODE, modeInt)
}
}
override fun onViewStateRestored(savedInstanceState: Bundle?) { override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState) super.onViewStateRestored(savedInstanceState)
if (savedInstanceState != null) { if (savedInstanceState != null) {
emitSessionChanges()
val mode = if (savedInstanceState.getInt(KEY_MODE) == 0) Mode.Private else Mode.Normal
getManagedEmitter<SessionControlChange>().onNext( getManagedEmitter<SessionControlChange>().onNext(
SessionControlChange.ModeChange(mode) SessionControlChange.TabsChange(
(savedInstanceState.getParcelableArrayList<Tab>(
KEY_TABS
) ?: arrayListOf()).toList()
)
)
getManagedEmitter<SessionControlChange>().onNext(
SessionControlChange.CollectionsChange(
(savedInstanceState.getParcelableArrayList<TabCollection>(
KEY_COLLECTIONS
) ?: arrayListOf()).toList()
)
) )
} }
} }
@ -210,6 +214,9 @@ class HomeFragment : Fragment(), CoroutineScope {
} }
} }
val mode = if ((activity as HomeActivity).browsingModeManager.isPrivate) Mode.Private else Mode.Normal
getManagedEmitter<SessionControlChange>().onNext(SessionControlChange.ModeChange(mode))
emitSessionChanges() emitSessionChanges()
sessionObserver = subscribeToSessions() sessionObserver = subscribeToSessions()
} }

View File

@ -42,9 +42,9 @@ class SessionControlComponent(
get() = uiView.view as RecyclerView get() = uiView.view as RecyclerView
override fun render(): Observable<SessionControlState> { override fun render(): Observable<SessionControlState> {
viewModel = ViewModelProviders.of(owner, SessionControlViewModel.Factory(initialState, changesObservable)) viewModel = ViewModelProviders.of(owner, SessionControlViewModel.Factory(initialState))
.get(SessionControlViewModel::class.java) .get(SessionControlViewModel::class.java)
return viewModel.render(uiView) return viewModel.render(changesObservable, uiView)
} }
init { init {
@ -123,20 +123,18 @@ sealed class SessionControlChange : Change {
data class CollectionsChange(val collections: List<TabCollection>) : SessionControlChange() data class CollectionsChange(val collections: List<TabCollection>) : SessionControlChange()
} }
class SessionControlViewModel(initialState: SessionControlState, changesObservable: Observable<SessionControlChange>) : class SessionControlViewModel(initialState: SessionControlState) :
UIComponentViewModel<SessionControlState, SessionControlAction, SessionControlChange>( UIComponentViewModel<SessionControlState, SessionControlAction, SessionControlChange>(
initialState, initialState,
changesObservable,
reducer reducer
) { ) {
class Factory( class Factory(
private val initialState: SessionControlState, private val initialState: SessionControlState
private val changesObservable: Observable<SessionControlChange>
) : ViewModelProvider.Factory { ) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T = override fun <T : ViewModel?> create(modelClass: Class<T>): T =
SessionControlViewModel(initialState, changesObservable) as T SessionControlViewModel(initialState) as T
} }
companion object { companion object {

View File

@ -39,8 +39,8 @@ class BookmarkComponent(
override fun render(): Observable<BookmarkState> { override fun render(): Observable<BookmarkState> {
return ViewModelProvider( return ViewModelProvider(
owner, owner,
BookmarkViewModel.Factory(initialState, changesObservable) BookmarkViewModel.Factory(initialState)
).get(BookmarkViewModel::class.java).render(uiView) ).get(BookmarkViewModel::class.java).render(changesObservable, uiView)
} }
init { init {
@ -81,16 +81,15 @@ operator fun BookmarkNode.contains(item: BookmarkNode): Boolean {
return children?.contains(item) ?: false return children?.contains(item) ?: false
} }
class BookmarkViewModel(initialState: BookmarkState, changesObservable: Observable<BookmarkChange>) : class BookmarkViewModel(initialState: BookmarkState) :
UIComponentViewModel<BookmarkState, BookmarkAction, BookmarkChange>(initialState, changesObservable, reducer) { UIComponentViewModel<BookmarkState, BookmarkAction, BookmarkChange>(initialState, reducer) {
class Factory( class Factory(
private val initialState: BookmarkState, private val initialState: BookmarkState
private val changesObservable: Observable<BookmarkChange>
) : ViewModelProvider.Factory { ) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T = override fun <T : ViewModel?> create(modelClass: Class<T>): T =
BookmarkViewModel(initialState, changesObservable) as T BookmarkViewModel(initialState) as T
} }
companion object { companion object {

View File

@ -35,8 +35,8 @@ class SignInComponent(
override fun render(): Observable<SignInState> = override fun render(): Observable<SignInState> =
ViewModelProvider( ViewModelProvider(
owner, owner,
SignInViewModel.Factory(initialState, changesObservable) SignInViewModel.Factory(initialState)
).get(SignInViewModel::class.java).render(uiView) ).get(SignInViewModel::class.java).render(changesObservable, uiView)
init { init {
render() render()
@ -54,18 +54,17 @@ sealed class SignInChange : Change {
object SignedOut : SignInChange() object SignedOut : SignInChange()
} }
class SignInViewModel(initialState: SignInState, changesObservable: Observable<SignInChange>) : class SignInViewModel(initialState: SignInState) :
UIComponentViewModel<SignInState, SignInAction, SignInChange>( UIComponentViewModel<SignInState, SignInAction, SignInChange>(
initialState, changesObservable, reducer initialState, reducer
) { ) {
class Factory( class Factory(
private val initialState: SignInState, private val initialState: SignInState
private val changesObservable: Observable<SignInChange>
) : ViewModelProvider.Factory { ) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T = override fun <T : ViewModel?> create(modelClass: Class<T>): T =
SignInViewModel(initialState, changesObservable) as T SignInViewModel(initialState) as T
} }
companion object { companion object {

View File

@ -36,8 +36,8 @@ class HistoryComponent(
override fun render(): Observable<HistoryState> = override fun render(): Observable<HistoryState> =
ViewModelProvider( ViewModelProvider(
owner, owner,
HistoryViewModel.Factory(initialState, changesObservable) HistoryViewModel.Factory(initialState)
).get(HistoryViewModel::class.java).render(uiView) ).get(HistoryViewModel::class.java).render(changesObservable, uiView)
init { init {
render() render()
@ -74,16 +74,15 @@ sealed class HistoryChange : Change {
data class RemoveItemForRemoval(val item: HistoryItem) : HistoryChange() data class RemoveItemForRemoval(val item: HistoryItem) : HistoryChange()
} }
class HistoryViewModel(initialState: HistoryState, changesObservable: Observable<HistoryChange>) : class HistoryViewModel(initialState: HistoryState) :
UIComponentViewModel<HistoryState, HistoryAction, HistoryChange>(initialState, changesObservable, reducer) { UIComponentViewModel<HistoryState, HistoryAction, HistoryChange>(initialState, reducer) {
class Factory( class Factory(
private val initialState: HistoryState, private val initialState: HistoryState
private val changesObservable: Observable<HistoryChange>
) : ViewModelProvider.Factory { ) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T = override fun <T : ViewModel?> create(modelClass: Class<T>): T =
HistoryViewModel(initialState, changesObservable) as T HistoryViewModel(initialState) as T
} }
companion object { companion object {

View File

@ -39,8 +39,8 @@ class QuickActionComponent(
override fun render(): Observable<QuickActionState> = override fun render(): Observable<QuickActionState> =
ViewModelProvider( ViewModelProvider(
owner, owner,
QuickActionViewModel.Factory(initialState, changesObservable) QuickActionViewModel.Factory(initialState)
).get(QuickActionViewModel::class.java).render(uiView) ).get(QuickActionViewModel::class.java).render(changesObservable, uiView)
init { init {
render() render()
@ -71,20 +71,18 @@ sealed class QuickActionChange : Change {
object BounceNeededChange : QuickActionChange() object BounceNeededChange : QuickActionChange()
} }
class QuickActionViewModel(initialState: QuickActionState, changesObservable: Observable<QuickActionChange>) : class QuickActionViewModel(initialState: QuickActionState) :
UIComponentViewModel<QuickActionState, QuickActionAction, QuickActionChange>( UIComponentViewModel<QuickActionState, QuickActionAction, QuickActionChange>(
initialState, initialState,
changesObservable,
reducer reducer
) { ) {
class Factory( class Factory(
private val initialState: QuickActionState, private val initialState: QuickActionState
private val changesObservable: Observable<QuickActionChange>
) : ViewModelProvider.Factory { ) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T = override fun <T : ViewModel?> create(modelClass: Class<T>): T =
QuickActionViewModel(initialState, changesObservable) as T QuickActionViewModel(initialState) as T
} }
companion object { companion object {

View File

@ -51,28 +51,26 @@ class AwesomeBarComponent(
override fun initView() = AwesomeBarUIView(container, actionEmitter, changesObservable) override fun initView() = AwesomeBarUIView(container, actionEmitter, changesObservable)
override fun render(): Observable<AwesomeBarState> = override fun render(): Observable<AwesomeBarState> =
ViewModelProviders.of(owner, AwesomeBarViewModel.Factory(initialState, changesObservable)) ViewModelProviders.of(owner, AwesomeBarViewModel.Factory(initialState))
.get(AwesomeBarViewModel::class.java).render(uiView) .get(AwesomeBarViewModel::class.java).render(changesObservable, uiView)
init { init {
render() render()
} }
} }
class AwesomeBarViewModel(initialState: AwesomeBarState, changesObservable: Observable<AwesomeBarChange>) : class AwesomeBarViewModel(initialState: AwesomeBarState) :
UIComponentViewModel<AwesomeBarState, AwesomeBarAction, AwesomeBarChange>( UIComponentViewModel<AwesomeBarState, AwesomeBarAction, AwesomeBarChange>(
initialState, initialState,
changesObservable,
reducer reducer
) { ) {
class Factory( class Factory(
private val initialState: AwesomeBarState, private val initialState: AwesomeBarState
private val changesObservable: Observable<AwesomeBarChange>
) : ViewModelProvider.Factory { ) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T = override fun <T : ViewModel?> create(modelClass: Class<T>): T =
AwesomeBarViewModel(initialState, changesObservable) as T AwesomeBarViewModel(initialState) as T
} }
companion object { companion object {

View File

@ -40,9 +40,9 @@ class QuickSettingsComponent(
} }
override fun render(): Observable<QuickSettingsState> = override fun render(): Observable<QuickSettingsState> =
ViewModelProvider(owner, QuickSettingsViewModel.Factory(initialState, changesObservable)).get( ViewModelProvider(owner, QuickSettingsViewModel.Factory(initialState)).get(
QuickSettingsViewModel::class.java QuickSettingsViewModel::class.java
).render(uiView) ).render(changesObservable, uiView)
init { init {
render() render()
@ -120,20 +120,18 @@ sealed class QuickSettingsChange : Change {
data class Stored(val phoneFeature: PhoneFeature, val sitePermissions: SitePermissions?) : QuickSettingsChange() data class Stored(val phoneFeature: PhoneFeature, val sitePermissions: SitePermissions?) : QuickSettingsChange()
} }
class QuickSettingsViewModel(initialState: QuickSettingsState, changesObservable: Observable<QuickSettingsChange>) : class QuickSettingsViewModel(initialState: QuickSettingsState) :
UIComponentViewModel<QuickSettingsState, QuickSettingsAction, QuickSettingsChange>( UIComponentViewModel<QuickSettingsState, QuickSettingsAction, QuickSettingsChange>(
initialState, initialState,
changesObservable,
reducer reducer
) { ) {
class Factory( class Factory(
private val initialState: QuickSettingsState, private val initialState: QuickSettingsState
private val changesObservable: Observable<QuickSettingsChange>
) : ViewModelProvider.Factory { ) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T = override fun <T : ViewModel?> create(modelClass: Class<T>): T =
QuickSettingsViewModel(initialState, changesObservable) as T QuickSettingsViewModel(initialState) as T
} }
companion object { companion object {

View File

@ -28,32 +28,33 @@ abstract class UIComponent<S : ViewState, A : Action, C : Change>(
} }
open class UIComponentViewModel<S : ViewState, A : Action, C : Change>( open class UIComponentViewModel<S : ViewState, A : Action, C : Change>(
private val initialState: S, initialState: S,
val changesObservable: Observable<C>, private val reducer: Reducer<S, C>
reducer: Reducer<S, C>
) : ViewModel() { ) : ViewModel() {
private val statesObservable: Observable<S> = internalRender(reducer) private var currentState: S = initialState
private var statesDisposable: Disposable? = null private var statesDisposable: Disposable? = null
/** /**
* Render the ViewState to the View through the Reducer * Render the ViewState to the View through the Reducer
*/ */
fun render(uiView: UIView<S, A, C>): Observable<S> { fun render(changesObservable: Observable<C>, uiView: UIView<S, A, C>): Observable<S> {
val statesObservable = internalRender(changesObservable, reducer)
statesDisposable = statesObservable statesDisposable = statesObservable
.subscribe(uiView.updateView()) .subscribe(uiView.updateView())
return statesObservable return statesObservable
} }
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")
protected fun internalRender(reducer: Reducer<S, C>): Observable<S> = protected fun internalRender(changesObservable: Observable<C>, reducer: Reducer<S, C>): Observable<S> =
changesObservable changesObservable
.scan(initialState, reducer) .scan(currentState, reducer)
.distinctUntilChanged() .distinctUntilChanged()
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.replay(1) .replay(1)
.autoConnect(0) .autoConnect(0)
.doOnNext { currentState = it }
override fun onCleared() { override fun onCleared() {
super.onCleared() super.onCleared()