Pass arguments from fragment to viewmodel function - android

Can you tell me if my approach is right? It works but I don't know if it's correct architecture. I read somewhere that we should avoid calling viewmodel function on function responsible for creating fragments/activities mainly because of screen orientation change which recall network request but I really need to pass arguments from one viewmodel to another one. Important thing is I'm using Dagger Hilt dependency injection so creating factory for each viewmodel isn't reasonable?
Assume I have RecyclerView of items and on click I want to launch new fragment with details - common thing. Because logic of these screens is complicated I decided to separate single viewmodel to two - one for list fragment, one for details fragment.
ItemsFragment has listener and launches details fragment using following code:
fun onItemSelected(item: Item) {
val args = Bundle().apply {
putInt(KEY_ITEM_ID, item.id)
}
findNavController().navigate(R.id.action_listFragment_to_detailsFragment, args)
}
Then in ItemDetailsFragment class in onViewCreated function I receive passed argument, saves it in ItemDetailsViewModel itemId variable and then launch requestItemDetails() function to make api call which result is saved to LiveData which is observed by ItemDetailsFragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
//...
val itemId = arguments?.getInt(KEY_ITEM_ID, -1) ?: -1
viewModel.itemId = itemId
viewModel.requestItemDetails()
//...
}
ItemDetailsViewModel
class ItemDetailsViewModel #ViewModelInject constructor(val repository: Repository) : ViewModel() {
var itemId: Int = -1
private val _item = MutableLiveData<Item>()
val item: LiveData<Item> = _item
fun requestItemDetails() {
if (itemId == -1) {
// return error state
return
}
viewModelScope.launch {
val response = repository.getItemDetails(itemId)
//...
_item.postValue(response.data)
}
}
}

Good news is that this is what SavedStateHandle is for, which automatically receives the arguments as its initial map.
#HiltViewModel
class ItemDetailsViewModel #Inject constructor(
private val repository: Repository,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
private val itemId = savedStateHandle.getLiveData(KEY_ITEM_ID)
val item: LiveData<Item> = itemId.switchMap { itemId ->
liveData(viewModelScope.coroutineContext) {
emit(repository.getItemDetails(itemId).data)
}
}

we should avoid calling viewmodel function on function responsible for creating fragments/activities mainly because of screen orientation change which recall network request
Yes, in your example a request will be executed whenever ItemDetailsFragment's view is created.
Take a look at this GitHub issue about assisted injection support in Hilt. The point of assisted injection is to pass additional dependencies at object's creation time.
This will allow you to pass itemId through the constructor, which then will allow you to access it in ViewModel's init block.
class ItemDetailsViewModel #HiltViewModel constructor(
private val repository: Repository,
#Assisted private val itemId: Int
) : ViewModel() {
init {
requestItemDetails()
}
private fun requestItemDetails() {
// Do stuff with itemId.
}
}
This way the network request will be executed just once when ItemDetailsViewModel is created.
By the time the feature is available you can either try workarounds suggested in the GitHub issue or simulate the init block with a flag:
class ItemDetailsViewModel #ViewModelInject constructor(
private val repository: Repository
) : ViewModel() {
private var isInitialized = false
fun initialize(itemId: Int) {
if (isInitialized) return
isInitialized = true
requestItemDetails(itemId)
}
private fun requestItemDetails(itemId: Int) {
// Do stuff with itemId.
}
}

Related

Multiple Instances of ViewModel in Hilt

