I have a repository where a chain of network requests is calling. The repository is accessed from the interactor. And interactor is accessed from viewModel. The view model is attached to activity A. If I go to activity B, which has its own viewModel, then the request chain in the repository of activity A does not complete its execution.
Is it possible to make a repository whose life cycle will be equal to the life cycle of the application. I need all requests to complete even if I go to a new activity.
Please, help me.
This is covered in the Coroutines guide on developer.android.com
class ArticlesRepository(
private val articlesDataSource: ArticlesDataSource,
private val externalScope: CoroutineScope,
private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
) {
// As we want to complete bookmarking the article even if the user moves
// away from the screen, the work is done creating a new coroutine
// from an external scope
suspend fun bookmarkArticle(article: Article) {
externalScope.launch(defaultDispatcher) {
articlesDataSource.bookmarkArticle(article)
}
.join() // Wait for the coroutine to complete
}
}
Here externalScope is defined like this (for a scope with application lifetime):
class MyApplication : Application() {
// No need to cancel this scope as it'll be torn down with the process
val applicationScope = CoroutineScope(SupervisorJob() + otherConfig)
}
You should create a singleton Repository class, then access its instance anywhere you want.
object Repository {
val instance: Repository
get() {
return this
}
}
You can create create its object in ViewModel for A and create object in ViewModel for B.
Both will have same instance for Repository class, so in this way you can achieve what you need.
Related
I'm trying to use MVVM with ViewModel, ViewBinding, Retrofit2 in Android/Kotlin.
I don't know how to move application logic from ViewModel. I can't just move methods with logic because they run on viewModelScope and put results into observable objects in my ViewModel.
Or maybe I can?
For example I have some ArrayList (to show on some ListView).
// items ArrayList
private val _stocktakings =
MutableLiveData<ArrayList<InventoryStocktakingWithCountsDto?>>(ArrayList())
val stocktakings : LiveData<ArrayList<InventoryStocktakingWithCountsDto?>> get() =
_stocktakings
// selected item
private val _selected_stocktaking = MutableLiveData<Int>>
val selected_stocktaking : LiveData<Int> get() = _selected_stocktaking
And a function that is called from my fragment:
public fun loadStocktakings() {
viewModelScope.launch {
Log.d(TAG, "Load stocktakings requested")
clearError()
try {
with(ApiResponse(ApiAdapter.apiClient.findAllStocktakings())){
if (isSuccessful && body != null){
Log.d(TAG, "Load Stocktakings done")
setStocktakings(body)
} else {
val e = "Can't load stocktakings, API error: {${errorMessage}}"
Log.e(TAG, e)
HandleError("Can't load stocktakings, API error: {${e}}") // puts error message into val lastError MutableLiveData...
}
}
} catch (t : Throwable) {
Log.e(TAG, "Can't load stocktakings, connectivity error: ${t.message}")
HandleError("Can't load stocktakings, API error: {${e}}") // puts error message into val lastError MutableLiveData...
}
}
}
Now I want to add another function that changes some field in one of stocktakings. Maybe something like:
public fun setSelectedStocktakingComplete() {
stocktakings.value[selected_stocktaking.value].isComplete = true;
// call some API function... another 15 lines of code?
}
How to do it properly?
I feel I have read wrong tutorials... This will end with fat viewmodel cluttered with viewModelScope.launch, error handling and I can't imagine what will happen when I start adding data/form validation...
Here, some tip for that
Make sure the ViewModel is only responsible for holding and managing
UI-related data.
Avoid putting business logic in the ViewModel. Instead, encapsulate
it in separate classes, such as Repository or Interactor classes.
Use LiveData to observe data changes in the ViewModel and update the
UI accordingly.
Avoid making network or database calls in the ViewModel. Instead,
use the Repository pattern to manage data operations and provide the
data to the ViewModel through a LiveData or other observable object.
Make sure the ViewModel does not hold context references, such as
Activity or Fragment.
Use a ViewModel factory to provide dependencies to the ViewModel, if
necessary.
you can ensure that your ViewModel is simple, easy to test,
and scalable. It also makes it easier to maintain your codebase, as
the business logic is separated from the UI logic.
hope you understand
I have an repository that contains an in-memory cache list inside a StateFlow. The problem is that whenever the user logs out and logs into another account, the old data from the previous user is still there.
object Repository {
private lateinit var remoteDataSource: RemoteDataSource
operator fun invoke(remoteDataSource: remoteDataSource) {
this.remoteDataSource = remoteDataSource
return this
}
private val myList = MutableStateFlow(listOf<myData>())
suspend fun getData(): Flow<List<myData>> =
withContext(Dispatchers.IO) {
if (myList.value.isEmpty()) {
val response = remoteDataSource.getData()
if (response != null) {
myList.value = response.map { it.toMyData() }
}
}
myList
}
suspend fun addData(newData: MyData) =
withContext(Dispatchers.IO) {
myList.value = myList.value.plus(newData)
remoteDataSource.addData(myData.toMyDataRequest())
}
}
This repository is used by multiple ViewModels. The list itself is only observed by one screen (let's call it myFragment), but other screens can add new elements to it. I've tried to clear the repository on myFragment's onDestroyView, but it clears the list whenever the user navigates away from myFragment (even when it's not a logout).
We could observe whenever the user logs out in an userRepository, but i don't know how to observe data in one repository from another repository (there's nothing like viewModelScope.launch to collect flows or something like that).
What approach can be used to solve this? And how would it clear the list?
i don't know how to observe data in one repository from another repository
I'd argue you shouldn't in this case.
You have a use-case: Logout.
When you invoke this use-case, you should perform al the necessary operations that your app requires. In this case, you should call your repository to let it know.
suspend fun clearData() =
withContext(Dispatchers.IO) {
// clear your data
}
I'd argue that you shouldn't hardcode the Dispatcher, since you'll likely test this at some point; in your tests you're going to use TestDispatcher or similar, and if you hardcode it, it will be harder to test. You write tests, right?
So now your use case..
class LogoutUseCase(repo: YourRepo) {
operator fun invoke() {
repo.clearData()
//do the logout
}
}
That's how I would think about this.
Your scope for all this is the UI that initiated the logout...
There is a lot of information out there on architecture components, kotlin and coroutines but nowhere I can find an example using all those things together.
I'm struggling on how to use android's architecture components as described here together with coroutines. I have an idea but feel uncertain if it's the correct way of implementating this architectural style.
I'm trying to use the view model + repository pattern together with retro fit and coroutines.
I have the following repository:
class FooRepostiroy(private val fooHttpService: FooHttpService) {
suspend fun someMethod() : SomeResult {
val response = fooHttpService.someRemotCall() // which is also a suspending method using retrofit-2
// process response, store it using room and return SomeResult data object
Then I use the FooRepository from my ViewModel but because someMethod is a suspending method I need to wrap it in a coroutine scope:
class FooViewModel(private val fooRepositoru : FooRepository) : ViewModel() {
private var someMethodJob : Job? = null
val result : MutableLiveData<SomeResult> = MutableLiveData()
fun someMethod() {
someMethodJob = viewModelScope.launch {
result.value = fooRepositoru.someMethod()
}
}
override fun onCleared() {
super.onCleared()
someMethodJob?.cancel()
}
Then in the fragment or activity I can observe the view model result
fooViewModel.result.observe(viewLifecycleOwner, Observer {
Starting from my repository layer and below everything can be a suspending function. Then from the view model I can call any suspending function but never have a publicly exposed suspending function in my view model.
Is this the correct or proper way to incorporate coroutines with the view model architecture ?
Is this the correct or proper way to incorporate coroutines with the view model architecture?
Yes!
Every instance of ViewModel has its own ViewModelScope.
The purpose of ViewModelScope is to run the jobs during the life cycle of that ViewModel and take care of automatic cancelation of running coroutine jobs in case the parent Activity/Fragment of ViewModel is destroyed.
Any running jobs under ViewModelScope will be canceled when the ViewModel will be destroyed.
Read more here
private var someMethodJob : Job? = null
val result : MutableLiveData<SomeResult> = MutableLiveData()
fun someMethod() {
someMethodJob = viewModelScope.launch {
result.value = fooRepositoru.someMethod()
}
}
You can ditch all of that and just say
val result: LiveData<SomeResult> = liveData {
emit(fooRepository.someMethod())
}
And then observe result.
we are using in our project KOIN like DI library.
in some cases, when ViewModel instance not refreshing when Koin context is killing and recreating again. We need to implement feature like 'reassembling dependency graph in runtime', and this issue very critical for us.
I have ViewModel module like this:
object ViewModelModule {
val module by lazy {
module {
viewModel { AppLauncherViewModel(get(), get(), get(), get()) }
viewModel { AuthLoginPasswordViewModel(get(), get()) }
viewModel { SettingsViewModel(get(), get()) }
// some others
}
}
}
And my graph is assembling in android application by this way:
private fun assembleGraph() {
val graph = listOf(
AppModule.module,
StorageModule.module,
DatabaseConfigModule.module,
RepositoryModule.module,
InteractorModule.module,
ViewModelModule.module
)
application.startKoin(application, platformGraph)
}
fun reassembleGraph() {
stopKoin()
assembleGraph()
}
And when reassembleGraph() is calling - all good, another instances in graph are refreshing, but ViewModels, that injected in activity - are not, and they are keeping old references. I guess, that viewmodel is attached to activity lifecycle, and could help activity recreation, but i think it's not the best solution.
Has anyone the same problems? And help me please with advice, how to solve it, please.
You can do it with the use of scope in KOIN.
1) Define your ViewModels in scope
scope(named("ViewModelScope")){
viewModel {
AppLauncherViewModel(get(), get(), get(), get())
AuthLoginPasswordViewModel(get(), get())
SettingsViewModel(get(), get())
}
}
2) Create that particular scope with the use of below line in your application class.
val viewModelScope = getKoin().getOrCreateScope("ViewModelScope")
Above code is used to get ViewModel. And when you want to recreate scope you just need to close scope and recreate again. To close scope use below code.
val viewModelScopeSession = getKoin().getOrCreateScope("ViewModelScope")
viewModelScopeSession.close()
Once the scope is closed then after whenever you request to create or get scope at that time it will return new instance as per your requirement.
For further reference, you can see below link (8th Point).
Koin documentation
I just started using MVVM architecture on Android. I have a service which basically fetches some data and updates the UI and this is what I understood from MVVM:
Activity should not know anything about the data and should take care of the views
ViewModels should not know about activity
Repository is responsible for getting the data
Now as ViewModels should not know anything about the activity and Activities should not do anything other than handling views, Can anyone please tell where should I start a service?
In MVVM, ideally, the methods to start a service should be defined in Repository since it has the responsibility to interact with Data Source. ViewModel keeps an instance of Repository and is responsible for calling the Repository methods and updating its own LiveData which could be a member of ViewModel. View keeps an instance of ViewModel and it observes LiveData of ViewModel and makes changes to UI accordingly. Here is some pseudo-code to give you a better picture.
class SampleRepository {
fun getInstance(): SampleRepository {
// return instance of SampleRepository
}
fun getDataFromService(): LiveData<Type> {
// start some service and return LiveData
}
}
class SampleViewModel {
private val sampleRepository = SampleRepository.getInstance()
private var sampleLiveData = MutableLiveData<Type>()
// getter for sampleLiveData
fun getSampleLiveData(): LiveData<Type> = sampleLiveData
fun startService() {
sampleLiveData.postValue(sampleRepository.getDataFromService())
}
}
class SampleView {
private var sampleViewModel: SampleViewModel
// for activities, this sampleMethod is often their onCreate() method
fun sampleMethod() {
// instantiate sampleViewModel
sampleViewModel = ViewModelProviders.of(this).get(SampleViewModel::class.java)
// observe LiveData of sampleViewModel
sampleViewModel.getSampleLiveData().observe(viewLifecycleOwner, Observer<Type> { newData ->
// update UI here using newData
}
}
As far as I know, Services are Android related so, they could be started from View (Activity/Fragment/Lifecycleowner).