I have a BaseViewModel class.
abstract class BaseViewModel : ViewModel() {
abstract fun sendLog(msg: String)
}
And now I need to inherit this in all the ViewModels. It's a lot of work.
I just want to implement once and use it in other ViewModels too.
So, Can I do this like this?
#HiltViewModel
abstract class BaseViewModel : ViewModel() {
#Inject construct(logger: Logger)
open fun sendLog(msg: String){
viewModelScope.launch{
logger.callApi(msg)
}
}
}
or is there any better way???
I assume that you have only one instance of Logger, so that you can get it lazyly. With a bit of Kotlins syntactic sugar and the ability to set a body for interface methods you can do something like this:
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
class Logger {
fun log(text: String) = println(text)
}
interface Foo {
fun ViewModel.logSomething(msg: String) {
viewModelScope.launch {
logger.log(msg)
}
}
companion object {
#Inject
lateinit var logger: Logger
}
}
#HiltViewModel
open class BaseViewModel : ViewModel(), Foo
class MainViewModel : BaseViewModel() {
init {
logSomething("That's a test")
}
}
If that doesn't fit your needs feel free to comment below
Related
I'm wondering, why my injecting works only in activity:
it works:
#AndroidEntryPoint
class MainActivity : ComponentActivity() {
#Inject lateinit var fakeRepo: FakeRepo
}
#Module
#InstallIn(ActivityComponent::class)
abstract class MainModule {
#Binds
abstract fun bindFakeRepo(fakeRepoImpl: FakeRepoImpl): FakeRepo
}
BUT when I'm trying inject this repo to viewModel this way it gives errors:
#Module
#InstallIn(MainActivityViewModel::class)
abstract class MainModule {
#Binds
abstract fun bindFakeRepo(fakeRepoImpl: FakeRepoImpl): FakeRepo
}
interface FakeRepo {
fun getData(): List<Int>
}
class FakeRepoImpl #Inject constructor(): FakeRepo {
override fun getData(): List<Int> {
return listOf(3,5,6)
}
}
#HiltViewModel
class MainActivityViewModel #Inject constructor(val fakeRepo: FakeRepo) : ViewModel() {
}
Why it doesn't work ? I get
#InstallIn, can only be used with #DefineComponent-annotated classes
Probably I can inject it using provide as singleton but this is not the solution.
Could someone explain why I can inject to activity but to viewmodel not using #Binds ?
Based on the Hilt tutorial, ViewModels needs to be inject the following way:
#HiltViewModel
class ExampleViewModel #Inject constructor(
private val savedStateHandle: SavedStateHandle,
private val repository: ExampleRepository
) : ViewModel() {
...
}
However, in my case, I want to use an interface:
interface ExampleViewModel()
#HiltViewModel
class ExampleViewModelImp #Inject constructor(
private val savedStateHandle: SavedStateHandle,
private val repository: ExampleRepository
) : ExampleViewModel, ViewModel() {
...
}
Then I want to inject it via the interface
#AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
private val exampleViewModel: ExampleViewModel by viewModels()
...
}
How to make this work?
viewModels requires child of ViewModel class
val viewModel: ExampleViewModel by viewModels<ExampleViewModelImp>()
Had a similar problem where I wanted to Inject the ViewModel via interface, primarily because to switch it with a fake implementation while testing. We are migrating from Dagger Android to Hilt, and we had UI tests that used fake view models. Adding my findings here so that it could help someone whose facing a similar problem.
Both by viewModels() and ViewModelProviders.of(...) expects a type that extends ViewModel(). So interface won't be possible, but we can still use an abstract class that extends ViewModel()
I don't think there is a way to use #HiltViewModel for this purpose, since there was no way to switch the implementation.
So instead, try to inject the ViewModelFactory in the Fragment. You can switch the factory during testing and thereby switch the ViewModel.
#AndroidEntryPoint
class ListFragment : Fragment() {
#ListFragmentQualifier
#Inject
lateinit var factory: AbstractSavedStateViewModelFactory
private val viewModel: ListViewModel by viewModels(
factoryProducer = { factory }
)
}
abstract class ListViewModel : ViewModel() {
abstract fun load()
abstract val title: LiveData<String>
}
class ListViewModelImpl(
private val savedStateHandle: SavedStateHandle
) : ListViewModel() {
override val title: MutableLiveData<String> = MutableLiveData()
override fun load() {
title.value = "Actual Implementation"
}
}
class ListViewModelFactory(
owner: SavedStateRegistryOwner,
args: Bundle? = null
) : AbstractSavedStateViewModelFactory(owner, args) {
override fun <T : ViewModel?> create(
key: String,
modelClass: Class<T>,
handle: SavedStateHandle
): T {
return ListViewModelImpl(handle) as T
}
}
#Module
#InstallIn(FragmentComponent::class)
object ListDI {
#ListFragmentQualifier
#Provides
fun provideFactory(fragment: Fragment): AbstractSavedStateViewModelFactory {
return ListViewModelFactory(fragment, fragment.arguments)
}
}
#Qualifier
annotation class ListFragmentQualifier
Here, ListViewModel is the abstract class and ListViewModelImpl is the actual implementation. You can switch the ListDI module while testing using TestInstallIn. For more information on this, and a working project refer to this article
Found a solution using HiltViewModel as a proxy to the actual class I wish to inject. It is simple and works like a charm ;)
Module
#Module
#InstallIn(ViewModelComponent::class)
object MyClassModule{
#Provides
fun provideMyClas(): MyClass = MyClassImp()
}
class MyClassImp : MyClass {
// your magic goes here
}
Fragment
#HiltViewModel
class Proxy #Inject constructor(val ref: MyClass) : ViewModel()
#AndroidEntryPoint
class MyFragment : Fragment() {
private val myClass by lazy {
val viewModel by viewModels<Proxy>()
viewModel.ref
}
}
Now you got myClass of the type MyClass interface bounded to viewModels<Proxy>() lifeCycle
It's so simple to inject an interface, you pass an interface but the injection injects an Impl.
#InstallIn(ViewModelComponent::class)
#Module
class DIModule {
#Provides
fun providesRepository(): YourRepository = YourRepositoryImpl()
}
Here is my ViewModel class
import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class ViewModel (private val context: Context) : ViewModel() {
private var leadslist = MutableLiveData<Response>()
init {
val Repository: Repository by lazy {
Repository
}
leadslist = Repository.getMutableLiveData(context)
}
fun getLeadsList(): MutableLiveData<Response> {
return leadslist
}
}
Here is how I am calling it in my Fragment.
viewmodel = ViewModelProvider(requireActivity()).get(ViewModel::class.java)
I know there are other answers already but nothing seems to work. Please help. Stuck on this for a pretty long time.
I have tried ViewModelFactory too.
ViewModelProvider(requireActivity(),ViewModelFactory(requireActivity())).get(ViewModel::class.java)
and used this code for ViewModelFactory class
import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
class ViewModelFactory(private val context: Context) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return ViewModel(context) as T
}
}
still, it doesn't work.
Then I tried this
viewmodel= ViewModelProvider.AndroidViewModelFactory(requireActivity().application)
.create(ViewModel::class.java)
and it didn't work again.
And how do create this zero-argument constructor in my ViewModel.
And why can't I create instance.
Also, with one of the approach I was able to compile and run but in that the viewmodel.observe function wasn't executing.
Please help. Thanks.
The parameter context must be initialized.
like:
class ViewModel (private val context: Context = requireActivity().application) : ViewModel()
I want to inject a class in Service. Lets have a look at the code below:
class DeviceUtil #Inject constructor() {
...
}
#AndroidEntryPoint
class LocationUpdateService : Service() {
#Inject
lateinit var deviceUtil: DeviceUtil
...
}
#Inject lateinit var deviceUtil: DeviceUtil is working fine in Activity but not working in Service.
Its giving the error: kotlin.UninitializedPropertyAccessException: lateinit property deviceUtil has not been initialized
For those dummies like me. As said by OP in the comments, a full example on how you can inject object in your service like so:
import android.app.Service
import android.content.Intent
import android.os.Binder
import android.os.IBinder
import android.util.Log
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import com.yourapp.services.UserService
#AndroidEntryPoint
class MyService: Service() {
#Inject lateinit var userService: UserService
override fun onCreate() {
super.onCreate()
userService.getUserList()
.subscribe { userList -> Log.d("tag", "users: $userList") }
}
override fun onBind(intent: Intent?): IBinder? {
return object: Binder() {
// ...
}
}
}
As for the service you're injecting make sure it has the #Inject annotation in its constructor like so:
class UserService #Inject() constructor() {
// ...
}
I am new to dagger 2. I was making a CarComponent on kotlin, I was trying to call my DaggerCarComponent with horsePower value without calling petrolEngineModule. the following is my code :
import dagger.BindsInstance
import dagger.Component
import javax.inject.Singleton
#Component (
modules = [WheelModule::class, PetrolEngineModule::class]
)
interface CarComponent {
fun getCar(): Car
fun inject(mainActivity: MainActivity)
#Component.Builder
interface Builder {
#BindsInstance
fun horsePower(horsePower : Int) : Builder
fun build(): CarComponent
}
}
this is PetrolEngine.kt:
package com.example.daggerapp
import android.util.Log
import javax.inject.Inject
class PetrolEngine : Engine {
private var horsePower : Int
#Inject constructor(horsePower: Int){
this.horsePower = horsePower
}
override fun start() {
Log.d("Engine", "Broom..., horsePower: ${this.horsePower}")
}
}
this is PetrolEngineModule.kt:
package com.example.daggerapp
import dagger.Module
import dagger.Provides
import javax.inject.Inject
#Module
class PetrolEngineModule {
private var horsePower: Int
#Inject constructor(horsePower: Int) {
this.horsePower = horsePower
}
#Provides
fun provideHorsePower(): Int {
return horsePower
}
#Provides
fun provideEngine(engine: PetrolEngine): Engine
{
return engine
}
}
I added the DaggerComponent here as DaggerCarComponent :
package com.example.daggerapp
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import javax.inject.Inject
class MainActivity : AppCompatActivity() {
#Inject
lateinit var car:Car
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val daggerCar: CarComponent = DaggerCarComponent.builder().petrolEngineModule(PetrolEngineModule(140)).build()
daggerCar.inject(this)
Log.d("Car instance", "$car")
car.drive()
}
}
I was following this tutorial: https://www.youtube.com/watch?v=3tIvekCTSJg&list=PLrnPJCHvNZuA2ioi4soDZKz8euUQnJW65&index=8
In your Builder:
#BindsInstance
Builder horsePower(#Named("horse_power") int horsePower);
After this you will be able to pass horsePower from MainActivity without passing instance of PetrolEngineModule.And same way in ur PetrolEngine constructor:
#Inject
public PetrolEngine(#Named("horse_power") int horsePower) {
this.horsePower = horsePower;
}
And in your PetrolEngineModule u can remove everything and just leave #Provides for PetrolEngine.
Remove #Inject in the module class, cause that what #BindsInstance is doing when pass horsePower it in #Component.Builder
Just an optimization for future viewers. Just replace the existing code with this. Now Dagger will not create the --Provide-- class to provide the instance of PetrolEngine.
Please improve the code if you find anything wrong.
#Module
abstract class PetrolEngineModule {
#Binds
abstract fun bindEngine(engine: PetrolEngine): Engine
}