Hilt field injection android - android

here I am trying to inject the adapter in activity via field injection. Adapter has a parameter(list).
Can somebody assist me here? i am facing compile time error
cannot be provided without an #Provides-annotated method.
Please refer below code
#AndroidEntryPoint
class RecipeActivity() : PostLoginActivity() {
var TAG = MainActivity::class.java.simpleName
private lateinit var binding: ActivityRecipeBinding
private val viewModel: RecipeViewModel by viewModels()
#Inject lateinit var adapter: RecipeAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
}
class RecipeAdapter #Inject constructor(list: MutableList<RecipeModel> ) :
BaseAdapter<RecipeModel>(list) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<RecipeModel> {
return RecipeViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.item_recipe, parent, false), this
)
}
override fun onBindViewHolder(holder: BaseViewHolder<RecipeModel>, position: Int) {
holder.bindData(baseList[position])
}
}
data class RecipeModel(
var title: String,
var imageType: String,
var url: String
) : Item()

In order to Inject a class, Hilt/Dagger needs to understand exactly how to Inject it. In your project, you should have a 'Module' object. Within here, you can create #Provides methods, which tell Hilt/Dagger exactly what a class looks like so that it can be injected (find out more here).
For example, to provide a class that implements some Android Retrofit services, you might have a module that looks like:
#Module
#InstallIn(ActivityComponent::class)
object AnalyticsModule {
#Provides
fun provideAnalyticsService(
// Potential dependencies of this type
): AnalyticsService {
return Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(AnalyticsService::class.java)
}
}
In this example, we can now use #Inject for an AnalyticsService, as Hilt/Dagger now knows how to make one!
In your scenario, it looks like your adapter needs to be constructed with a list of RecipeModels. As you will unlikely have access to this data at the Module level, I don't think you want to be injecting the Adapter like this? Simply creating it in the Activity should be sufficient for what you need!
Something like this:
private var adapter: RecipeAdapter? = null // OR
lateinit var adapter: RecipeAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
adapter = RecipeAdapter(viewModel.recipeModels)
}
As a rule of thumb, it is generally more common to use injection for services, factories and view models rather than UI elements like adapters, as these UI elements need to often be constructed with actual data which isn't available in an application's Hilt/Dagger module.

Well as an error suggests you need to have a module in which you provide default construction of you adapter.
Example:
#Module
#InstallIn(ActivityComponent::class)
object AppModule {
#Provides
fun provideRecipeAdapter(
list: MutableList<RecipeModel>
): RecipeAdapter {
return RecipeAdapter(list)
}
}
This is just an example of what you are missing, not actual working code. For more details of how to create these modules look at the documentation

Related

How to access a function from a viewModel in another viewModel

I have 2 viewModels -
MainViewModel**
StorageViewModel
StorageViewModel.kt
class StorageViewModel #ViewModelInject constructor(private val preferenceStorage:
PreferenceStorage, #ApplicationContext context: Context) : ViewModel() {
........
//save last played song
fun saveLastPlayedSong(song: Songs){
viewModelScope.launch {
protoDataStoreManager.saveLastPlayedSong(song)
}
}
}
Now, I want to call the saveLastPlayedSong function in MainViewModel
MainViewModel.kt
class MainViewModel #ViewModelInject constructor(
private val musicServiceConnection: MusicServiceConnection,
private val storageViewModel: StorageViewModel
) : ViewModel(){
.........
fun playOrToggleSong(
mediaItem: Songs, toggle: Boolean = false
)
{
//here, I want to call the function from StorageViewModel e.g
storageViewModel.saveLastPlayedSong(mediaItem)
}
}
How do I instantiate the "StorageViewModel" inside MainViewModel and whats the best way (Good Practice).
I'm using MVVM and Hilt.
This is usually a symptom of bad architecture.
If StorageViewModel is acting like a Repository it should not extend ViewModel. If it doesn't have connections to UI you can convert it to a repository class and that would solve your problem because it would just become an injectable singleton.
If StorageViewModel is connected to a Fragment (for example) you should take a reference to both viewmodels and pass data between them from the UI layer.
Something like:
class StorageFragment : Fragment {
private val storageViewModel: StorageViewModel by viewModels()
private val mainActivityViewModel: MainViewModel by activityViewModels()
//....
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
//you can do this if the song saving is a UI related thing
//just have playOrToggleSong accept a function as parameter
//as success callback
button.setOnClickListener {
mainActivityViewModel.playOrToggleSong(...) {
storageViewModel.saveLastPlayedSong(param)
}
}
}
}

