Using Dagger 2 singletons in View Models on Android - android

I want my application to have the following architecture :
-di // dependancy injection package
-model
--datasources // implementations of repositories, using dao to return data
--dao // access to the database (room)
--repositories // interfaces
-ui // activities, fragments...
-viewmodels // viewmodels of each fragment / activity which will return data to the views in the ui package
On Android, people seem to usually have the dependancy injection in the activities, but I want to inject my repositories in view models.
I have one particular viewmodel that uses a manually created LiveData
class NavigationViewModel(application: Application) : AndroidViewModel(application) {
init {
DaggerAppComponent.builder()
.appModule(AppModule(getApplication()))
.roomModule(RoomModule(getApplication()))
.build()
.injectNavigationViewModel(this)
}
#Inject
lateinit var displayScreenRepository: DisplayScreenRepository
fun setScreenToDisplay(screen: DisplayScreen) {
displayScreenRepository.setScreenToDisplay(screen)
}
}
In my RoomModule, I have this provider :
#Singleton
#Provides
internal fun displayScreenRepository(): DisplayScreenRepository {
return DisplayScreenDataSource()
}
The class DisplayScreenDataSource is very simple :
class DisplayScreenDataSource : DisplayScreenRepository {
private val screenToDisplay = MutableLiveData<DisplayScreen>()
override fun getScreenToDisplay(): LiveData<DisplayScreen> {
return screenToDisplay
}
override fun setScreenToDisplay(screen: DisplayScreen) {
if (Looper.myLooper() == Looper.getMainLooper()) {
screenToDisplay.value = screen
} else {
screenToDisplay.postValue(screen)
}
}
}
I want this singleton to be available to an other viewmodel, MainActivityViewModel, so I did this :
class MainActivityViewModel(application: Application) : AndroidViewModel(application) {
#Inject
lateinit var displayScreenRepository: DisplayScreenRepository
init {
DaggerAppComponent.builder()
.appModule(AppModule(application))
.roomModule(RoomModule(application))
.build()
.injectMainViewModel(this)
}
}
But when I run my application, the repositories instantiated don't have the same reference and when I update the value of one LiveData in one of my ViewModel, if I observe the LiveData of an other ViewModel, it is not the same LiveData so is is not updated.
My guess is that I am not correctly instantiating the DaggerAppComponent : it is supposed to be created only once and eventually injected several times.
Is that right?
Where should I be supposed to store the instance of the DaggerAppComponent? In the Application class?
I could have something like that :
class MainActivityViewModel(application: Application) : AndroidViewModel(application) {
#Inject
lateinit var displayScreenRepository: DisplayScreenRepository
init {
(application as MyApplication).daggerAppComponent.injectMainViewModel(this)
}
}
Is this the recommended way?

Related

Best way to use a Global String variable in Android App 2022?

I have a variable globalStringVar which I use throughout my application, including in different repositories in the app's data layer. I am trying to work out the best/cleanest way to pass this around my application.
Currently globalStringVar is instantiated in my Application() class. I then use Application().getAppInstance().myGlobalString in the repositories when I need to use it. I also update the variable in the repositories.
I feel like there is code smell here and a better way exists to store and use a global string variable. I tried to see if I could somehow inject the string using dagger/hilt but had no success. Does anybody have a better way of using global string variables?
#HiltAndroidApp
class Application : android.app.Application() {
var globalStringVar = "Start Value"
companion object {
private lateinit var applicationInstance: Application
#JvmStatic
fun getAppInstance(): Application {
return applicationInstance
}
}
override fun onCreate() {
super.onCreate()
applicationInstance = this
}
}
#Singleton
class MyRepo #Inject constructor() {
fun updateValue() {
Application.getAppInstance().globalStringVar = "Update Value"
}
/*other repo code*/
}
I have created my own custom class MyData() that holds globalString property.
This can then be injected where necessary with Dagger/Hilt.
class MyData() {
var globalString: String = "Start Value"
}
#Module
#InstallIn(SingletonComponent::class)
object DataModule {
#Singleton
#Provides
fun provideDataInstance(): MyData {
return MyData()
}
}
#Singleton
class MyRepo #Inject constructor() {
#Inject lateinit var myData: MyData
fun updateValue() {
myData.globalString = "Update Value"
}
/*other repo code*/
}

