I have two Views, Coffee and Doppio coffee (maybe any coffee). CoffeeView is a list of available coffees, and when one Coffee has taped, it will open its details page (in this example, Doppio has tapped).
I have two ViewModels, CoffeViewModel and CoffeeDetailsViewModel, respectively.
I can implement this scenario but can't MVVMify it. I know about routing. I can pass variables on routing but not between ViewModels. See the below Picture. When I tap on a specific item on Coffee, I want it to send its data from CoffeeViewModel to its CoffeeDetailsViewModel and at the same time open its DetailsViewPage and show data on that page.
await Navigation.PushAsync(new CoffeeDetailsPage(family));
or
await Shell.Current.GoToAsync($"{nameof(CoffeeDetialsPage)}?Data={Data}");
I have used the above routing syntax, but it can post data to CoffeeDetails.XAML.cs not to its ViewModel, and when I want to send data to its ViewModel, it doesn't launch its page.
Update
The ViewModels are instantiated in Xaml using the below syntax.
<ContentPage.BindingContext>
<ViewModel:CoffeeViewModel></ViewModel:CoffeeViewModel>
</ContentPage.BindingContext>
if you assign the VM in the constructor you can easily pass a parameter to it
public CoffeeDetailsPage(Family family)
{
InitializeComponent();
BindingContext = new CoffeeViewModel(family);
}
Related
i have a data class
data class Holder(uri: String, title: String, desc: String, source: String, color: String?) that i have from screen a, that i want to pass along to screen b. i have the code set up as follows:
#Composable
fun A(navigateToScreenB: (holder: Holder) -> Unit) {...}
#Composable
fun B(holder: Holder) {...}
in my nav file:
composable(
route = Screen.b.route
) {
B(how to get `Holder` to pass here)
}
composable(
route = Screen.A.route
) {
A(
navigateToScreenB = { it ->
// `it` is my data class `Holder`, but how to pass it to screen b?
navController.navigate(Screen.B.route)
}
)
}
any insight on this?
tl/dr: you shouldn't be passing the object at all, the same way you don't see a website that is example.com/data/{the whole object in the URL}, you'd see example.com/data/{a unique ID used to retrieve the object}
As per this thread, if the object exists only at the UI layer (i.e., it isn't coming from a remote database or any other source of truth), you can just pass the object directly by following the documentation and making your class Parcelable and serializing it to include it as part of your Screen.b.route just like any other argument.
However, you've said that your Holder object actually comes from a remote database. That same thread goes on to discuss exactly this case:
A better way to start the conversation is figuring out how StoreList is going to recreate its list after process death and recreation. Your objects have been wiped from memory entirely, which means that ViewModel needs to be talking to the actual source of truth (i.e., your repository layer - note that doesn't mean "save to disk" or "database", it just means the layer responsible for getting your data)
Once you have a repository layer anyways (since a ViewModel shouldn't be doing network calls or disk access directly), that's also the layer that can do in memory caching
Keep in mind that every list already has a way to uniquely define each item by the index in the list
So at that point you already have everything you need - a repository both destinations can talk to that is the single source of truth for Store objects and an index into the list that you can pass to the details screen to define you element
And if you start persisting those objects into a database, the rest of the layers don't have to care
(of course, you probably will add a unique ID to each element if you do add it to a database at some point, which then makes the index based approach unnecessary)
So how you should actually be doing this is:
Create a data layer that is responsible for loading your data. No other layer should know that your data is actually from a remote database. Okhttp caching is a simple way to add caching to prevent redownloading data in a way that is transparent to the other layers.
Both Screen A and Screen B would talk to the same data layer, generally, as per the UI Layer documentation, would be by using a state holder such as a ViewModel that is responsible for loading data from the data layer.
2a) Screen A would request the entire list of Holder objects from the data layer, which would load the entire list from the remote database.
2b) Screen B would request just a single Holder object, either by using the index in the list or a unique key if one is available (it isn't clear from your Holder class if a unique key exists)
Instead of passing the entire Holder as part of Screen B's route argument, you'd pass only that unique key, which could simply be the index in the list or something more complicated if your remote database has such a unique key (note that a Uri would need to be encoded via Uri.encode if included in a route)
This approach follows the approach of a single source of truth. If you later change your data layer to store data locally, none of the rest of your layers need to change. If Screen B gains the ability to edit your Holder objects, then Screen A (assuming your data layer uses a Flow or similar observable structure) would automatically update, without needing to pass data back or anything complicated like that.
It also means that cases which are generally hard to handle, such as configuration changes (i.e., rotating your device) or process death and recreation (which can happen at any screen, meaning you can't rely on Screen A to have loaded your data into memory) are also handled without any additional work.
I'm using MVVM architecture components by Android. For information that is going to be displayed in the View (Activity), they recommend to expose LiveData from ViewModel. The Observers in the Activity will consume that data and react to their changes.
But I don't know what happens when you want to fetch information (maybe other tables in your database) that you need to do some business validation for example. You definitely don't want to display that information in the UI, so it doesn't make sense to include Observers in the Activity for that.
It is possible to use observeForever in the ViewModel, so you can consume LiveData from Repository that you don't need to display in the Activity. However, the documentation says that ViewModel should noy contain observers to LiveData.
I cannot find any good example that deals with this situation so far.
EXAMPLE
I have one MainActivity that will create a Tournament record, which contains the Integer "numberOfRounds", selected by the user on the screen.
Now, in Activity PairingsView, I display the following AlertDialog when the user clicks the "finish current Round" button:
new AlertDialog.Builder(this)
.setTitle(title)
.setMessage(message)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.continues, (dialogInterface, i) -> {
if (mViewModel.hasNextRond()) {
mViewModel.calculateNewPairings();
}
In ViewModel:
public boolean hasNextRound() {
return currentRound <= numberOfRounds;
}
Here, I've access to currentRound value, since I exposed Round table information in PairingsView. But I also need to know the numberOfRounds to check if I must generate pairings for next round. This value is stored in Tournament table, and I don't need to display any information from that table in my View, so I think I shouldn't include an Observer for it.
Yes you can use observeForever in the ViewModel but you should remove the observer in onCleared method of ViewModel in order to prevent from memory leaking
From official we know
The ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way
But i think lot of developers use ViewModel as both data store and controller(like calling repository, network client for data). I also use as for both data store and controller for view.
Android official sample code has also some controller logic. From official :
class MyViewModel : ViewModel() {
private val users: MutableLiveData<List<User>> by lazy {
MutableLiveData().also {
loadUsers()
}
}
fun getUsers(): LiveData<List<User>> {
return users
}
private fun loadUsers() {
// Do an asynchronous operation to fetch users.
}
}
Here loadUsers may calling some Repository or NetworkClient . So here it acting like controller.
I am sure many developer do this way, but as from definition ViewModel should store and manage UI related data, should ViewModel act as a Controller ?
I found some stackoverflow thread this and this about this.
First one accepted answer suggested not to use use ViewModel as Controller and to use a Controller for other task.
In comment section of Second one #commonsware also suggested not to use complicated things other than data.
So my question is
What will be the actual responsibility of ViewModel from architectural concept?
If i have to do some method calls related to View [like data query, network call and other business login related stuff ] where should i do it?
and if i have to use a Controller then how i connect View and Controller for device rotation and sharing controller between Fragment ?
Hope my question is clear to all
Thanks in advance.
Here loadUsers() may calling some Repository or NetworkClient . So here it acting like controller.
I am sure many developer do this way, but as from definition ViewModel should store and manage UI related data, should ViewModel act as a Controller ?
Theoretically, the retrieval of data should be internal to the LiveData, triggered by having active observers and based on that, deciding what to do (in onActive()). If the LiveData is actually a MediatorLiveData, then this also applies to any block bound with addSource, as the block added with addSource of a MediatorLiveData is only called when the MediatorLiveData is observed by an active observer
You can see this technique used to its fullest in the NetworkBoundResource. The ViewModel only stores data, and knows nothing of data loading.
What will be the actual responsibility of ViewModel from architectural concept?
If you see the comments by Yigit Boyar (creator of ViewModel):
I'm the guy (or part of the team) that added it and it had nothing to do w/ MVVM. It is all about trying to give a class to people where they should put the data.
AAC is not an MVVM implementation, nor VM concept only lives as part of MVVM.
In fact, the main motivation for this was; we've been telling devs not to manage data in the UI controller and the answers was also, so where? And ViewModel became that answer.
We want it to be the model for your view layer (fragment, activity whatever). On the hindsight, it could be better to pick a name that is new but naming is really hard.
In conclusion: ViewModel is the Model in an MVC scenario, where C is the Activity or Fragment, V is the inflated view, and M is the ViewModel.
If i have to do some method calls related to View [like data query, network call and other business login related stuff ] where should i do it?
ViewModel gets the data in the form of a LiveData, and the LiveData is "activated" by observing it from the View with a given lifecycle.
Network calls are supposed to be also triggered in the same manner (if you follow the approach as per LiveData was designed).
In theory, if you have a login call, you could as well do it in the controller instead of the model, so you could do it in the Fragment, even though there are tricks like Jetpack Databinding that would let you call methods from the View on the Model directly from the XML.
and if i have to use a Controller then how i connect View and Controller for device rotation and sharing controller between Fragment ?
ViewModel exposes LiveData and can potentially also expose LiveEvent if you write the necessary code for that (unfortunately that is not provided by the Jetpack team, and neither are Command bindings), and either the View or the Controller can call methods directly on it if necessary. ViewModel is stored across config changes (not across process death, ofc) so it should not hold a direct view reference.
maybe the headline does not fit the question really well so I'll explain it.
In my app I send a request to a server to get gerneral user information. If I receive on I want to save it into the local room db. Now we come to the problem. I want to bind this one user to the view directly out of the db. But I can not bind a element, which maybe does not exists, because the request is in progress.
(My bad solution): Creating another livedate element which holds a boolean. I create a observer in the Activity and add the observer after the boolean observes a "true". With this solution I can not use "Data Binding" in the xml layout.
Does anyone have an idea? (If you need further information just ask - I know it is a really abstract question without any code)
As Sanlok Lee mentioned:
If you reassign user then it becomes a completely different instance and the observer will not listen to the new LiveData. Instead you can do val user: MediatorLiveData<User> and later you can call user.addSource(dao.getUserById(1), ...)
OR:
Just load the user (also if you know that there is no one in the DB) from the DB. You can do that in you UserRepository for example.
val user: LiveData<User> = userDao.getUser()
The Livedata will get notified when there is a valid user inserted.
I just wanted to know and how to implement multiple API call observer in a single view(Act or Fragment)
So my question brief is I have a multiple API's calls in a single Activity like fetch user detail, fetch banner image list and products list via ViewModel and Livedata,
So is it compulsory to observe three livedata depend on three API calls?
I want to observe only a single livedata for three different APIs.
like below example, I have three live data for three APIs
var userLiveData = MutableLiveData<UserDetails>()
var bannerLiveData = MutableLiveData<BannerDetails>()
var productLiveData = MutableLiveData<Product>()
Call rest API and observe in Act like below
userLiveData.observe(this, Observer { data ->
})
bannerLiveData.observe(this, Observer { data ->
})
productLiveData.observe(this, Observer { data ->
})
it is ok no issue but I think it is not good practice to observe every API call, I need to combine all three API calls to one so is there any solution for that?
I read about MediatorLiveData but cant help!
Forgive me for my bad English.
If anyone knows anything please help me.