Can Hilt be used on Android with by viewModels to initialize an abstract viewModel field?

I'm trying to wrap my head around Hilt and the way it deals with ViewModels.
I would like my fragments to depend on abstract view models, so I can easily mock them during UI tests. Ex:
#AndroidEntryPoint
class MainFragment : Fragment() {
private val vm : AbsViewModel by viewModels()
/*
...
*/
}
#HiltViewModel
class MainViewModel(private val dependency: DependencyInterface) : AbsViewModel()
abstract class AbsViewModel : ViewModel()
Is there a way to configure by viewModels() so that it can map concrete implementations to abstract view models? Or pass a custom factory producer to viewModels() that can map concrete view models instances to abstract classes?
The exact question is also available here, but it is quite old considering hilt was still in alpha then: https://github.com/google/dagger/issues/1972
However, the solution provided there is not very desirable since it uses a string that points to the path of the concrete view model. I think this will not survive obfuscation or moving files and it can quickly become a nightmare to maintain. The answer also suggests injecting a concrete view model into the fragment during tests with all the view model's dependencies mocked, thus gaining the ability to control what happens in the test. This automatically makes my UI test depend on the implementation of said view model, which I would very much like to avoid.
Not being able to use abstract view models in my fragments makes me think I'm breaking the D in SOLID principles, which is something that I would also like to avoid.
Not the cleanest solution, but here's what I managed to do.
First create a ViewModelClassesMapper to help map an abstract class to a concrete one. I'm using a custom AbsViewModel in my case, but this can be swapped out for the regular ViewModel. Then create a custom view model provider that depends on the above mapper.
class VMClassMapper #Inject constructor (private val vmClassesMap: MutableMap<Class<out AbsViewModel>, Provider<KClass<out AbsViewModel>>>) : VMClassMapperInterface {
#Suppress("TYPE_INFERENCE_ONLY_INPUT_TYPES_WARNING")
override fun getConcreteVMClass(vmClass: Class<out AbsViewModel>): KClass<out AbsViewModel> {
return vmClassesMap[vmClass]?.get() ?: throw Exception("Concrete implementation for ${vmClass.canonicalName} not found! Provide one by using the #ViewModelKey")
}
}
interface VMClassMapperInterface {
fun getConcreteVMClass(vmClass: Class<out AbsViewModel>) : KClass<out AbsViewModel>
}
interface VMDependant<VM : AbsViewModel> : ViewModelStoreOwner {
fun getVMClass() : KClass<VM>
}
class VMProvider #Inject constructor(private val vmMapper: VMClassMapperInterface) : VMProviderInterface {
#Suppress("UNCHECKED_CAST")
override fun <VM : AbsViewModel> provideVM(dependant: VMDependant<VM>): VM {
val concreteClass = vmMapper.getConcreteVMClass(dependant.getVMClass().java)
return ViewModelProvider(dependant).get(concreteClass.java) as VM
}
}
interface VMProviderInterface {
fun <VM :AbsViewModel> provideVM(dependant: VMDependant<VM>) : VM
}
#Module
#InstallIn(SingletonComponent::class)
abstract class ViewModelProviderModule {
#Binds
abstract fun bindViewModelClassesMapper(mapper: VMClassMapper) : VMClassMapperInterface
#Binds
#Singleton
abstract fun bindVMProvider(provider: VMProvider) : VMProviderInterface
}
Then, map your concrete classes using the custom ViewModelKey annotation.
#Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
#kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
#MapKey
annotation class ViewModelKey(val value: KClass<out AbsViewModel>)
#Module
#InstallIn(SingletonComponent::class)
abstract class ViewModelsDI {
companion object {
#Provides
#IntoMap
#ViewModelKey(MainContracts.VM::class)
fun provideConcreteClassForMainVM() : KClass<out AbsViewModel> = MainViewModel::class
#Provides
#IntoMap
#ViewModelKey(SecondContracts.VM::class)
fun provideConcreteClassForSecondVM() : KClass<out AbsViewModel> = SecondViewModel::class
}
}
interface MainContracts {
abstract class VM : AbsViewModel() {
abstract val textLiveData : LiveData<String>
abstract fun onUpdateTextClicked()
abstract fun onPerformActionClicked()
}
}
interface SecondContracts {
abstract class VM : AbsViewModel()
}
Finally, your fragment using the abstract view model looks like this:
#AndroidEntryPoint
class MainFragment : Fragment(), VMDependant<MainContracts.VM> {
#Inject lateinit var vmProvider: VMProviderInterface
protected lateinit var vm : MainContracts.VM
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
vm = vmProvider.provideVM(this)
}
override fun getVMClass(): KClass<MainContracts.VM> = MainContracts.VM::class
}
It's a long way to go, but after you have the initial setup is completed, all you need to do for individual fragments is to make them implement VMDependant and provide a concrete class for YourAbsViewModel in Hilt using the #ViewModelKey.
In tests, vmProvider can then be easily mocked and forced to do your bidding.