I apologize if this has been asked before. I am trying to create multiple instances of the same type of viewmodel scoped to an activity using dagger-hilt, but even with different custom default args, it is returning the same instance each time.
I need all the viewmodel instances to be activity scoped, not fragment or navgraph scoped because I need all the fragments to subscribe to the updated data that will be received in the activity.
(Using Kotlin)
Activity Code
#AndroidEntryPoint
class Activity : AppCompatActivity() {
private val vm1:MyViewModel by viewModels(extrasProducer = {
val bundle = Bundle().apply {
putString("ViewModelType", "vm1")
}
MutableCreationExtras(defaultViewModelCreationExtras).apply {
set(DEFAULT_ARGS_KEY, bundle)
}
}) {
MyViewModel.Factory
}
private val vm2:MyViewModel by viewModels(extrasProducer = {
val bundle = Bundle().apply {
putString("ViewModelType", "vm2")
}
MutableCreationExtras(defaultViewModelCreationExtras).apply {
set(DEFAULT_ARGS_KEY, bundle)
}
}) {
MyViewModel.Factory
}
...
}
ViewModel Code
#HiltViewModel
class MyViewModel #Inject constructor(
application: Application,
private val myRepo: MyRepository,
private val savedStateHandle: SavedStateHandle
) : AndroidViewModel(application) {
...
// Define ViewModel factory in a companion object
companion object {
val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(
modelClass: Class<T>,
extras: CreationExtras
): T {
val defaultArgs = extras[DEFAULT_ARGS_KEY]
println("extras $extras and default $defaultArgs")
// Get the Application object from extras
val application = checkNotNull(extras[APPLICATION_KEY])
// Create a SavedStateHandle for this ViewModel from extras
val savedStateHandle = extras.createSavedStateHandle()
savedStateHandle.keys().forEach {
println("factory $it, ${savedStateHandle.get<Any>(it)}")
}
return MyViewModel(
application = application,
myRepo = MyRepository(application),
savedStateHandle = savedStateHandle
) as T
}
}
}
}
When I print out the default arguments, the first initialized viewmodel is always returned, and is not initialized again even with both variables in the activity having different default arguments. Expected result: New viewmodel instance with different default arguments.
I think it has to do with the Viewmodel store owner key being the same, but I do want the viewmodel store owner to be the same, just as a new instance, if that makes sense.
I know that in the past you could use AbstractSavedStateViewModelFactory, or a custom viewmodel factory with ViewModelProvider.get(), but I can't access ViewModelProvider.get without passing a ViewModelStoreOwner, and since I don't want to pass it to the factory since it could leak the activity, I'm confused as to how to go about this. Is there a better way than using hilt to create multiple instances of the same type of viewmodel in the same scope?
override val viewModel: MyViewModel by activityViewModels()
Create instance of viewModel which lives with activity.

Passing errors coming from the API call

