Hilt: introduce MavericksComponent and try on RoomList

This commit is contained in:
ganfra 2021-10-15 10:27:45 +02:00
parent ff53cf4db9
commit f8d208fb4f
9 changed files with 179 additions and 64 deletions

View File

@ -0,0 +1,88 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.core.di
import com.airbnb.mvrx.MavericksState
import com.airbnb.mvrx.MavericksViewModel
import com.airbnb.mvrx.MavericksViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import dagger.hilt.DefineComponent
import dagger.hilt.EntryPoint
import dagger.hilt.EntryPoints
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
/**
* To connect Mavericks ViewModel creation with Hilt's dependency injection, add the following Factory and companion object to your MavericksViewModel.
*
* Example:
*
* class MyViewModel @AssistedInject constructor(...): MavericksViewModel<MyState>(...) {
*
* @AssistedFactory
* interface Factory : AssistedViewModelFactory<MyViewModel, MyState> {
* ...
* }
*
* companion object : MavericksViewModelFactory<MyViewModel, MyState> by hiltMavericksViewModelFactory()
* }
*/
inline fun <reified VM : MavericksViewModel<S>, S : MavericksState> hiltMavericksViewModelFactory() = HiltMavericksViewModelFactory<VM, S>(VM::class.java)
class HiltMavericksViewModelFactory<VM : MavericksViewModel<S>, S : MavericksState>(
private val viewModelClass: Class<out MavericksViewModel<S>>
) : MavericksViewModelFactory<VM, S> {
override fun create(viewModelContext: ViewModelContext, state: S): VM {
// We want to create the ViewModelComponent. In order to do that, we need to get its parent: ActivityComponent.
val componentBuilder = EntryPoints.get(viewModelContext.app(), CreateMavericksViewModelComponent::class.java).mavericksViewModelComponentBuilder()
val viewModelComponent = componentBuilder.build()
val viewModelFactoryMap = EntryPoints.get(viewModelComponent, HiltMavericksEntryPoint::class.java).viewModelFactories
val viewModelFactory = viewModelFactoryMap[viewModelClass]
@Suppress("UNCHECKED_CAST")
val castedViewModelFactory = viewModelFactory as? MavericksAssistedViewModelFactory<VM, S>
return castedViewModelFactory?.create(state) as VM
}
}
/**
* Hilt's ViewModelComponent's parent is ActivityRetainedComponent but there is no easy way to access it. SingletonComponent should be sufficient
* because the ViewModel that gets created is the only object with a reference to the created component so the lifecycle of it will
* still be correct.
*/
@MavericksViewModelScoped
@DefineComponent(parent = SingletonComponent::class)
interface MavericksViewModelComponent
@DefineComponent.Builder
interface MavericksViewModelComponentBuilder {
fun build(): MavericksViewModelComponent
}
@EntryPoint
@InstallIn(SingletonComponent::class)
interface CreateMavericksViewModelComponent {
fun mavericksViewModelComponentBuilder(): MavericksViewModelComponentBuilder
}
@EntryPoint
@InstallIn(MavericksViewModelComponent::class)
interface HiltMavericksEntryPoint {
val viewModelFactories: Map<Class<out MavericksViewModel<*>>, MavericksAssistedViewModelFactory<*, *>>
}

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.core.di
import com.airbnb.mvrx.MavericksState
import com.airbnb.mvrx.MavericksViewModel
/**
* This factory allows Mavericks to supply the initial or restored [MavericksState] to Hilt.
*
* Add this interface inside of your [MavericksViewModel] class then create the following Hilt module:
*
* @Module
* @InstallIn(MavericksViewModelComponent::class)
* interface ViewModelsModule {
* @Binds
* @IntoMap
* @ViewModelKey(MyViewModel::class)
* fun myViewModelFactory(factory: MyViewModel.Factory): AssistedViewModelFactory<*, *>
* }
*
* If you already have a ViewModelsModule then all you have to do is add the multibinding entry for your new [MavericksViewModel].
*/
interface MavericksAssistedViewModelFactory<VM : MavericksViewModel<S>, S : MavericksState> {
fun create(state: S): VM
}

View File