Is there a way to use injectors in a non-(Activity,Service,Fragment, Application) class

We're using Dagger2 in our application. I am trying to do a room database and I am writing the repository code, but I would like to inject application context and the DAO for the class.
I have a feeling that you can only do Dagger injection in Fragments, Activities, Services, Applications, etc.
Here's what I have:
class DownloadsDataRepositoryImpl : IDownloadsDataRepository, HasAndroidInjector {
#Inject
lateinit var androidInjector : DispatchingAndroidInjector<Any>
#Inject
lateinit var downloadsDao: DownloadsDao
override fun androidInjector(): AndroidInjector<Any> = androidInjector
init {
androidInjector()
}
}
But I'm sure it's not going to work. Is there a way to do it?
As stated, dagger-android is just a tool to help injecting specific framework classes that you can't have control on it's creation.
The proper approach is to use simple construction injection.
To be more direct on how you should expose it on your #Component, I would need more code, specifically on what you have on your activity/fragment, but here is a crude example (that I did not tested, if there are minor errors, you can fix them following the compiler error messages):
First, you will have some object that exposes your DAO. Probably it's room?
#Entity(tableName = "download_table")
data class DownloadEntity(
#PrimaryKey
val key: String
)
#Dao
interface DownloadsDao {
#Query("SELECT * FROM download_table")
fun load(): List<DownloadEntity>
}
#Database(
entities = [DownloadEntity::class], version = 1
)
abstract class DownloadRoomDatabase : RoomDatabase() {
abstract val downloadsDao: DownloadsDao
}
Now we will create a crude repository that is build with #Inject annotation. Dagger will take care of building this object for us. Notice that I am not using dagger-android for it:
interface IDownloadsDataRepository
class DownloadsDataRepositoryImpl #Inject constructor(
val downloadsDao: DownloadsDao
) : IDownloadsDataRepository
How to expose it to your activity/fragment/service requires more details on your implementation. For example, if it's inside a ViewModel or a Presenter that is annotated with #Inject or you are accessing directly on your activity will result in different implementations. Without more details, I will suppose that you are accessing the repository directly on your activity:
class DownloadActivity : FragmentActivity() {
#Inject
lateinit val repo: IDownloadsDataRepository
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DaggerDownloadComponent.factory().create(this).inject(this)
}
}
Now we need to instruct Dagger on how to:
Bind your concrete DownloadsDataRepositoryImpl to the IDownloadsDataRepository interface that the activity requires
How to provide the dependencies to build DownloadsDataRepositoryImpl
For this we will need a module:
#Module
abstract class RepositoryModule {
//We will bind our actual implementation to the IDownloadsDataRepository
#Binds
abstract fun bindRepo(repo: DownloadsDataRepositoryImpl): IDownloadsDataRepository
#Module
companion object {
//We need the database to get access to the DAO
#Provides
#JvmStatic
fun provideDataBase(context: Context): DownloadRoomDatabase =
Room.databaseBuilder(
context,
DownloadRoomDatabase::class.java,
"download_database.db"
).build()
//With the database, we can provide the DAO:
#Provides
#JvmStatic
fun provideDao(db: DownloadRoomDatabase): DownloadsDao = db.downloadsDao
}
}
With this, we can finish the last part of our puzzle, creating the #Component:
#Component(
modules = [
RepositoryModule::class
]
)
interface DownloadComponent {
fun inject(activity: DownloadActivity)
#Component.Factory
interface Factory {
fun create(context: Context): DownloadComponent
}
}
Notice that I did not use any dagger-android code, I don't think it's useful and causes more confusion than necessary. Stick with basic dagger2 constructs and you are fine. You can implement 99.9% of your app only understanding how those constructs works:
#Module, #Component and #Subcomponent
Edit: As stated in the comments, probably you will need to properly manage the scope of your repository, specially the DB creation if you are actually using Room.
Not sure how you implemented dagger, but here is an example how you can provide context to non activity class.
Suppose you have AppModule class, so there you can add provideContext() method:
#Module
class AppModule(app: App) {
private var application: Application = app
#Provides
fun provideContext(): Context {
return application
}
}
and here is non activity class written in Kotlin:
class Utils #inject constructor(private val context: Context) {
..
}
And that's it, just rebuild j
I have a feeling that you can only do Dagger injection in Fragments, Activities, Services, Applications, etc.
You were correct to assume that before Dagger-Android 2.20, but not after 2.20+.
Now you can create a #ContributesAndroidInjector for any class, which will generate an AndroidInjector<T> for that T for which you added #ContributesAndroidInjector.
This means that there is a multi-binding that allows you to get an AndroidInjector<T> for a T, and this is what HasAndroidInjector does for you.
So the following worked for me in a different scenario (for member-injecting Workers in work-manager, instead of creating a multi-binding and a factory):
#Keep
class SyncWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
init {
val injector = context.applicationContext as HasAndroidInjector
injector.androidInjector().inject(this)
}
#Inject
lateinit var apiService: ApiService
and
#ContributesAndroidInjector
abstract fun syncWorker(): SyncWorker
HOWEVER in your particular case, none of this is required.
Dagger-Android is for member-injecting classes using an auto-generated subcomponent, that you typically need only if your injected type is inside a different module, and therefore you can't directly add fun inject(T t) into your AppComponent, OR you don't see your AppComponent.
In your case, simple constructor injection is enough, as you own your own class.
#Singleton
class DownloadsDataRepositoryImpl #Inject constructor(
private val downloadsDao: DownloadsDao
): IDownloadsDataRepository {}
Which you can bind via a module
#Module
abstract class DownloadsModule {
#Binds
abstract fun dataRepository(impl: DownloadsDataRepositoryImpl): IDownloadsDataRepository
}
And otherwise you just create your component instance inside Application.onCreate()
#Component(modules = [DownloadsModule::class])
#Singleton
interface AppComponent {
fun dataRepository(): DownloadsDataRepository
#Component.Factory
interface Factory {
fun create(#BindsInstance appContext: Context): AppComponent
}
}
And
class CustomApplication: Application() {
lateinit var component: AppComponent
private set
override fun onCreate() {
super.onCreate()
component = DaggerAppComponent.factory().create(this)
}
}
Then you can get it as
val component = (context.applicationContext as CustomApplication).component
Though technically you may as well create an extension function
val Context.appComponent: AppComponent
get() = (applicationContext as CustomApplication).component
val component = context.appComponent

Dagger2.10+: Inject ViewModel in Fragment/Activity which has run-time dependencies

For ViewModels which has only compile-time dependencies, I use the ViewModelProvider.Factory from Architecture components like following:
class ViewModelFactory<T : ViewModel> #Inject constructor(private val viewModel: Lazy<T>) : ViewModelProvider.Factory {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T = viewModel.get() as T
}
And in my Activity or Fragment I get the ViewModel in following way:
#Inject
lateinit var viewModelFactory: ViewModelFactory<ProductsViewModel>
This is working fine until my ViewModel needs a dependency which is only available at run-time.
Scenario is, I have a list of Product which I am displaying in RecyclerView. For each Product, I have ProductViewModel.
Now, the ProductViewModel needs variety of dependencies like ResourceProvider, AlertManageretc which are available compile-time and I can either Inject them using constructor or I can Provide them using Module. But, along with above dependencies, it needs Product object as well which is only available at run-time as I fetch the list of products via API call.
I don't know how to inject a dependency which is only available at run-time. So I am doing following at the moment:
ProductsFragment.kt
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
productsAdapter = ProductsAdapter(context!!, products, R.layout.list_item_products, BR.productVm)
rvProducts.layoutManager = LinearLayoutManager(context)
rvProducts.addItemDecoration(RecyclerViewMargin(context, 10, 20))
rvProducts.adapter = productsAdapter
getProducts()
}
private fun getProducts() {
productsViewModel.getProducts()
.observe(this, Observer { productResponse: GetProductResponse ->
products.clear()
productsAdapter?.notifyDataSetChanged()
val productsViewModels = productResponse.data.map { product ->
// Here product is fetched run-time and alertManager etc are
// injected into Fragment as they are available compile-time. I
// don't think this is correct approach and I want to get the
// ProductViewModel using Dagger only.
ProductViewModel(product, resourceProvider,
appUtils, alertManager)
}
products.addAll(productsViewModels)
productsAdapter?.notifyDataSetChanged()
})
}
ProductsAdapter binds the ProductViewModel with the list_item_products layout.
As I mentioned in comments in the code, I don't want to create ProductViewModel my self and instead I want it from dagger only. I also believe the correct approach would be to Inject the ProductsAdapter directly into the Fragment, but then also, I need to tell dagger from where it can get Product object for ProductViewModel which is available at run time and it ends up on same question for me.
Any guide or directions to achieve this would be really great.
You are on the right direction in wanting to inject dependencies instead of creating them like you are doing with ProductViewModel. But, yes, you can't inject ProductViewModel as it needs a Product which is only available a runtime.
The solution to this problem is to create a Factory of ProductViewModel:
class ProductViewModel(
val product: Product,
val resourceProvider: ResourceProvider,
val appUtils: AppUtils,
val alertManager: AlertManager
) {
// ...
}
class ProductViewModelFactory #Inject constructor(
val resourceProvider: ResourceProvider,
val appUtils: AppUtils,
val alertManager: AlertManager
) {
fun create(product: Product): ProductViewModel {
return ProductViewModel(product, resourceProvider, appUtils, alertManager)
}
}
Then inject ProductViewModelFactory in your ProductsFragment class, and call productViewModelFactory.create(product) when the Product is available.
As your project start getting bigger and you see this pattern repeating, consider using AssistedInject to reduce the boilerplate.

