This is my first mvvm project with koin and I'm using Room database. I'm making network calls in viemwodel, and after fetching data from the api I want to store it in the database. Below is my class which has all the db methods like insert and delete.
class UserViewModel(application: Application) : AndroidViewModel(application) {
private val userSDAO: UserDAO
private val userDB: AppSettingsDatabase
init {
userDB = AppSettingsDatabase.getAppSettingsDatabase(application.applicationContext)!!
userDAO = userDB.userDao()
}
fun getAppSetting():LiveData<AppSettingsEntity>{
return userDB.appSettingDao().getAllAppSettings()
}
fun deleteUser() {
userDB.databaseWriteExecutor.execute { ->
userDAO.deleteUser()
}
}
}
I was calling this class from activity like this
userViewModel = ViewModelProvider(this).get(UserViewModel::class.java)
now I want to call this inside a viewmodel class where I am making the network api calls and I don't know what is the best approach to access it using koin, or any other way.I have different methods where I need database access and I have to initialize it like above in every method.
class SubscriptionViewModel(): BaseViewModel() {
fun init(owner:ViewModelStoreOwner) {
userServiceViewModel = ViewModelProvider(owner).get(UserServiceViewModel::class.java)
}
}
In general, it's a better pattern to not accessing a db object in ViewModel. I mean the dao should be used in a data source class, then the data source would be injected in the ViewModel or even better, use the data source in a repository, then inject the repository in the ViewModel.
After that, you must not access a ViewModel inside another one. They should be independent. If you want to do something with the db or api in multiple ViewModels, access to them through a common repository class.
Please take a look at: https://developer.android.com/jetpack/guide#overview
Related
how can I make Dto map and pass it to the use-case using clean architecture in android kotlin
i tried to make an object class and pass it to the use-case but it didn't work
Note that I will use HILT for dependency injection ... you can use any di library.
You can make a base interface mapper for objects like this:
interface mapper <T,R> {
fun map(t: T): R
}
Then create a class that implements Mapper interface:
class ProductMapper #Inject constructor() : Mapper<RemoteProduct, ProductModel>() {
override fun map(t: RemoteProduct): ProductModel {
// Pass any argument you want or do some business logic
return ProductModel(t.id, t.name)
}
Now I Suggest injecting this ProductMapper inside the repo when you get the response from API or locale database then return mapped data to the use-case.
But anyway you can inject this mapper inside use-case like this:
class GetProductsUseCase #Inject constructor (private val productsMapper: ProductsMapper) {
// Don't forget to inject your repo too
operator fun invoke(): ProductMode {
return productsMapper.map(repo.getProduct())
}
}
Finally using Hilt create new module to bind ProductMapper in our case there is no need to do that step but if your mapper depends on another classes you need to add it in module like this:
#Module
#InstallIn(SingltonComponent::Class)
interface AppModule {
#Binds
fun bindProductDetailsMapper(productsMapper: ProductsMapper): Mapper
}
I am trying to understand how can I use my same viewmodel across different fragments which even belongs to different activities.
So let's say I have Activity1 with Fragment A, Fragment B and Activity2 with Fragment C. How do I create a single instance of viewmodel that I can use across all these fragments.
I tried understanding shared viewmodel but seems like it is to be used if sharing data between fragments of a single activity and not multiple activities.
So basically I want to create a single instance of viewmodel across all the fragments? How can I achieve this functionality also keeping in mind the MVVM approach.
Any help would be appreciated. Thanks!
This is not supported. Google's recommendation is to put all your screens in a single Activity.
But, you can make an intermediate singleton class that each instance of the ViewModel uses.
Or maybe you could use a factory that treats it like a temporary singleton and does reference counting so it doesn't get cleared too early or hang onto the reference for too long. Untested example of what I mean:
private var viewModelInstance: MyViewModel? = null
private var refCount = 0
class MyViewModel: ViewModel() {
override fun onCleared() {
if (--refCount > 0) {
return
}
viewModelInstance = null
// Do typical onCleared cleanup here
}
}
class MyViewModelFactory: ViewModelProvider.Factory {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
require(modelClass.isAssignableFrom(MyViewModel::class.java)) { "Factory only supports MyViewModel" }
refCount++
viewModelInstance = viewModelInstance ?: MyViewModel()
return viewModelInstance as T
}
}
Shared viewmodel between multiple activities is not supported.
One way to achieve this using AndroidViewModel. You can create a ViewModel extending AndroidViewModel . This requires application instance. This viewmodel will be binded to application lifecycle and same instance will be available through out the lifecycle of the application. In one activity you can add data, and in other activity you can get updated data.
This will be acting something like singleton instance(But not exactly).
Addition to this, you can also use live data in AndroidViewModel if you use observer with activity/fragment lifecycle owner. So the observer will be live only till life cycle of fragment or activity.
ViewModel:
class AppViewModel constructor(private val mApplication: Application) :
AndroidViewModel(mApplication) {
//ViewModel Logic
}
Initializing Viewmodel:
You can initialize viewmodel like this in any fragment or activity.
val appViewModel = ViewModelProvider.AndroidViewModelFactory.getInstance(MyApp.getInstance())
.create(AppViewModel::class.java)
Application Class:
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
mInstance = this
}
companion object {
lateinit var mInstance: CooperApp
#Synchronized
fun getInstance(): MyApp {
return mInstance
}
}
}
Also one more thing we can use is like initializing viewmodel in application class and create similar function to getInstance() which will return viewmodel instance and use it all over the app
I've started using Hilt since it got stable. I'm also new to DI (Dependency Injection).
My question is if I have a single class, say AdapterA, and I want to provide a new instance every time its DI (using #Inject) requests it.
I've used the following code to provide the instance, let me know if I need to add/remove anything to make it work as expected.
// Kotlin
#Module
#InstallIn(ActivityComponent::class)
class AdapterModule {
#Provides
#ActivityScoped
fun provideAdapterAInstace(): AdapterA {
return AdapterA()
}
}
Now, whenever I'm trying to inject the dependency multiple times in an activity then it always returns the same instance (like a static variable that stores once and reuses numerous times.
// Usage
class ActivityA extends AppCompatActivity {
#Inject
lateinit var instanceOne: AdapterA
#Inject
lateinit var instanceTwo: AdapterA
...
}
Help me understand what I did wrong or missed here.
Thanks in advance.
Scope annotations tell Dagger to only create one instance in the component with that scope. In this case, that means AdapterA will only be created once per activity. If you want to create a new instance of AdapterA every time, don't provide a scope.
#Module
#InstallIn(ActivityComponent::class)
class AdapterModule {
#Provides
// unscoped
fun provideAdapterAInstance(): AdapterA {
return AdapterA()
}
}
I try to create instance of ViewModel, but get an error.
java.lang.RuntimeException: Cannot create an instance of class com.example.event.ui.main.EventsViewModel
my code here to get instance:
viewModel = ViewModelProvider(this).get(EventsViewModel::class.java)
view model class
class EventsViewModel #Inject constructor(private val eventApi: EventApi): BaseViewModel() {
val getEventsData = MutableLiveData<Response<List<Event>>>()
fun getEventsData() {
uiScope.launch {
getEventsData.value = eventApi.getEvents()
}
}
}
Here, your ViewModelProvider is not aware of how to create EventsViewModel. If your ViewModel constructor was an empty constructor without any dependencies, it would work. However, if there is dependency or a parameter in your ViewModel constructor you need to provided a custom ViewModelProviderFactory.
If you want to check how/why custom view model factory is created you can check this codelab:
https://developer.android.com/codelabs/kotlin-android-training-view-model#7
If you want to employ dagger for injecting dependencies into your ViewModel, you can find an example in my sample repo:
https://github.com/hakanbagci/truemvvm
I am trying to get a value from the SharedViewModel class but the ViewModelProvider() is giving a parameter error when i am passing requireActivity() although the same initilization and assignment works in my fragments.
It is requiring "ViewModelStoreOwner" to be passed.
class CourseRepository(val app: Application) {
private var viewModel: SharedViewModel = ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)
val courseData = MutableLiveData<List<Course>>()
init {
CoroutineScope(Dispatchers.IO).launch {
callWebService()
}
}
#WorkerThread
suspend fun callWebService() {
if (Utility.networkAvailable(app)) {
val retrofit = Retrofit.Builder().baseUrl(WEB_SERVICE_URL).addConverterFactory(MoshiConverterFactory.create()).build()
val service = retrofit.create(CourseService::class.java)
val serviceData = service.getCourseData(viewModel.pathName).body() ?: emptyList()
courseData.postValue(serviceData)
}
}
}
The purpose of the ViewModel here is because i am storing the Id of the selected RecyclerView item in order to send it to a server
ViewModel instances are scoped to Fragments or Activities (or anything with a similar lifecycle), which is why you need to pass in a ViewModelStoreOwner to the provider to get a ViewModel from it. The point of ViewModels is that they will exist until the store they belong to is destroyed.
The requireActivity method doesn't work here, because you're not inside a Fragment.
Some things to consider here:
Do you really need ViewModel in this use case? Could you perhaps use just a regular class that you can create by calling its constructor?
Could you call this Repository from your ViewModel, and pass in any parameters you need from there?