From f8d208fb4f9ccf4ce76737675cc81a88c1951a15 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 15 Oct 2021 10:27:45 +0200 Subject: [PATCH] Hilt: introduce MavericksComponent and try on RoomList --- .../core/di/HiltMavericksViewModelFactory.kt | 88 +++++++++++++++++++ .../di/MavericksAssistedViewModelFactory.kt | 40 +++++++++ .../di/MavericksViewModelModule.kt} | 14 +-- .../app/core/di/MavericksViewModelScoped.kt | 25 ++++++ .../im/vector/app/core/di/ScreenComponent.kt | 2 - .../im/vector/app/core/di/ViewModelKey.kt | 6 ++ .../home/room/list/RoomListFragment.kt | 1 - .../home/room/list/RoomListViewModel.kt | 23 +++-- .../room/list/RoomListViewModelFactory.kt | 44 ---------- 9 files changed, 179 insertions(+), 64 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/core/di/HiltMavericksViewModelFactory.kt create mode 100644 vector/src/main/java/im/vector/app/core/di/MavericksAssistedViewModelFactory.kt rename vector/src/main/java/im/vector/app/{features/home/room/list/RoomListModule.kt => core/di/MavericksViewModelModule.kt} (61%) create mode 100644 vector/src/main/java/im/vector/app/core/di/MavericksViewModelScoped.kt delete mode 100644 vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModelFactory.kt diff --git a/vector/src/main/java/im/vector/app/core/di/HiltMavericksViewModelFactory.kt b/vector/src/main/java/im/vector/app/core/di/HiltMavericksViewModelFactory.kt new file mode 100644 index 0000000000..d0dd4fa002 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/di/HiltMavericksViewModelFactory.kt @@ -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(...) { + * + * @AssistedFactory + * interface Factory : AssistedViewModelFactory { + * ... + * } + * + * companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + * } + */ + +inline fun , S : MavericksState> hiltMavericksViewModelFactory() = HiltMavericksViewModelFactory(VM::class.java) + +class HiltMavericksViewModelFactory, S : MavericksState>( + private val viewModelClass: Class> +) : MavericksViewModelFactory { + + 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 + 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>, MavericksAssistedViewModelFactory<*, *>> +} diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksAssistedViewModelFactory.kt b/vector/src/main/java/im/vector/app/core/di/MavericksAssistedViewModelFactory.kt new file mode 100644 index 0000000000..4600ced00e --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/di/MavericksAssistedViewModelFactory.kt @@ -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, S : MavericksState> { + fun create(state: S): VM +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt similarity index 61% rename from vector/src/main/java/im/vector/app/features/home/room/list/RoomListModule.kt rename to vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index a708f86b67..e8eb1d0fd0 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -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<*, *> } diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelScoped.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelScoped.kt new file mode 100644 index 0000000000..58b9246fe5 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelScoped.kt @@ -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 diff --git a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt index 022d66f113..f822644676 100644 --- a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt +++ b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt @@ -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 ] ) diff --git a/vector/src/main/java/im/vector/app/core/di/ViewModelKey.kt b/vector/src/main/java/im/vector/app/core/di/ViewModelKey.kt index 5f0ee30821..2782edd558 100644 --- a/vector/src/main/java/im/vector/app/core/di/ViewModelKey.kt +++ b/vector/src/main/java/im/vector/app/core/di/ViewModelKey.kt @@ -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) + +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.FUNCTION) +@MapKey +annotation class MavericksViewModelKey(val value: KClass>) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt index c36836c87f..1c173e12e8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt @@ -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 diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index 345c33ec18..81c887bd0e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -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(initialState) { - interface Factory { - fun create(initialState: RoomListViewState): RoomListViewModel + @AssistedFactory + interface Factory: MavericksAssistedViewModelFactory { + override fun create(state: RoomListViewState): RoomListViewModel } private var updatableQuery: UpdatableLivePageResult? = null @@ -115,14 +121,7 @@ class RoomListViewModel @Inject constructor( } } - companion object : MavericksViewModelFactory { - - @JvmStatic - override fun create(viewModelContext: ViewModelContext, state: RoomListViewState): RoomListViewModel { - val fragment: RoomListFragment = (viewModelContext as FragmentViewModelContext).fragment() - return fragment.roomListViewModelFactory.create(state) - } - } + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() private val roomListSectionBuilder = if (appStateHandler.getCurrentRoomGroupingMethod() is RoomGroupingMethod.BySpace) { RoomListSectionBuilderSpace( diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModelFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModelFactory.kt deleted file mode 100644 index e017a8fe08..0000000000 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModelFactory.kt +++ /dev/null @@ -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, - 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 - ) - } -}