viewModel init{} block not called from Koin

I have an app which fetches data from an API that should be available globally within the application. This data should be fetched eagerly directly at the app start. I manage dependencies using Koin.
To achieve the desired behaviour, I created a ViewModel class that is tied to the Activity lifecycle using a AndroidViewModel instance:
class AppViewModel(
application: Application,
private val apiService: ApiService
) : AndroidViewModel(application) {
init {
getData();
Log.d("INIT", "Hooray, initialization worked!");
}
var data: List<DataObject> by mutableStateOf(listOf())
fun getData() {
viewModelScope.launch(Dispatchers.IO) {
//... consulting API service
}
}
}
I initialize this as a Koin module at the App class:
class App : Application() {
override fun onCreate() {
super.onCreate()
val appViewModel = module(createdAtStart = true) {
viewModel { AppViewModel(androidApplication(), get()) }
}
startKoin {
androidContext(this#App)
modules(/*...., */ appViewModel /*,.... */)
}
}
}
But whenever I start the application, I can see that the data request is not performed, the Log output does not appear, and the data is not present. What do I need to change so that Koin triggers the init block of the ViewModel? If this is not possible, what are alternative ways to achieve this?

share data between fragments using ModelFactory

I've successfully implemented repository based MVVM. However I need to pass a class object between fragments. I've implemented a sharedViewModel between multiple fragments but the set value always gives null. I know this is due to me not passing the activity context to the initialization of the viewmodels in fragments. I am working with ModelFactory to make instances of my viewmodel yet I can't figure out a way to give 'applicationActivity()' .
Here's my modelFactory:
class MyViewModelFactory constructor(private val repository: MyRepository): ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return if (modelClass.isAssignableFrom(MyOwnViewModel::class.java)) {
MyOwnViewModel(this.repository) as T
} else {
throw IllegalArgumentException("ViewModel Not Found")
}
}
and this is how I intialize my viewmodel:
viewModel=ViewModelProvider(this, MyViewModelFactory(
MyRepository(MyServices() ) )).get(MyOwnViewModel::class.java)
fetching data and everything else works, but I need to be able to share data between fragments and i can't do that with this architecture. I'm not using dagger or Hilt.
Thank you for any pointers.
You can use by activityViewModels() and pass the factory
private val myViewModel: MyViewModel by activityViewModels(factoryProducer = {
MyViewModelFactory(<your respository instance>)
})
It would be good idea to get your repository instance from a singleton or from a field in Application class. If you choose to get from an Application class you can do it like this;
class MyApp: Application() {
val service by lazy { MyService() }
val repository by lazy { MyRepository(service) }
}
by defining them lazy, it ensures that they are not initialized until its necessary
With your application class, your viewmodel call should look like this
private val myViewModel: MyViewModel by activityViewModels(factoryProducer = {
MyViewModelFactory((activity?.application as MyApp).repository)
})
You can also write viewmodelfactory this way
class MyViewModelFactory(internal var viewModel: ViewModel) : ViewModelProvider.Factory {
override fun create(modelClass: Class): T {
return viewModel as T
}
}
And for share data between fragment you can use bundle

how to get a ViewModel instance inside another ViewModel withKoin