@ -14,16 +14,20 @@
* limitations under the License.
*/
package im.vector.app.features.home.room.list
package im.vector.app.core.di
import dagger.Binds
import dagger.Module
import dagger.hilt.migration.DisableInstallInCheck
import dagger.hilt.InstallIn
import dagger.multibindings.IntoMap
import im.vector.app.features.home.room.list.RoomListViewModel
@DisableInstallInCheck
@InstallIn(MavericksViewModelComponent::class)
@Module
abstract class RoomListModule {
interface MavericksViewModelModule {
@Binds
abstract fun providesRoomListViewModelFactory(factory: RoomListViewModelFactory): RoomListViewModel.Factory
@IntoMap
@MavericksViewModelKey(RoomListViewModel::class)
fun roomListViewModelFactory(factory: RoomListViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.core.di
import javax.inject.Scope
/**
* Scope annotation for bindings that should exist for the life of an MavericksViewModel.
*/
@Scope
annotation class MavericksViewModelScoped

View File

@ -73,7 +73,6 @@ import im.vector.app.features.home.room.detail.timeline.reactions.ViewReactionsB
import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet
import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet
import im.vector.app.features.home.room.filtered.FilteredRoomsActivity
import im.vector.app.features.home.room.list.RoomListModule
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet
import im.vector.app.features.html.EventHtmlRenderer
import im.vector.app.features.html.VectorHtmlCompressor
@ -253,7 +252,6 @@ interface ScreenComponentDependencies {
ViewModelModule::class,
FragmentModule::class,
HomeModule::class,
RoomListModule::class,
ScreenModule::class
]
)

View File

@ -17,6 +17,7 @@
package im.vector.app.core.di
import androidx.lifecycle.ViewModel
import com.airbnb.mvrx.MavericksViewModel
import dagger.MapKey
import kotlin.reflect.KClass
@ -24,3 +25,8 @@ import kotlin.reflect.KClass
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
@MapKey
annotation class MavericksViewModelKey(val value: KClass<out MavericksViewModel<*>>)

View File

@ -64,7 +64,6 @@ data class RoomListParams(
class RoomListFragment @Inject constructor(
private val pagedControllerFactory: RoomSummaryPagedControllerFactory,
val roomListViewModelFactory: RoomListViewModel.Factory,
private val notificationDrawerManager: NotificationDrawerManager,
private val footerController: RoomListFooterController,
private val userPreferencesProvider: UserPreferencesProvider

View File

@ -24,8 +24,13 @@ import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MavericksViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.AppStateHandler
import im.vector.app.RoomGroupingMethod
import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider
@ -48,8 +53,8 @@ import org.matrix.android.sdk.flow.flow
import timber.log.Timber
import javax.inject.Inject
class RoomListViewModel @Inject constructor(
initialState: RoomListViewState,
class RoomListViewModel @AssistedInject constructor(
@Assisted initialState: RoomListViewState,
private val session: Session,
private val stringProvider: StringProvider,
private val appStateHandler: AppStateHandler,
@ -57,8 +62,9 @@ class RoomListViewModel @Inject constructor(
private val autoAcceptInvites: AutoAcceptInvites
) : VectorViewModel<RoomListViewState, RoomListAction, RoomListViewEvents>(initialState) {
interface Factory {
fun create(initialState: RoomListViewState): RoomListViewModel
@AssistedFactory
interface Factory: MavericksAssistedViewModelFactory<RoomListViewModel, RoomListViewState> {
override fun create(state: RoomListViewState): RoomListViewModel
}
private var updatableQuery: UpdatableLivePageResult? = null
@ -115,14 +121,7 @@ class RoomListViewModel @Inject constructor(
}
}
companion object : MavericksViewModelFactory<RoomListViewModel, RoomListViewState> {
@JvmStatic
override fun create(viewModelContext: ViewModelContext, state: RoomListViewState): RoomListViewModel {
val fragment: RoomListFragment = (viewModelContext as FragmentViewModelContext).fragment()
return fragment.roomListViewModelFactory.create(state)
}
}
companion object : MavericksViewModelFactory<RoomListViewModel, RoomListViewState> by hiltMavericksViewModelFactory()
private val roomListSectionBuilder = if (appStateHandler.getCurrentRoomGroupingMethod() is RoomGroupingMethod.BySpace) {
RoomListSectionBuilderSpace(

View File

@ -1,44 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.home.room.list
import im.vector.app.AppStateHandler
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.invite.AutoAcceptInvites
import im.vector.app.features.settings.VectorPreferences
import org.matrix.android.sdk.api.session.Session
import javax.inject.Inject
import javax.inject.Provider
class RoomListViewModelFactory @Inject constructor(private val session: Provider<Session>,
private val appStateHandler: AppStateHandler,
private val stringProvider: StringProvider,
private val vectorPreferences: VectorPreferences,
private val autoAcceptInvites: AutoAcceptInvites) :
RoomListViewModel.Factory {
override fun create(initialState: RoomListViewState): RoomListViewModel {
return RoomListViewModel(
initialState = initialState,
session = session.get(),
stringProvider = stringProvider,
appStateHandler = appStateHandler,
vectorPreferences = vectorPreferences,
autoAcceptInvites = autoAcceptInvites
)
}
}