How to initialize/inject generic ViewModel in BaseActivity by Koin injection on Android/Kotlin App

I'm building the architecture of a new Android application using Kotlin and Android Architecture Components (ViewModel, LiveData) and I'm also using Koin as my dependency injection provider.
The problem is that I'm not been able to initialize the ViewModel in a generic way inside my BaseActivity via koin injection. The current code looks like this:
abstract class BaseActivity<ViewModelType : ViewModel> : AppCompatActivity() {
// This does not compile because of the generic type
private val viewModel by lazy {
// Koin implementation to inject ViewModel
getViewModel<ViewModelType>()
}
#CallSuper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Fabric.with(this, Crashlytics())
}
/**
* Method needed for Calligraphy library configuration
*/
#CallSuper
override fun attachBaseContext(newBase: Context) {
super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase))
}
}
I'd like to know if is there a way to do this in Kotlin because I'm pretty sure I would be able to do in Java easily.
Thanks.
The solution was provided by the koin team in version 0.9.0-alpha-11 and the final code looks like this:
open class BaseActivity<out ViewModelType : BaseViewModel>(clazz: KClass<ViewModelType>) :
AppCompatActivity() {
val viewModel: ViewModelType by viewModel(clazz)
fun snackbar(message: String?) {
message?.let { longSnackbar(find(android.R.id.content), it) }
}
fun toast(message: String?) {
message?.let { longToast(message) }
}
}
Here is example of not passing Class and Generic to base implementation
In your base fragment/activity:
abstract class BaseFragment<T : BaseViewModel> : Fragment() {
...
#Suppress("UNCHECKED_CAST")
private val clazz: KClass<T> = ((this.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class<T>).kotlin
protected val viewModel: T by viewModel(clazz = clazz)
...
}
It looks ugly, but it works.
you can use a delegate version declaration for your ViewModel and avoid using directly a lazy expression. Try with this:
abstract class BaseActivity<T : ViewModel> : AppCompatActivity() {
val model by viewModel<T>()
}
This will give you a lazy of
getViewModel<T>()
Throw an eye on the quick ref: https://insert-koin.io/docs/1.0/getting-started/android-viewmodel/
Hope it will help.

Categories

Resources