I am new to koin and kotlin and I have just started using koin in my project and It is working quite good. I have two viewmodel classes, SubscritpionViewModel and LoginViewModel. Is there a way I can get instance of LoginViewModel inside SubscriptionViewModel. I don't know if it is right or not but it will be handy for me if I can access the other viewmodel.
val viewModule = module {
viewModel { SubscriptionViewModel(get(), get()) }
viewModel { LoginViewModel(get()) }
}
SubscriptionViewModel
class SubscriptionViewModel(val api: ServiceApi, var user: LoginViewModel) : BaseViewModel() {
...
}
I have also created a separate module for this, but I don't know what is the right way to initialize it.
val userModule = module {
single( definition = {
get<LoginViewModel>() })
}
I think it's a bad design. I think what you should do is to create a common object between LoginViewModel and SubscriptionViewModel and inject it via constructor to both LoginViewModel and SubscriptionViewModel. Maybe Repository pattern would be good? Please describe the functionality you want to implement so we can get the idea of why you need one ViewModel inside another. With repository you can do something like this:
class UserRepository(private val serviceApi: ServiceApi) {
}
class SubscriptionViewModel(val userRepository: UserRepository) : BaseViewModel() {
...
}
class LoginViewModel(val userRepository: UserRepository) : BaseViewModel() {
...
}
and in Koin module:
module {
single { UserRepository(get()) }
viewModel { SubscriptionViewModel(get()) }
viewModel { LoginViewModel(get()) }
}

How can i inject object into presenter in android kotlin MVP mosby app with dagger

I am trying to get dagger working in my application.
After creating Module Component and MyApp i can use dagger to inject database service into view but i am having trouble doing same thing with presenter.
Code:
class MyApp : Application() {
var daoComponent: DaoComponent? = null
private set
override fun onCreate() {
super.onCreate()
daoComponent = DaggerDaoComponent.builder()
.appModule(AppModule(this)) // This also corresponds to the name of your module: %component_name%Module
.daoModule(DaoModule())
.build()
}
}
Module
#Module
class DaoModule {
#Provides
fun providesEstateService(): EstateService = EstateServiceImpl()
}
Component
#Singleton
#Component(modules = arrayOf(AppModule::class, DaoModule::class))
interface DaoComponent {
fun inject(activity: MainActivity)
}
AppModule
#Module
class AppModule(internal var mApplication: Application) {
#Provides
#Singleton
internal fun providesApplication(): Application {
return mApplication
}
}
MainActivity
class MainActivity : MvpActivity<MainView, MainPresenter>(), MainView {
#Inject
lateinit var estateService : EstateService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
(application as MyApp).daoComponent!!.inject(this)estateService.numberOfInvoicedEstates.toString()
}
override fun createPresenter(): MainPresenter = MainPresenterImpl()
}
After injecting estateService this way I can use it, but I cant figure out how do I inject service directly into the presenter.
I tried doing it like this but it isn't working.
Should I just pass injected objects from the activity? or maybe I should pass MyApp as an argument or make static method allowing my to get it from any place in the application?
class MainPresenterImpl
#Inject
constructor(): MvpBasePresenter<MainView>(),MainPresenter {
#Inject
lateinit var estateService : EstateService
}
Your component should provide the presenter like that:
#Component(modules = arrayOf(AppModule::class, DaoModule::class))
#Singleton
interface MyComponent {
mainPresenter() : MainPresenter
}
And all dependencies the presenter needs are injected to the presenter via constructor parameters:
class MainPresenterImpl
#Inject constructor(private val estateService : EstateService ) :
MvpBasePresenter<MainView>(),MainPresenter {
...
}
Than in createPresenter() just grab the presenter from dagger component like this:
class MainActivity : MvpActivity<MainView, MainPresenter>(), MainView {
...
override fun createPresenter(): MainPresenter =
(application as MyApp).myComponent().mainPresenter()
}
Please note that the code shown above will not compile. This is just pseudocode to give you an idea how the dependency graph could look like in Dagger.
Please refer to this example on how to use Dagger 2 in combination with MVP.
You have to tell your component where you want to inject it.
So, try with this component:
#Singleton
#Component(modules = arrayOf(AppModule::class, DaoModule::class))
interface DaoComponent {
fun inject(presenter: MainPresenterImpl)
}

Categories

Resources