I am using 2 separate liveData exposed to show the error coming from the API. I am basically checking if there is an exception with the API call, pass a failure status and serverErrorLiveData will be observed.
So I have serverErrorLiveData for error and creditReportLiveData for result without an error.
I think I am not doing this the right way. Could you please guide me on what is the right way of catching error from the API call. Also, any concerns/recommendation on passing data from repository on to view model.
What is the right way of handing loading state?
CreditScoreFragment
private fun initViewModel() {
viewModel.getCreditReportObserver().observe(viewLifecycleOwner, Observer<CreditReport> {
showScoreUI(true)
binding.score.text = it.creditReportInfo.score.toString()
binding.maxScoreValue.text = "out of ${it.creditReportInfo.maxScoreValue}"
initDonutView(
it.creditReportInfo.score.toFloat(),
it.creditReportInfo.maxScoreValue.toFloat()
)
})
viewModel.getServerErrorLiveDataObserver().observe(viewLifecycleOwner, Observer<Boolean> {
if (it) {
showScoreUI(false)
showToastMessage()
}
})
viewModel.getCreditReport()
}
MainActivityViewModel
class MainActivityViewModel #Inject constructor(
private val dataRepository: DataRepository
) : ViewModel() {
var creditReportLiveData: MutableLiveData<CreditReport>
var serverErrorLiveData: MutableLiveData<Boolean>
init {
creditReportLiveData = MutableLiveData()
serverErrorLiveData = MutableLiveData()
}
fun getCreditReportObserver(): MutableLiveData<CreditReport> {
return creditReportLiveData
}
fun getServerErrorLiveDataObserver(): MutableLiveData<Boolean> {
return serverErrorLiveData
}
fun getCreditReport() {
viewModelScope.launch(Dispatchers.IO) {
val response = dataRepository.getCreditReport()
when(response.status) {
CreditReportResponse.Status.SUCCESS -> creditReportLiveData.postValue(response.creditReport)
CreditReportResponse.Status.FAILURE -> serverErrorLiveData.postValue(true)
}
}
}
}
DataRepository
class DataRepository #Inject constructor(
private val apiServiceInterface: ApiServiceInterface
) {
suspend fun getCreditReport(): CreditReportResponse {
return try {
val creditReport = apiServiceInterface.getDataFromApi()
CreditReportResponse(creditReport, CreditReportResponse.Status.SUCCESS)
} catch (e: Exception) {
CreditReportResponse(null, CreditReportResponse.Status.FAILURE)
}
}
}
ApiServiceInterface
interface ApiServiceInterface {
#GET("endpoint.json")
suspend fun getDataFromApi(): CreditReport
}
CreditScoreResponse
data class CreditReportResponse constructor(val creditReport: CreditReport?, val status: Status) {
enum class Status {
SUCCESS, FAILURE
}
}
It's creates complexity and increased chances for a coding error to have two LiveData channels for success and failure. You should have a single LiveData that can offer up the data or an error so you know it's coming in orderly and you can observe it in one place. Then if you add a retry policy, for example, you won't risk somehow showing an error after a valid value comes in. Kotlin can facilitate this in a type-safe way using a sealed class. But you're already using a wrapper class for success and failure. I think you can go to the source and simplify it. You can even just use Kotlin's own Result class.
(Side note, your getCreditReportObserver() and getServerErrorLiveDataObserver() functions are entirely redundant because they simply return the same thing as a property. You don't need getter functions in Kotlin because properties basically are getter functions, with the exception of suspend getter functions because Kotlin doesn't support suspend properties.)
So, to do this, eliminate your CreditReportResponse class. Change your repo function to:
suspend fun getCreditReport(): Result<CreditReport> = runCatching {
apiServiceInterface.getDataFromApi()
}
If you must use LiveData (I think it's simpler not to for a single retrieved value, see below), your ViewModel can look like:
class MainActivityViewModel #Inject constructor(
private val dataRepository: DataRepository
) : ViewModel() {
val _creditReportLiveData = MutableLiveData<Result<CreditReport>>()
val creditReportLiveData: LiveData<Result<CreditReport>> = _creditReportLiveData
fun fetchCreditReport() { // I changed the name because "get" implies a return value
// but personally I would change this to an init block so it just starts automatically
// without the Fragment having to manually call it.
viewModelScope.launch { // no need to specify dispatcher to call suspend function
_creditReportLiveData.value = dataRepository.getCreditReport()
}
}
}
Then in your fragment:
private fun initViewModel() {
viewModel.creditReportLiveData.observe(viewLifecycleOwner) { result ->
result.onSuccess {
showScoreUI(true)
binding.score.text = it.creditReportInfo.score.toString()
binding.maxScoreValue.text = "out of ${it.creditReportInfo.maxScoreValue}"
initDonutView(
it.creditReportInfo.score.toFloat(),
it.creditReportInfo.maxScoreValue.toFloat()
)
}.onFailure {
showScoreUI(false)
showToastMessage()
}
viewModel.fetchCreditReport()
}
Edit: the below would simplify your current code, but closes you off from being able to easily add a retry policy on failure. It might make better sense to keep the LiveData.
Since you are only retrieving a single value, it would be more concise to expose a suspend function instead of LiveData. You can privately use a Deferred so the fetch doesn't have to be repeated if the screen rotates (the result will still arrive and be cached in the ViewModel). So I would do:
class MainActivityViewModel #Inject constructor(
private val dataRepository: DataRepository
) : ViewModel() {
private creditReportDeferred = viewModelScope.async { dataRepository.getCreditReport() }
suspend fun getCreditReport() = creditReportDeferred.await()
}
// In fragment:
private fun initViewModel() = lifecycleScope.launch {
viewModel.getCreditReport()
.onSuccess {
showScoreUI(true)
binding.score.text = it.creditReportInfo.score.toString()
binding.maxScoreValue.text = "out of ${it.creditReportInfo.maxScoreValue}"
initDonutView(
it.creditReportInfo.score.toFloat(),
it.creditReportInfo.maxScoreValue.toFloat()
)
}.onFailure {
showScoreUI(false)
showToastMessage()
}
}

Compose and Room: Issue with initializing ViewModel with a repository

