- constructor based Dependency Injection
- navigation using Typed navigation classes
- saving and reloading VM state for 'tombstoning'
I'm sorry that these changes are breaking changes for existing v1 and vNext apps.
However, I hope we'll all find these changes very useful in the future.
How ViewModels are Created in v3
The default ViewModelLocator in v3 builds new ViewModel instances using a 4-step process - CIRS:
- Construction - using IoC for Dependency Injection
- Init() - initialisation of navigation parameters
- ReloadState() - rehydration after tombstoning
- Start() - called when initialisation and rehydration are complete
Construction
As in vNext, you navigate to a ViewModel using arguments like:
In vNext, these navigation parameters were passed to the constructor of the ViewModel.
However, in v3, these navigation parameters are instead passed to the Init() method, and the constructor is now free to be used for Dependency Injection.
This means that, for example, a DetailViewModel constructor might now look like:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class DetailViewModel : MvxViewModel | |
{ | |
private readonly IDetailRepository _repository; | |
public DetailViewModel(IDetailRepository repository) | |
{ | |
_repository = repository; | |
} | |
// ... | |
} |
This Dependency Injection is, of course, optional - you code can instead continue to use ServiceLocation if you prefer:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class DetailViewModel : MvxViewModel | |
{ | |
private readonly IDetailRepository _repository; | |
public DetailViewModel() | |
{ | |
_repository = Mvx.Resolve<IDetailRepository>(); | |
} | |
// ... | |
} |
Init()
Now that the construction is used for Dependency Injection, the navigation parameters move to a new method - Init()
Init() will always be called after construction and before ReloadState() and Start()
Init() can come in several flavors:.
- individual simply-Typed parameters
- a single Typed parameter object with simply-Typed properties
- as InitFromBundle() with an IMvxBundle parameter - this last flavor is always supported via the IMvxViewModel interface.
So, for example, to support the navigation:
you could implement any of:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class DetailViewModel : MvxViewModel | |
{ | |
// ... | |
public void Init(string First, string Second, int Answer) | |
{ | |
// use the values | |
} | |
// ... | |
} |
or:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class DetailViewModel : MvxViewModel | |
{ | |
// ... | |
public class NavObject | |
{ | |
public string First {get;set;} | |
public string Second {get;set;} | |
public int Answer {get;set;} | |
} | |
public void Init(NavObject navObject) | |
{ | |
// use navObject | |
} | |
// ... | |
} |
or:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class DetailViewModel : MvxViewModel | |
{ | |
// ... | |
public override void InitFromBundle(IMvxBundle bundle) | |
{ | |
// use bundle - e.g. bundle.Data["First"] | |
} | |
// ... | |
} |
Note that multiple calls can be used together if required. This allows for some separation of logic in your code:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class DetailViewModel : MvxViewModel | |
{ | |
// ... | |
public class FirstNavObject | |
{ | |
public string First {get;set;} | |
public string Second {get;set;} | |
} | |
public class SecondNavObject | |
{ | |
public int Answer {get;set;} | |
} | |
public void Init(FirstNavObject firstNavObject) | |
{ | |
// use firstNavObject | |
} | |
public void Init(SecondNavObject secondNavObject) | |
{ | |
// use secondNavObject | |
} | |
// ... | |
} |
ReloadState
If the View/ViewModel is recovering from a Tombstoned state, then ReloadState will be called with the data needed for rehydration.
If there is no saved state then no ReloadState() methods will be called.
Exactly as with Init(), ReloadState can be called in several different ways.
- individual simply-Typed parameters
- a single Typed parameter object with simply-Typed properties
- as ReloadStateFromBundle() using an IMvxBundle parameter - this last flavor is always supported via the IMvxViewModel interface.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class DetailViewModel : MvxViewModel | |
{ | |
// ... | |
public class SavedState | |
{ | |
public string Name {get;set;} | |
public int Position {get;set;} | |
} | |
public void ReloadState(SavedState savedState) | |
{ | |
// use savedState | |
} | |
// ... | |
} |
Aside: where does the SavedState come from?
One of the new ViewModel APIs available in Hot Tuna is a SaveState pattern.
This can be implemented in one of two ways:
- using one or more paremeterless methods that return Typed state objects
- using the override SavedStateToBundle(IMvxBundle bundle)
Using a Typed state object:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class DetailViewModel : MvxViewModel | |
{ | |
// ... | |
public class SavedState | |
{ | |
public string Name {get;set;} | |
public int Position {get;set;} | |
} | |
public SavedState SaveState() | |
{ | |
return new SavedState() | |
{ | |
Name = _name, | |
Position = _position | |
}; | |
} | |
// ... | |
} |
Using SavedStateToBundle:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class DetailViewModel : MvxViewModel | |
{ | |
// ... | |
protected override void SaveStateToBundle(IMvxBundle bundle) | |
{ | |
bundle.Data["Name"] = _name; | |
bundle.Data["Position"] = _position.ToString(); | |
} | |
// ... | |
} |
Start()
After all of Construction, Init, and ReloadState is complete, then the Start() method will be called.
This method is simply:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class DetailViewModel : MvxViewModel | |
{ | |
// ... | |
public override void Start() | |
{ | |
// do any start | |
} | |
// ... | |
} |
Putting it all together
For a real app, I would expect the navigation, construction and state saving/loading code to actually look like:
and
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class DetailViewModel : MvxViewModel | |
{ | |
public class SavedState | |
{ | |
public string Name {get;set;} | |
public int Position {get;set;} | |
} | |
public class NavObject | |
{ | |
public string First {get;set;} | |
public string Second {get;set;} | |
public int Answer {get;set;} | |
} | |
private readonly IDetailRepository _repository; | |
public DetailViewModel(IDetailRepository repository) | |
{ | |
_repository = repository; | |
} | |
public void Init(NavObject navObject) | |
{ | |
// use navObject | |
} | |
public void ReloadState(SavedState savedState) | |
{ | |
// use savedState | |
} | |
public override void Start() | |
{ | |
// do any start | |
} | |
public SavedState SaveState() | |
{ | |
return new SavedState() | |
{ | |
Name = _name, | |
Position = _position | |
}; | |
} | |
// ... | |
} |
Oh yuk - I hate it - this CIRS stuff is not for me....
If you don't like this CIRS flow for building your ViewModels, then the good news is that you can easily override the ViewModelLocator within v3, just as you could within vNext :)
You can easily go back to vNext style ViewModel construction - or you can easily invent something new.
Oh yes - I love it - when can I use it?
Soon... very soon ...
... well, just as soon as I work out some of this MonoTouch->Xamarin.iOS pain :/
very helpful thanks....
ReplyDelete