Skip to content

Commit

Permalink
Allow injecting View ViewModelFactory (#4271)
Browse files Browse the repository at this point in the history
Task/Issue URL: https://app.asana.com/0/276630244458377/1206794624404182/f

### Description
Being able to inject ViewModelFactory for ViewScope
Use ContributesViewModel from CustomViews
Refactor one CustomView as proof of concept during review


### Steps to test this PR

_Feature 1_
- [x] Fresh install of this branch on a device Android >= 33
- [x] during onboarding don't accept notification permissions
- [x] Go to downloads
- [x] Ensure Notifications permissions is present
- [x] smoke test the card

### UI changes
| Before  | After |
| ------ | ----- |
!(Upload before screenshot)|(Upload after screenshot)|
  • Loading branch information
cmonfortep authored Mar 8, 2024
1 parent eb278a4 commit 3e86da3
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ import com.duckduckgo.common.ui.notifyme.NotifyMeViewModel.Command.OpenSettingsO
import com.duckduckgo.common.ui.notifyme.NotifyMeViewModel.Command.ShowPermissionRationale
import com.duckduckgo.common.ui.notifyme.NotifyMeViewModel.Command.UpdateNotificationsState
import com.duckduckgo.common.ui.notifyme.NotifyMeViewModel.Command.UpdateNotificationsStateOnAndroid13Plus
import com.duckduckgo.common.ui.notifyme.NotifyMeViewModel.Factory
import com.duckduckgo.common.ui.notifyme.NotifyMeViewModel.ViewState
import com.duckduckgo.common.ui.view.gone
import com.duckduckgo.common.ui.view.show
import com.duckduckgo.common.ui.viewbinding.viewBinding
import com.duckduckgo.common.utils.ViewViewModelFactory
import com.duckduckgo.di.scopes.ViewScope
import com.duckduckgo.mobile.android.R
import com.duckduckgo.mobile.android.R.styleable
Expand All @@ -74,7 +74,7 @@ class NotifyMeView @JvmOverloads constructor(
) : FrameLayout(context, attrs, defStyle) {

@Inject
lateinit var viewModelFactory: Factory
lateinit var viewModelFactory: ViewViewModelFactory

private lateinit var sharedPrefsKeyForDismiss: String

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import android.os.Build
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.duckduckgo.anvil.annotations.ContributesViewModel
import com.duckduckgo.appbuildconfig.api.AppBuildConfig
import com.duckduckgo.common.ui.notifyme.NotifyMeViewModel.Command.CheckPermissionRationale
import com.duckduckgo.common.ui.notifyme.NotifyMeViewModel.Command.DismissComponent
Expand All @@ -31,6 +31,7 @@ import com.duckduckgo.common.ui.notifyme.NotifyMeViewModel.Command.ShowPermissio
import com.duckduckgo.common.ui.notifyme.NotifyMeViewModel.Command.UpdateNotificationsState
import com.duckduckgo.common.ui.notifyme.NotifyMeViewModel.Command.UpdateNotificationsStateOnAndroid13Plus
import com.duckduckgo.common.ui.store.notifyme.NotifyMeDataStore
import com.duckduckgo.di.scopes.ViewScope
import javax.inject.Inject
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.Channel
Expand All @@ -44,7 +45,8 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch

@SuppressLint("NoLifecycleObserver") // we don't observe app lifecycle
class NotifyMeViewModel(
@ContributesViewModel(ViewScope::class)
class NotifyMeViewModel @Inject constructor(
private val appBuildConfig: AppBuildConfig,
private val notifyMeDataStore: NotifyMeDataStore,
) : ViewModel(), DefaultLifecycleObserver {
Expand Down Expand Up @@ -144,22 +146,4 @@ class NotifyMeViewModel(
command.send(newCommand)
}
}

@Suppress("UNCHECKED_CAST")
class Factory @Inject constructor(
private val appBuildConfig: AppBuildConfig,
private val notifyMeDataStore: NotifyMeDataStore,
) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return with(modelClass) {
when {
isAssignableFrom(NotifyMeViewModel::class.java) -> NotifyMeViewModel(
appBuildConfig,
notifyMeDataStore,
)
else -> throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}")
}
} as T
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2023 DuckDuckGo
*
* 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 com.duckduckgo.common.utils

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.duckduckgo.common.utils.plugins.view_model.ViewViewModelFactoryPluginPoint
import com.duckduckgo.di.scopes.ViewScope
import com.squareup.anvil.annotations.ContributesBinding
import dagger.SingleInstanceIn
import javax.inject.Inject

@SingleInstanceIn(ViewScope::class)
@ContributesBinding(ViewScope::class)
class ViewViewModelFactory @Inject constructor(
private val viewModelFactoryPluginPoint: ViewViewModelFactoryPluginPoint,
) : ViewModelProvider.NewInstanceFactory() {

override fun <T : ViewModel> create(modelClass: Class<T>): T {
return viewModelFactoryPluginPoint.getPlugins().mapNotNull { it.create(modelClass) }
.first()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2022 DuckDuckGo
*
* 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 com.duckduckgo.common.utils.plugins.view_model

import com.duckduckgo.common.utils.plugins.PluginPoint
import com.duckduckgo.di.DaggerSet
import com.duckduckgo.di.scopes.ViewScope
import dagger.SingleInstanceIn
import javax.inject.Inject

@SingleInstanceIn(ViewScope::class)
class ViewViewModelFactoryPluginPoint @Inject constructor(
private val injectorPlugins: DaggerSet<ViewModelFactoryPlugin>,
) : PluginPoint<ViewModelFactoryPlugin> {
override fun getPlugins(): List<ViewModelFactoryPlugin> {
return injectorPlugins.toList()
}
}

0 comments on commit 3e86da3

Please sign in to comment.