I am currently following the Android Room with a View codelab and trying to adopt it with Jetpack Compose. I am stuck in initializing the viewModel in a compose function.
The error I am getting:
None of the following functions can be called with the arguments supplied:
public inline fun <reified VM : ViewModel> viewModel(viewModelStoreOwner: ViewModelStoreOwner = ..., key: String? = ..., factory: ViewModelProvider.Factory? = ...): TypeVariable(VM) defined in androidx.lifecycle.viewmodel.compose
public fun <VM : ViewModel> viewModel(modelClass: Class<TypeVariable(VM)>, viewModelStoreOwner: ViewModelStoreOwner = ..., key: String? = ..., factory: ViewModelProvider.Factory? = ...): TypeVariable(VM) defined in androidx.lifecycle.viewmodel.compose
#Composable
fun WordBookApp() {
val context = LocalContext.current
val wordViewModel: WordViewModel by viewModel( // error here - viewModel
WordViewModelFactory((context.applicationContext as WordsApplication).repository)
)
val words: List<Word> by wordViewModel.allWords.observeAsState(listOf())
...
The View Model and the View Model Factory:
class WordViewModel(private val repository: WordRepository) : ViewModel() {
val allWords: LiveData<List<Word>> = repository.allWords.asLiveData()
fun insert(word: Word) = viewModelScope.launch {
repository.insert(word)
}
}
class WordViewModelFactory(private val repository: WordRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(WordViewModel::class.java)) {
#Suppress("UNCHECKED_CAST")
return WordViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
The other parts of the code:
class WordRepository(private val wordDao: WordDao) {
val allWords: Flow<List<Word>> = wordDao.getAlphabetizedWords()
#Suppress("RedundantSuspendModifier")
#WorkerThread
suspend fun insert(word: Word) {
wordDao.insert(word)
}
}
class WordsApplication : Application() {
private val database by lazy { WordRoomDatabase.getDatabase(this) }
val repository by lazy { WordRepository(database.wordDao()) }
}
#Database(entities = [Word::class], version = 1, exportSchema = false)
public abstract class WordRoomDatabase : RoomDatabase() {
abstract fun wordDao(): WordDao
companion object {
// Singleton prevents multiple instances of database opening at the
// same time.
#Volatile
private var INSTANCE: WordRoomDatabase? = null
fun getDatabase(context: Context): WordRoomDatabase {
// if the INSTANCE is not null, then return it,
// if it is, then create the database
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
WordRoomDatabase::class.java,
"word_database"
).build()
INSTANCE = instance
// return instance
instance
}
}
}
}
AndroidMenifest.xml
<application
android:name=".WordsApplication"
....
Can anyone please help? Thanks!
Since you did not mention the question, I already started typing this. This may not pertain to your question, but perhaps you still should read this.
If the problem is that values are not being updated inside the viewmodel, - Initialising a viewmodel inside a composable, very bad idea.
You see the composables often recompose, where every line of code inside it is re-executed. Hence, if you initialize the viewmodel inside this composable like that, it will be re-initialised at every recomposition. Recompositions can take place theoretically even at the frame rate (even do in many cases). Hence, this is not how to declare variables inside the composable.
Ok so there's the remember composable to help you out with that. If you wrap the initializing statement with remember, it will not be re-initialised upon recompositions. However, it has its limitations. For example, if the composable gets destroyed, for example, if you swipe it off the screen, the remembered value is lost. remember is destroyed with the destruction of the composable enclosing it.
Hence, for small stuff like animations and all, it is ok to store variables inside the composables, but for important things, you should not trust this framework.
Hence, the best way would be to initialise the viewmodel in your main activity, then pass its methods and varibales around to composables. You can even pass the viewmodel itself around, but its not required most of the time.
Code:
#Composable
fun WordBookApp() {
val context = LocalContext.current
val wordViewModel: WordViewModel by remember {
viewModel( // error here - viewModel
WordViewModelFactory((context.applicationContext as WordsApplication).repository)
)
}
val words: List<Word> by wordViewModel.allWords.observeAsState(listOf())
...
Got a clue from the answer of #MARSK and fixed it. Moved the initialization of the view model to the onCreate() of the MainActivity, and passed it to the composable function. Working everything perfectly now!
Here is the code if anyone needs it in the future:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
...
val wordViewModel by viewModels<WordViewModel> {
WordViewModelFactory((this.applicationContext as WordsApplication).repository)
}
setContent {
WordBookApp(wordViewModel)
}
}
}
#Composable
fun WordBookApp(wordViewModel: WordViewModel) {
val words: List<Word> by wordViewModel.allWords.observeAsState(listOf())
...

How do I use the new Saved State Module of ViewModel

I'm using lifecycle version 2.2.0-rc03 and the official docs and articles found don't even list the correct class name or constructor arguments. I think I have to get the ViewModel instance through something like this
viewModel = ViewModelProvider(this, SavedStateViewModelFactory(requireActivity().application, savedStateRegistryOwner))
.get(SelectedTracksViewModel::class.java)
but I can't figure out the SavedStateRegistryOwner.
Can someone give a simple example of how to create the saved state ViewModel instance and the correct way to save and restore a value in the ViewModel?
For using Saved State module for View Model you have to add the androidx.lifecycle:lifecycle-viewmodel-savedstate dependency to your project. This example has been written based on version 1.0.0-rc03.
Please add the following line to your project Gradle file:
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0-rc03'
ViewModel implementation:
class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() {
val liveData = state.getLiveData("liveData", Random.nextInt().toString())
fun saveState() {
state.set("liveData", liveData.value)
}
}
Activity implementation:
class SavedStateActivity : AppCompatActivity() {
lateinit var viewModel: SavedStateViewModel;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityStateBinding = DataBindingUtil.setContentView(this, R.layout.activity_state)
viewModel = ViewModelProvider(this, SavedStateViewModelFactory(this.application, this)).get(SavedStateViewModel::class.java)
binding.viewModel = viewModel
binding.lifecycleOwner = this
}
override fun onSaveInstanceState(outState: Bundle) {
if(::viewModel.isInitialized)
viewModel.saveState()
super.onSaveInstanceState(outState)
}
}
I have tested this code and it works fine.
I am adding an answer to this old post just in case someone might find it useful.
I managed to do it as follows:
Add the following dependency to your "build.gradle (Module: app)" file
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
Add savedState: SavedStateHandle property to the constructor of the ViewModel
class SelectedTracksViewModel(private val savedState: SavedStateHandle) : ViewModel() {
companion object {
private const val SAVED_TRACK_INDEX = "savedTrackIndex"
}
private var trackIndex: Int
set(value) {
field = value
// Simply update the savedState every time your saved property changes
savedState.set(SAVED_TRACK_INDEX, value)
}
init {
trackIndex = savedState.get<Int>(SAVED_TRACK_INDEX) ?: 0
}
fun moveToNextTrack() {
trackIndex++
// Initially I was updating savedState here - now moved to setter
// Some more code here
}
}
Finally in the activity/fragment
private val selectedTracksViewModel: SelectedTracksViewModel by lazy {
ViewModelProvider(this).get(SelectedTracksViewModel::class.java)
}
And that's it. No need for SavedStateViewModelFactory, simply add the savedState property to your ViewModel constructor and update it when tracked properties change. Everything else works as if you're not using savedState: SavedStateHandle and this way is very similar to the traditional onSaveInstanceState(Bundle) in activities/fragments.
Update: Initially I was updating savedState after changing trackIndex. This means one has to update savedState every time saved properties are changed. This is a huge potential future bug if one forgets to add that line. A better and more robust pattern is to update the savedState in the setter of the property.
As far as I understand you want to create View model with spec constructor.
You can use ViewModelProvider.Factory.
viewModel = ViewModelProvider(this, SavedStateViewModelFactory.create(state)
.get(SelectedTracksViewModel::class.java)
example of ViewModelFactory
public class SavedStateViewModelFactory {
public static <E> ViewModelProvider.Factory create(State state){
return new ViewModelProvider.Factory() {
#NonNull
#Override
#SuppressWarnings("unchecked")
public <T extends ViewModel> T create(#NonNull Class<T> modelClass) {
if (modelClass.isAssignableFrom(SelectedTracksViewModel.class)) {
return (T) new SelectedTracksViewModel<>(state);
} else {
throw new IllegalArgumentException("Unknown ViewModel class");
}
}
};
}
}

Clean Architecture: ViewModel with multiple UseCases on Android

This is more of an Architecture question than a bug fixing one.
Let's assume this app lets users mark a Bus and/or Bus Stations as a favourite. My question is, should I have a ViewModel with both UseCases or should I build a UseCase that encapsulates the current logic?
Also for the question part, I'm not entirely sure the way I should expose the combined data to the UI layer (see favouritesExposedLiveData)
Thanks in advance any feedback is welcome, here's my ViewModel you can assume each UseCase is passing the correct data from the data source(s).
open class FavouritesViewModel #Inject internal constructor(
private val getFavouriteStationsUseCase: GetFavouriteStationsUseCase,
private val getFavouriteBusesUseCase: GetFavouriteBusesUseCase,
private val favouriteMapper: FavouriteMapper,
private val busMapper: BusMapper,
private val stationMapper: StationMapper) : ViewModel() {
private val favouriteBusesLiveData: MutableLiveData<Resource<List<BusView>>> = MutableLiveData()
private val favouriteStationsLiveData: MutableLiveData<Resource<List<StationView>>> = MutableLiveData()
private lateinit var favouritesMediatorLiveData: MediatorLiveData<List<FavouriteView>>
private lateinit var favouritesExposedLiveData: LiveData<Resource<List<FavouriteView>>>
init {
fetchFavourites()
}
override fun onCleared() {
getFavouriteStationsUseCase.dispose()
getFavouriteBusesUseCase.dispose()
super.onCleared()
}
fun getFavourites(): LiveData<Resource<List<FavouriteView>>> {
return favouritesExposedLiveData
}
private fun fetchFavourites() {
favouritesMediatorLiveData.addSource(favouriteStationsLiveData, { favouriteStationListResource ->
if (favouriteStationListResource?.status == ResourceState.SUCCESS) {
favouriteStationListResource.data?.map {
favouriteMapper.mapFromView(it)
}
}
})
favouritesMediatorLiveData.addSource(favouriteBusesLiveData, { favouriteBusesListResource ->
if (favouriteBusesListResource?.status == ResourceState.SUCCESS) {
favouriteBusesListResource.data?.map {
favouriteMapper.mapFromView(it)
}
}
})
getFavouriteStationsUseCase.execute(FavouriteStationsSubscriber())
getFavouriteBusesUseCase.execute(FavouriteBusesSubscriber())
}
inner class FavouriteStationsSubscriber : DisposableSubscriber<List<Station>>() {
override fun onComplete() {}
override fun onNext(t: List<Station>) {
favouriteStationsLiveData.postValue(Resource(ResourceState.SUCCESS, t.map { stationMapper.mapToView(it) }, null))
}
override fun onError(exception: Throwable) {
favouriteStationsLiveData.postValue(Resource(ResourceState.ERROR, null, exception.message))
}
}
inner class FavouriteBusesSubscriber : DisposableSubscriber<List<Bus>>() {
override fun onComplete() {}
override fun onNext(t: List<Bus>) {
favouriteBusesLiveData.postValue(Resource(ResourceState.SUCCESS, t.map { busMapper.mapToView(it) }, null))
}
override fun onError(exception: Throwable) {
favouriteBusesLiveData.postValue(Resource(ResourceState.ERROR, null, exception.message))
}
}
}
Note: Currently the MediatorLiveData (favouritesMediatorLiveData)is not binding the data back to the favouritesExposedLiveData since at this time, I'm not sure this is the correct way to go ;).
Ideally a ViewModel would only have the view state for its view. By using the MediatorLiveData you could aggregate all sources of state into one that represents the view state over time.
What you can have is a data class that represents your ViewState that you construct on your view model and is your exposed LiveData
data class FavouritesViewState(val favoriteStations: List<Station>, val favoritBuses: List<Bus>)
However you know depend on the ViewModel to construct the final ViewState which kinda breaks the single responsibility principle and also makes you dependent of an Android framework.
I would approach it using a composite UseCase that had both station and bus use cases and returns the composed data that you can then easily expose from the ViewModel.
The whole point of a ViewModel is that it is a model of what the view is using. It should be as close to that as possible.. Unless you are presenting stations and buses in the same view list (seems ugly), otherwise, they are separate views, and should get separate models.

Categories

Resources