View
9
Download
0
Category
Preview:
Citation preview
Patterns for reusing .NET skills and code for
iOS, Android and Windows Apps
Vincent Hoogendoorn twitter.com/vincenth_net
Sander Egberink
Agenda
- Introduction
- Demo and code FlyBy BCC App
- MVP+
- MVVM+
- Pattern Comparison
- Next Steps, Resources, Questions
IntroductionOngoing research goal:
“Optimise reuse of .NET code and skills when building native Apps for iOS,
Android, Windows Store and Windows Phone.”
Case App: BCC Veiling – Responsive Design, Azure, SignalR, Complex
Authentication, clients for Mobile Web, iOS, Android and Windows Store
V1: MVP+; in stores.
V2: MVVM+; almost done.
Spoiler: MVVM+ lets a non-mobile .NET developer write most code for you
Demo – BCC App
FlyBy – All the code you need to add 2nd+
platform…
With MVVM+
MVP+ - Overview
Model• Entities (POCOs)
public class Lot{
public int Id { get; set; }public string AuctionTitle { get; set; }public string Title { get; set; }public string Info { get; set; }}
public class LotUpdate{
public decimal ProgressRemaining { get; set; }public decimal CurrentPrice { get; set; }public RemainingTime TimeRemaining { get; set; }
}
MVP+ - Overview
Presenter• Properties
• INotifyPropertyChanged
implemented
Model• Entities (POCOs)
public Lot ActiveLot{
get { return _activeLot; }set {
_activeLot = value;NotifyPropertyChanged("ActiveLot"); }
}
public List<Lot> ProductsList{
get { return _productsList; }set {
_productsList = value;NotifyPropertyChanged("ProductsList"); }
}
public event PropertyChangedEventHandlerPropertyChanged;
MVP+ - Overview
Presenter• Properties
• INotifyPropertyChanged
implemented
Model• Entities (POCOs)
ServiceAgent• Service Methods
• Service Events
• Connection Management
private SignalRConnectionResponseInitialSignalRConnect(){
connection = newHubConnection(serviceInfo.SignalRRootUrl);
proxy = connection.CreateProxy(serviceInfo.SignalRHubName);
proxy.On<Lot>("auctionInitialized", HandleAuctionInitialized);}
private void HandleAuctionInitialized(Lot lot){ MainModel.Current.ActiveLot = lot; }
public void InvokePlaceBid(int lotId, decimallotCurrentPrice){
proxy.Invoke("PlaceBid", new object[] { lotId, lotCurrentPrice });
}
MVP+ - Overview
Presenter• Properties
• INotifyPropertyChanged
implemented
Model• Entities (POCOs)
ServiceAgent• Service Methods
• Service Events
• Connection Management
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Entry
Point
ServiceAgent.Current.InitiateConnection();
Presenter.Current.PropertyChanged += OnCurrentChanged;private void OnCurrentChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{if (e.PropertyName == "ActiveLot"){
Activity.RunOnUiThread(delegate{
FullUIUpdate(); });
}}
private void FullUIUpdate(){
currentProductHeader.Text = Presenter.Current.ActiveLot.Title;
regularStorePrice.Text = Presenter.Current.ActiveLot.RegularPrice.ToString("C");
MVP+ – Application Startup
Presenter• Properties
• INotifyPropertyChanged
implemented
Model• Entities (POCOs)
ServiceAgent• Service Methods
• Service Events
• Connection Management
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Entry
Point
1) Application starts Initial View
2) View calls Service agent constructor which establishes connection
3) ServiceAgent requests initial data and sets listeners for updates
4) ServiceAgent receives data and passes it to the Presenter
5) Presenter’s properties are INotifyPropertyChanged – Being set by
the ServiceAgent raises the event which is caught by View, which
subsequently updates the UI.1
2 3
4
5
MVP+ - Ordering a product
Presenter• Properties
• INotifyPropertyChanged
implemented
Model• Entities (POCOs)
ServiceAgent• Service Methods
• Service Events
• Connection Management
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Entry
Point
ServiceAgent.Current.InvokePlaceBid(Presenter.Current.ActiveLot.Id, Presenter.Current.ActiveLot.UpdateInformation.CurrentPrice);
MVP+ - Ordering a product
Presenter• Properties
• INotifyPropertyChanged
implemented
Model• Entities (POCOs)
ServiceAgent• Service Methods
• Service Events
• Connection Management
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Entry
Point
public void InvokePlaceBid(int lotId, decimallotCurrentPrice){
proxy.Invoke("PlaceBid", new object[] { lotId, lotCurrentPrice });
}
proxy.On<Lot>("BidPlaced", BidPlaced);proxy.On("BidFailed", BidFailed);proxy.On("BidFailedOnAuthentication",
BidFailedOnAuthentication);
proxy.On("BidCancelled", BidCancelled);proxy.On("CancelBidFailed", CancelBidFailed);
MVP+ - Ordering a product
Presenter• Properties
• INotifyPropertyChanged
implemented
Model• Entities (POCOs)
ServiceAgent• Service Methods
• Service Events
• Connection Management
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Entry
Point
private void BidPlaced(Lot reservedLot){
Presenter.Current.ReservedLot = reservedLot;}
MVP+ - Ordering a product
Presenter• Properties
• INotifyPropertyChanged
implemented
Model• Entities (POCOs)
ServiceAgent• Service Methods
• Service Events
• Connection Management
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Entry
Point
private Lot _reservedLot;public Lot ReservedLot{
get { return _reservedLot; }set {
_reservedLot = value;NotifyPropertyChanged("ReservedLot");
}}
public event PropertyChangedEventHandlerPropertyChanged;
private void NotifyPropertyChanged(string propertyName){
var eventHandler = PropertyChanged;if (eventHandler != null) {
eventHandler(this, newPropertyChangedEventArgs(propertyName));
}}
MVP+ - Ordering a product
Presenter• Properties
• INotifyPropertyChanged
implemented
Model• Entities (POCOs)
ServiceAgent• Service Methods
• Service Events
• Connection Management
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Entry
Point
Presenter.Current.PropertyChanged += OnCurrentChanged;
private void OnCurrentChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e){if (e.PropertyName == "ReservedLot"){if (null != Presenter.Current.ReservedLot)
{ Activity.RunOnUiThread(delegate{ BuyLot(); }); }}}}
private void BuyLot(){if (null != Presenter.Current.ReservedLot){Intent i = new Intent(Activity,
typeof(FinalizeOrderActivity));StartActivity(i);}
}
MVP+ – Shared Code
Presenter• Properties
• INotifyPropertyChanged
implemented
Model• Entities (POCOs)
ServiceAgent• Service Methods
• Service Events
• Connection Management
- Entities
- Application-wide Model
(through presenter)
- Application-wide Model
change events
- Service Agent
- Business Logic
MVVM+ ViewModel Properties
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
public decimal ActiveLotCurrentPrice{
get { return _activeLotCurrentPrice; }set{
_activeLotCurrentPrice = value;PropChanged("ActiveLotCurrentPrice");
}}
public bool SigninButtonIsAvailable{
get{
return !_auctionApplication.IsUserLoggedIn();}
}
MVVM+ ViewModel Commands
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
public RelayCommand PlaceBidCommand{ get; private set; }
private void WireupCommands(){
PlaceBidCommand = new RelayCommand(PlaceBid);PlaceBidCommand.IsEnabled = true;…
}
private void PlaceBid(){
ShowReasonForRejection = false;var placeBidRequest = new PlaceBidRequest{
LotId = ActiveLotId,Price = ActiveLotCurrentPrice
};_auctionServiceAgent.RaisePlaceBidAsync(placeBidRequest);
}
MVVM+ ViewModel Data Binding
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
public abstract class ViewModelBase : INotifyPropertyChanged, INotifyCollectionChanged…protected void PropChanged(string propName){
RunOnUIThread(() => OnPropertyChanged(propName));}
public class AuctionViewModel : ViewModelBase…public DateTime AuctionStartsAt{
get { return _auctionStartsAt; }set{
_auctionStartsAt = value;PropChanged("AuctionStartsAt");
}}.
MVVM+ View
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
View development can start at the earliest right after a ViewModel has
its properties and commands defined, and an instance with hardcoded
design time data in the properties is available.
public class AuctionViewModelStub : AuctionViewModel{
public AuctionViewModelStub(){
// Set all base ViewModel properties to// hardcoded values, optionally with random// selection from multiple values or value sets.
}}
View development can start at the latest after all shared code has
been completed.
The View implementation is platform specific. Details for each
platform are shown later in this presentation.
MVVM+ Model Entities
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
Model• Entities (POCO)
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
public class ClientLotUpdate{
public decimal CurrentPrice { get; set; }public int ProgressRemaining { get; set; }public RemainingTime TimeRemaining { get; set; }
public ClientLotUpdate(){
TimeRemaining = new RemainingTime();}
}
MVVM+ Service Entities
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
ServiceAgent• Service Entities
• Service Events
• Service Methods
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
public class ClientLotUpdate{
public decimal CurrentPrice { get; set; }public int ProgressRemaining { get; set; }public RemainingTime TimeRemaining { get; set; }
public ClientLotUpdate(){
TimeRemaining = new RemainingTime();}
}
Note that in simple cases service entities defined in the service contract can be used directly as the ‘model’.
MVVM+ Model ServiceAgent Events
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
Model• Entities (POCO)
ServiceAgent• Service Entities
• Service Events
• Service Methods
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
public class LotUpdatedEventArgs : EventArgs{
public ClientLotUpdate clientLotUpdate { get; set; }}
public event EventHandler<LotUpdatedEventArgs> ActiveLotUpdated;
MVVM+ Model ServiceAgent Methods
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
Model• Entities (POCO)
ServiceAgent• Service Entities
• Service Events
• Service Methods
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
public class PlaceBidRequest{
public int LotId { get; set; }public decimal Price { get; set; }
}
public Task RaisePlaceBidAsync(PlaceBidRequestplaceBidRequest)
{return _hubProxy.Invoke(
"PlaceBid", new object[] { placeBidRequest });
}
MVVM+ ViewModel use of Application
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
Model• Entities (POCO)
ServiceAgent• Service Entities
• Service Events
• Service Methods
Application• ViewModels instance
• Navigation context
• Navigation transitions
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
public interface IAuctionApplication{
void ContinueToAuction();void ContinueToCheckout(Basket basket);void ContinueToOrderConfirmed();void ContinueToProductList();void ContinueToProductPage(ClientLot product);void UserWantsToSignIn();bool IsUserLoggedIn();
}
The ViewModel only uses the application to initiate navigation and to obtain relevant application state.
This is not available to ViewModels:
public interface IApplication<TNavigationContext>{
TNavigationContext CurrentNavigationContext { set; }TViewModel GetViewModel<TViewModel>();
}
MVVM+ Application
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
Model• Entities (POCO)
ServiceAgent• Service Entities
• Service Events
• Service Methods
Application• ViewModels instance
• Navigation context
• Navigation transitions
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
public class AuctionApplication<TNavigationContext> :IAuctionApplication,IApplication<TNavigationContext>
public void ContinueToCheckout(Basket basket, TNavigationContext navigationContext)
{CheckoutViewModel = new CheckoutViewModel(basket,_auctionServiceAgentBase, this, _taskScheduler);
RunOnUIThread(() =>_auctionNavigator.NavigateToCheckoutView(navigationContext)
);}
Application initializes viewmodel and then calls navigator for platform-specific view navigation.
MVVM+ Application use of Navigator
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
Model• Entities (POCO)
ServiceAgent• Service Entities
• Service Events
• Service Methods
Application• ViewModels instance
• Navigation context
• Navigation transitions
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Navigator• Navigate to view
implementation
public interface IAuctionNavigator<TNavigationContext>{
void NavigateToSignInView(TNavigationContext c);void NavigateToAuctionView(TNavigationContext c);void NavigateToCheckoutView(TNavigationContext c);void NavigateToOrderConfirmed(TNavigationContext c);void NavigateToProductsView(TNavigationContext c);void NavigateToProductNotInAuctionView(
TNavigationContext navigationContext);}
The navigator implementation is platform specific (TNavigationContext)
MVVM+ Application Entry Point for Windows
Store App
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
Model• Entities (POCO)
ServiceAgent• Service Entities
• Service Events
• Service Methods
Application• ViewModels instance
• Navigation context
• Navigation transitions
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Navigator• Navigate to view
implementation
Entry
Point
public static AuctionApplication<Frame> AuctionApplication { get; private set; }
private static void EntryPoint(){
var taskScheduler =TaskScheduler.FromCurrentSynchronizationContext();
AuctionApplication =await AuctionApplication<Frame>.GetInstance(
rootFrame, new AuctionNavigator(), taskScheduler
);
AuctionApplication.Start();}
The entry point is platform specific (TNavigationContext, here a WinRT Frame).
MVVM+ Navigator for Windows Store App
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
Model• Entities (POCO)
ServiceAgent• Service Entities
• Service Events
• Service Methods
Application• ViewModels instance
• Navigation context
• Navigation transitions
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Navigator• Navigate to view
implementation
Entry
Point
public class AuctionNavigator : IAuctionNavigator<Frame>{
public void NavigateToAuctionView(Frame navigationContext)
{navigationContext.Navigate(typeof(AuctionPage));
}
public async void NavigateToOrderConfirmed(Frame navigationContext)
{var dialog = newWindows.UI.Popups.MessageDialog(“Message");await dialog.ShowAsync();NavigateToAuctionView(navigationContext);
}…
}
The navigator is platform specific (TNavigationContext, here a WinRT Frame).
MVVM+ View for Windows Store App – Data
Binding
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
Model• Entities (POCO)
ServiceAgent• Service Entities
• Service Events
• Service Methods
Application• ViewModels instance
• Navigation context
• Navigation transitions
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Navigator• Navigate to view
implementation
Entry
Point
<local:CheckoutViewBasex:Class="MobileAuction.W8.Views.CheckoutView"xmlns:local="using:MobileAuction.W8.Views“d:DataContext="{d:DesignInstance
IsDesignTimeCreatable=True,Type=shared:CheckoutViewModelStub}"
…
<TextBox x:Name="txtVoornaam“Text="{Binding FirstName, Mode=TwoWay}" />
public class Page<TViewModel> : Page{
protected Page(IApplication<Frame> application){
if (application != null) DataContext =application.GetViewModel<TViewModel>();
}}
Note: both design-time and runtime data is supported.
MVVM+ View for Windows Store App – C#
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
Model• Entities (POCO)
ServiceAgent• Service Entities
• Service Events
• Service Methods
Application• ViewModels instance
• Navigation context
• Navigation transitions
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Navigator• Navigate to view
implementation
Entry
Point
<local:CheckoutViewBasex:Class="MobileAuction.W8.Views.CheckoutView"xmlns:local="using:MobileAuction.W8.Views“d:DataContext="{d:DesignInstance
IsDesignTimeCreatable=True,Type=shared:CheckoutViewModelStub}"
…
// This class can be eliminated once the x:TypeArguments // attribute is supported in XAML:// http://stackoverflow.com/questions/14478469/windows-8-store-application-support-for-xtypearguments
public abstract class CheckoutViewBase :Mobile.Presentation.W8.Page<
MobileAuction.Shared.CheckoutViewModel>{
public CheckoutViewBase() :base(App.AuctionApplication) { }
}
MVVM+ Navigator for Android
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
Model• Entities (POCO)
ServiceAgent• Service Entities
• Service Events
• Service Methods
Application• ViewModels instance
• Navigation context
• Navigation transitions
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Navigator• Navigate to view
implementation
Entry
Point
public class AuctionNavigator : IAuctionNavigator<Activity>
{
public void NavigateToCheckoutView(ActivitynavigationContext)
{Intent i = new Intent(navigationContext,
typeof(CheckoutActivity));navigationContext.StartActivity(i);
}
public void NavigateToAuctionView(ActivitynavigationContext)
{Intent i = new Intent(navigationContext,
typeof(HomeActivity));navigationContext.StartActivity(i);
}
MVVM+ View for Android
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
Model• Entities (POCO)
ServiceAgent• Service Entities
• Service Events
• Service Methods
Application• ViewModels instance
• Navigation context
• Navigation transitions
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Navigator• Navigate to view
implementation
Entry
Point
public class Activity<TActivity, TViewModel> : global::Android.Support.V4.App.Activity,
IView<TViewModel> where TActivity : Activity<TActivity, TViewModel>
where TViewModel : INotifyPropertyChanged{
public TViewModel ViewModel { get; set; }
public override void OnCreate(BundlesavedInstanceState)
{base.OnCreate(savedInstanceState);ViewModel =
AuctionApplication<Activity>.GetInstance().GetViewModel<TViewModel>(); }}
MVVM+ View for Android
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
Model• Entities (POCO)
ServiceAgent• Service Entities
• Service Events
• Service Methods
Application• ViewModels instance
• Navigation context
• Navigation transitions
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Navigator• Navigate to view
implementation
Entry
Point
public class SplashScreen : Activity{
protected override void OnCreate(BundlesavedInstanceState)
{base.OnCreate(savedInstanceState);
AuctionApplication<Activity>.GetInstance(this, newAuctionNavigator(), TaskScheduler.FromCurrentSynchronizationContext()).Start(); }}
Note: The initial definition of the AuctionApplication in a view is also the Entry Point
MVVM+ Navigator for iOS
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
Model• Entities (POCO)
ServiceAgent• Service Entities
• Service Events
• Service Methods
Application• ViewModels instance
• Navigation context
• Navigation transitions
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Navigator• Navigate to view
implementation
Entry
Point
public class AuctionNavigator : IAuctionNavigator<UIViewController>
{
public void NavigateToCheckoutView(UIViewController context)
{CheckoutViewController checkoutView =
context.Storyboard.InstantiateViewController ("CheckoutViewController")
as CheckoutViewController;context.NavigationController.PushViewController(checkoutView, false);}
public void NavigateToAuctionView(UIViewController context){context.NavigationController.PopToRootViewController (false);}
}
MVVM+ View for iOS
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
Model• Entities (POCO)
ServiceAgent• Service Entities
• Service Events
• Service Methods
Application• ViewModels instance
• Navigation context
• Navigation transitions
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Navigator• Navigate to view
implementation
Entry
Point
public partial class ViewController<TViewController, TViewModel> : UIViewController, IView<TViewModel>
where TViewController: ViewController<TViewController, TViewModel>
where TViewModel : INotifyPropertyChanged{
public TViewModel ViewModel { get; set; }
public override void ViewDidLoad (){
ViewModel = AuctionApplication<UIViewController>.GetInstance ().GetViewModel<TViewModel> ();
base.ViewDidLoad ();}
}
MVVM+ View for iOS
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
Model• Entities (POCO)
ServiceAgent• Service Entities
• Service Events
• Service Methods
Application• ViewModels instance
• Navigation context
• Navigation transitions
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Navigator• Navigate to view
implementation
Entry
Point
public partial class AuctionViewController : ViewController<AuctionViewController, AuctionViewModel>{
public AuctionViewController (IntPtr handle) : base(handle)
{InvokeOnMainThread (delegate {
AuctionApplication<UIViewController>.GetInstance (this, new AuctionNavigator (),
TaskScheduler.FromCurrentSynchronizationContext ()).Start ();});
}}
Note: The initial definition of the AuctionApplication in a view is also the Entry Point
MVVM – Composite ViewModels
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
MVVM – Composite ViewModels Android
MVVM – Composite ViewModels Android
public class AuctionFragment : Fragment<AuctionFragment, AuctionViewModel>{
}
public class AuctionFragment : Fragment<AuctionFragment, AuctionViewModel>{
}
public class Fragment<TFragment, TViewModel> : global::Android.Support.V4.App.Fragment, IView<TViewModel> where TFragment : Fragment<TFragment, TViewModel>
where TViewModel : INotifyPropertyChanged{
public TViewModel ViewModel { get; set; }
public override void OnCreate(Bundle savedInstanceState){
base.OnCreate(savedInstanceState);ViewModel = AuctionApplication<Activity>.GetInstance().GetViewModel<TViewModel>(); }
}
MVVM – Composite ViewModels Android
<LinearLayoutandroid:orientation="vertical"android:layout_width=“fill_parent"android:layout_height="fill_parent"android:background="@color/white">
<fragment class="mobile.android.ProductListFragment"android:layout_width=“0dip"android:layout_height="match_parent"android:layout_weight="0.5"/>
<fragment class="mobile.android.ProductListFragment"android:layout_width=“0dip"android:layout_height="match_parent"android:layout_weight="0.5"/>
</LinearLayout>
MVVM – Composite ViewModels iOS
MVVM – Composite ViewModels iOS
public partial class AuctionViewController : ViewController<AuctionViewController, AuctionViewModel>{
}
public partial class ProductListController : DialogViewController<ProductListController, ProductsViewModel>{
}
public partial class ViewController<TViewController, TViewModel> : UIViewController, IView<TViewModel>where TViewController: ViewController<TViewController, TViewModel>
where TViewModel : INotifyPropertyChanged{
public TViewModel ViewModel { get; set; }
public override void ViewDidLoad (){
ViewModel = AuctionApplication<UIViewController>.GetInstance ().GetViewModel<TViewModel> ();base.ViewDidLoad ();
}}
MVVM – Composite ViewModels iOSAuctionViewController auctionViewController =
UIStoryboard.FromName ("MainStoryboard_iPad", null).InstantiateViewController("AuctionViewController")as AuctionViewController;
AuctionApplication<UIViewController>.GetInstance ().CurrentNavigationContext = this;
UIViewController productsViewController = UIStoryboard.FromName ("MainStoryboard_iPad", null).InstantiateViewController
("ProductListViewController") as UIViewController;
RectangleF rect = this.View.Bounds;rect.Width = rect.Width / 2;
UIView auctionView = auctionViewController.View;auctionView.Frame = new RectangleF (new PointF (0, 0), new SizeF (this.View.Bounds.Width / 2,
this.View.Bounds.Height));
UIView aboutView = productsViewController.View;aboutView.Frame = new RectangleF (new PointF (this.View.Bounds.Width / 2, 0), new SizeF
(this.View.Bounds.Width / 2, this.View.Bounds.Height));
this.View.AddSubviews (new UIView[] { auctionView, aboutView });
this.AddChildViewController (auctionViewController);this.AddChildViewController (productsViewController);
Demo: Windows Store App Composite
ViewModels – Design time
Demo: Windows Store App Composite
ViewModels – Design time
Demo: Windows Store App Composite
ViewModels - Runtime
MVVM+ Composite View for Windows Store App
– C#
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
Model• Entities (POCO)
ServiceAgent• Service Entities
• Service Events
• Service Methods
Application• ViewModels instance
• Navigation context
• Navigation transitions
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Navigator• Navigate to view
implementation
Entry
Point
public class UserControl<TViewModel> : UserControl{
protected UserControl(IApplication<Frame> application)
{ if (application != null) DataContext =application.GetViewModel<TViewModel>(); }
}
// This class can be eliminated once the x:TypeArguments attribute is supported in XAML: http://stackoverflow.com/questions/14478469/windows-8-store-application-support-for-xtypeargumentspublic abstract class AuctionViewBase :
Mobile.Presentation.W8.UserControl<MobileAuction.Shared.AuctionViewModel>
{public AuctionViewBase() :
base(App.AuctionApplication) { }}
MVVM+ Composite View for Windows Store App
- XAML
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
Model• Entities (POCO)
ServiceAgent• Service Entities
• Service Events
• Service Methods
Application• ViewModels instance
• Navigation context
• Navigation transitions
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Navigator• Navigate to view
implementation
Entry
Point
<local:AuctionViewBasex:Class="MobileAuction.W8.Views.AuctionView“d:DataContext="{d:DesignInstanceIsDesignTimeCreatable=True,Type=shared:AuctionViewModelStub}"…
<local:ProductsViewBasex:Class="MobileAuction.W8.Views.ProductsView“d:DataContext="{d:DesignInstanceIsDesignTimeCreatable=True,Type=shared:ProductsViewModelStub}"…
<views:AuctionView d:DataContext="{d:DesignInstanceIsDesignTimeCreatable=True,Type=shared:AuctionViewModelStub}" /><views:ProductsView d:DataContext="{d:DesignInstanceIsDesignTimeCreatable=True,Type=shared:ProductsViewModelStub}" />
MVVM+ Runtime flow
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
Model• Entities (POCO)
ServiceAgent• Service Entities
• Service Events
• Service Methods
Application• ViewModels instance
• Navigation context
• Navigation transitions
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Navigator• Navigate to view
implementation
Entry
Point
Runtime flow: Application starts with AuctionView, user places bid and navigates to CheckoutView.1. Entrypoint is called, creates Application instance
and calls Application.Start().
1
MVVM+ Runtime flow
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
Model• Entities (POCO)
ServiceAgent• Service Entities
• Service Events
• Service Methods
Application• ViewModels instance
• Navigation context
• Navigation transitions
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Navigator• Navigate to view
implementation
Entry
Point
Runtime flow: Application starts with AuctionView, user places bid and navigates to CheckoutView.1. Entrypoint is called, creates Application instance
and calls Application.Start().2. Application ctor creates ServiceAgent
Start() creates AuctionViewModel + ProductsViewModel
1
2
2
MVVM+ Runtime flow
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
Model• Entities (POCO)
ServiceAgent• Service Entities
• Service Events
• Service Methods
Application• ViewModels instance
• Navigation context
• Navigation transitions
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Navigator• Navigate to view
implementation
Entry
Point
Runtime flow: Application starts with AuctionView, user places bid and navigates to CheckoutView.1. Entrypoint is called, creates Application instance
and calls Application.Start().2. Application ctor creates ServiceAgent
Start() creates AuctionViewModel + ProductsViewModel3. Application calls
_auctionNavigator.NavigateToAuctionView()
1
2
3
2
MVVM+ Runtime flow
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
Model• Entities (POCO)
ServiceAgent• Service Entities
• Service Events
• Service Methods
Application• ViewModels instance
• Navigation context
• Navigation transitions
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Navigator• Navigate to view
implementation
Entry
Point
Runtime flow: Application starts with AuctionView, user places bid and navigates to CheckoutView.1. Entrypoint is called, creates Application instance
and calls Application.Start().2. Application ctor creates ServiceAgent
Start() creates AuctionViewModel + ProductsViewModel3. Application calls
_auctionNavigator.NavigateToAuctionView()4. Navigator calls platform-specific navigate to
AuctionView
1
2
3
4
2
MVVM+ Runtime flow
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
Model• Entities (POCO)
ServiceAgent• Service Entities
• Service Events
• Service Methods
Application• ViewModels instance
• Navigation context
• Navigation transitions
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Navigator• Navigate to view
implementation
Entry
Point
Runtime flow: Application starts with AuctionView, user places bid and navigates to CheckoutView.1. Entrypoint is called, creates Application instance
and calls Application.Start().2. Application ctor creates ServiceAgent
Start() creates AuctionViewModel + ProductsViewModel3. Application calls
_auctionNavigator.NavigateToAuctionView()4. Navigator calls platform-specific navigate to
AuctionView5. View base class gets ViewModel from Application1
2
3
4
5
2
MVVM+ Runtime flow
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
Model• Entities (POCO)
ServiceAgent• Service Entities
• Service Events
• Service Methods
Application• ViewModels instance
• Navigation context
• Navigation transitions
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Navigator• Navigate to view
implementation
Entry
Point
Runtime flow: Application starts with AuctionView, user places bid and navigates to CheckoutView.1. Entrypoint is called, creates Application instance
and calls Application.Start().2. Application ctor creates ServiceAgent
Start() creates AuctionViewModel + ProductsViewModel3. Application calls
_auctionNavigator.NavigateToAuctionView()4. Navigator calls platform-specific navigate to
AuctionView5. View base class gets ViewModel from Application6. On platforms without native data binding, the view
subscribes itself to PropertyChanged and CollectionChanged on ViewModel, for select (groups of) properties (it is not necessary to bind to each individual property). Also the commands of the views are invoked from code in an event handler.
1
2
3
4
5 6
2
MVVM+ Runtime flow
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
Model• Entities (POCO)
ServiceAgent• Service Entities
• Service Events
• Service Methods
Application• ViewModels instance
• Navigation context
• Navigation transitions
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Navigator• Navigate to view
implementation
Entry
Point
Runtime flow: Application starts with AuctionView, user places bid and navigates to CheckoutView.1. Entrypoint is called, creates Application instance
and calls Application.Start().2. Application ctor creates ServiceAgent
Start() creates AuctionViewModel + ProductsViewModel3. Application calls
_auctionNavigator.NavigateToAuctionView()4. Navigator calls platform-specific navigate to
AuctionView5. View base class gets ViewModel from Application6. On platforms without native data binding, the view
subscribes itself to PropertyChanged and CollectionChanged on ViewModel, for select (groups of) properties (it is not necessary to bind to each individual property). Also the commands of the views are invoked from code in an event handler.
7. The viewmodel is updated by events from service and commands from view. The view updates w data binding.
1
2
3
4
5 6
72
MVVM+ Runtime flow
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
Model• Entities (POCO)
ServiceAgent• Service Entities
• Service Events
• Service Methods
Application• ViewModels instance
• Navigation context
• Navigation transitions
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Navigator• Navigate to view
implementation
Entry
Point
Runtime flow: Application starts with AuctionView, user places bid and navigates to CheckoutView.1. Entrypoint is called, creates Application instance
and calls Application.Start().2. Application ctor creates ServiceAgent
Start() creates AuctionViewModel + ProductsViewModel3. Application calls
_auctionNavigator.NavigateToAuctionView()4. Navigator calls platform-specific navigate to
AuctionView5. View base class gets ViewModel from Application6. On platforms without native data binding, the view
subscribes itself to PropertyChanged and CollectionChanged on ViewModel, for select (groups of) properties (it is not necessary to bind to each individual property). Also the commands of the views are invoked from code in an event handler.
7. The viewmodel is updated by events from service and commands from view. The view updates w data binding.
8. A command on a viewmodel can invoke the ContinueTo… method on application … continue with step 3.
1
2
3
4
5 6
782
MVVM+ Runtime flow
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
Model• Entities (POCO)
ServiceAgent• Service Entities
• Service Events
• Service Methods
Application• ViewModels instance
• Navigation context
• Navigation transitions
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Navigator• Navigate to view
implementation
Entry
Point
Runtime flow: Application starts with AuctionView, user places bid and navigates to CheckoutView.1. Entrypoint is called, creates Application instance
and calls Application.Start().2. Application ctor creates ServiceAgent
Start() creates AuctionViewModel + ProductsViewModel3. Application calls
_auctionNavigator.NavigateToAuctionView()4. Navigator calls platform-specific navigate to
AuctionView5. View base class gets ViewModel from Application6. On platforms without native data binding, the view
subscribes itself to PropertyChanged and CollectionChanged on ViewModel, for select (groups of) properties (it is not necessary to bind to each individual property). Also the commands of the views are invoked from code in an event handler.
7. The viewmodel is updated by events from service and commands from view. The view updates w data binding.
8. A command on a viewmodel can invoke the ContinueTo… method on application … continue with step 3.
1
2
3
4
5 6
782
MVP+ Shared Code
Presenter• Properties
• INotifyPropertyChanged
implemented
Model• Entities (POCOs)
ServiceAgent• Service Methods
• Service Events
• Connection Management
MVVM+ Shared Code
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
Model• Entities (POCO)
ServiceAgent• Service Entities
• Service Events
• Service Methods
Application• ViewModels instance
• Navigation context
• Navigation transitions
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
Navigator• Navigate to view
implementation
Entry
Point
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
• Navigation logic
Entry
Point
Pattern Comparison
MVP+ Shared Code MVVM+ Shared Code
Presenter.Current.PropertyChanged += OnCurrentChanged;
private void OnCurrentChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e){if (e.PropertyName == "ReservedLot"){if (null != Presenter.Current.ReservedLot)
{ Activity.RunOnUiThread(delegate{ BuyLot(); }); }}}}
private void BuyLot(){if (null != Presenter.Current.ReservedLot){Intent i = new Intent(Activity,
typeof(FinalizeOrderActivity));StartActivity(i);}
}
View• Markup
• Data binding code
(if not supported in markup)
• UI Manipulation Code
ViewModel• Properties
• Commands
• INotifyPropertyChanged,
INotifyCollectionChanged
private void OnBidPlaced(object sender, BidPlacedEventArgs e){
if (e.PlaceBidResult.BidIsAccepted){
var lot = e.PlaceBidResult.Lot;var basket = new Basket{
// Basket Init code here};
_auctionApplication.ContinueToCheckout(basket);}public class AuctionNavigator :
IAuctionNavigator<Activity>{
public void NavigateToCheckoutView(ActivitynavigationContext)
{Intent i = new Intent(navigationContext,
typeof(CheckoutActivity));navigationContext.StartActivity(i);
}
MVP+ Shared Code MVVM+ Shared Code
Android Specific
26%
Shared Code
74%
CODE SHARE
Android Specific
52%
Shared Code
48%
CODE SHARE
Pro/Con of MVP+ versus MVVM+MVP+ MVVM+
Pro • Less total Code – esp for cases
where API matches view closely.
• Simpler pattern to apply
(write)
• First client by single developer takes
less time
• Higher % shared code
• Second cmeters to viewlient takes less
time
• Working application can be developed
by any .NET developer; no mobile
development tools, licenses, hardware,
skills needed.
(mobile devs are more scarse and more
expensive than.NET devs)
• Consistent behaviour across platforms
easer
• Application logic simpler to understand
(read)
• Fully unit testable
• Can pass complex objects as para
Con Inverse of MVVM+ Pro’s Inverse of MVP+ Pro’s
Next Steps, Resources, QuestionsNext for MVVM+
• Optimize for less total LOC
• Possibly automation for repetitive development steps
(e.g. add a property)
• Possibly open source an example implementation.
Resources:
• http://stackoverflow.com/questions/2056/what-are-mvp-and-mvc-and-
what-is-the-difference
• http://propertycross.com/
• http://braincells2pixels.wordpress.com/2013/06/07/the-case-for-xamarin/
• http://vincenth.net
Questions?
kd@macaw.nl; for MVVM shared code questions: maurice.peters@macaw.nl
Recommended