I've a ViewModel which contains a Team which has a Players property which is a list of Player objects. Within TeamView the Team is deep loaded, so player data is already in the memory.
What is the best way to pass a given selected Player class instance to a PlayerView?
The problem is that MVVMCross ViewModel constructors can only contain string properties in current version.
I've the following ideas:
Pass the Id of the selected Player and assign the Team.Players property as a ViewModel to the PlayerView. This can be a reasonable solution if the selected player is just the focused player in the PlayerView and the PlayerView is really a "players" view, where the user can swipe between the other team players as well.
Have a ASP.Net MVC like ViewBag service which can carry data only between navigate actions, in a Dictionary like storage, and the parameter passed to PlayerView is a "viewbag:PlayerId123" which is a special key pointing to the class instance.
Serialize the selected object to string and pass it as serialized object to the constructor. It is possible but I don't like this solution.
In general navigation, MvvmCross only allows strings to be passed between ViewModels.
The reason for this is that the navigation needs to be done at a platform level via mechanisms such as Xaml Uris or Android Intents.
For the situation you suggest, the general pattern I would typcially use is:
- that the TeamViewModel gets the team data from the network using an injected ITeamService
- that the TeamViewModel also uses an injected singleton ITeamCache to cache the Team
- that the navigation happens via a call like:
this.RequestNavigate<PlayerViewModel>(new { teamId, playerId })
- the PlayerViewModel then receives the TeamId and PlayerId in its constructor, and uses the ITeamCache to collect the right player
This code might look like:
public class TeamViewModel
: MvxViewModel
, IMvxServiceConsumer<ITeamCache>
{
public TeamViewModel(string teamId, string playerId)
{
var teamCache = this.GetService<ITeamCache>();
Player = teamCache.GetPlayer(teamId, playerId);
if (Player == null)
{
// todo - handle this error somehow!
}
}
public Player Player { get; set; }
}
Notice that the code above tests for whether Player is null
. This is because there is a problem with your assumption "Within TeamView the Team is deep loaded, so player data is already in the memory."
The problem is that in platforms like Android and WP7, the operating system is free to remove your application from memory and to then restart it later. This is referred to as Tombstoning on WP7, but just seems to be called Killed on Android.
In these cases, then the operating system may restart your application later when the user navigates back. This restart will go direct to the activity where the user last was, and it will remember the back stack - it will then be up to your application to properly rehydrate any required objects back into memory.
Here are some very small pictures explaining this...
For more detail, see Xamarin and MSDN
For your Team/Player case, you might be able to cope with rehydration by:
- Implementing the ITeamCache as a file-backed object - e.g. it could use a JSON file or a SQLite database as a persistent store for the in-memory data
- Implementing some logic in your code that refetches data from the network when needed
- Implementing some emergency-navigate-back-home strategy in these cases - as these cases don't happen that often in many applications on modern resource rich phones.
- Just crashing - although this isn't advisable...
It's no surprise, that many applications don't handle tombstoning very well...
Note - for small objects, your option 3 (serialization) can works well - however, this wouldn't help you with the situation where app rehydration occurs and a user then navigates back from a PlayerViewModel to a TeamViewModel.
For more on some of the recent changes on Android lifecyle within MvvmCross, see http://slodge.blogspot.co.uk/2012/05/android-application-initialization-and.html