Page Navigation using MVVM pattern without using existing MVVM frameworks - xamarin

I am trying to implement MVVM pattern in my xamarin mobile project.
I have following files for MVVM
LoginView
LoginViewModel
BaseViewModel
Following is my LoginViewModel
public class LoginViewModel : BaseViewModel
{
private bool isLoginIndicator= false;
private string etUserName;
private string etPassword;
public LoginViewModel()
{
OnLogin = new Command(doLogin , ()=>!LoginIndicator);
MessagingCenter.Subscribe<IMessage, EventType>(this, RestApi.UI_EVENT, (sender, eventType) =>
{
LoginIndicator = false;
if (eventType.status)
{
Application.Current.MainPage.DisplayAlert(AppResources.success, "Login done", "Ok");
}
else
{
Application.Current.MainPage.DisplayAlert(AppResources.failed, eventType.errorMessage, "Ok");
}
});
}
public bool LoginIndicator
{
get { return isLoginIndicator; }
set
{
isLoginIndicator = value;
OnPropertyChanged("LoginIndicator");
OnLogin.ChangeCanExecute();
}
}
public string UserName
{
get { return etUserName; }
set
{
etUserName = value;
OnPropertyChanged("UserName");
}
}
public string Password
{
get { return etPassword; }
set
{
etPassword = value;
OnPropertyChanged("Password");
}
}
public Command OnLogin { get; }
void doLogin()
{
LoginIndicator = true;
UserRequest user = new UserRequest();
user.userName = etUserName;
user.password = etPassword;
user.companyId = "CEE";
user.appVersion = Constants.getAppVersion();
user.osVersion = Constants.getOSVersion();
user.deviceId = Constants.getDeviceModel() + " " + Constants.getDevicePlatform();
new RestApi().userLogin(JsonConvert.SerializeObject(user));
}
}
This class usually makes a webservice call when OnLogin command gets fired from Button and broadcast the Message using MessageCenter
Now i want to navigate to my MainPage which is master page once the user is logged in successfully hence i need to navigate to master page when eventType.status is true inside the Message Subscriber
but i don't know how can i properly navigate to other pages according to MVVM pattern.
i tried to search on net and i found there are ready made frameworks available like MVVMCross and MVVMLight etc. But i do not want to use those dependecies and willing to implement navigation some other way if anyone can suggest

MVVM says nothing about navigation, so basically every option will be fine.
The only thing against code like:
Application.Current.MainPage = new MyFirstPageAfterLogin();
Is that you now have a reference to a page from your ViewModel, which should not be what you want. That is why MVVM frameworks tend to implement a concept called ViewModel-to-ViewModelnavigation. With that, you can specify a ViewModel that you want to navigate to. Depending on the framework (or how they implemented it), they have you register a coupling first or use a naming convention. For instance; I like to use FreshMvvm, which does this by naming convention.
So when I want to navigate to the PageAfterLoginPage, I create a PageAfterLoginPageModel. From my ViewModel (or PageModel in Xamarin naming) I can now navigate to the PageModel, instead of making a hard reference to the page. This way, Page and PageModel are separated and I can easily swap out the View if I wanted to.
So, either use an already existing framework, or peek into their Github repo to see how they do it if you insist on doing it yourself.

With the latest tools do a File / New Project / CrossPlatform / Master-Detail. The master-detail template is all MVVM, without using any 3rd party frameworks. There are permutatations of native and forms. Great for learning and exploring.
Healy in Tampa.

Related

Xamarin Native, Binding actions to listview items

I would like to ask about bindings. What is the best approach to bind some actions in listview items in ios and android using xamarin in mvvm world. As I understand, we have few approaches.
1.
For every list item we have some Model, and to this model we have to add some Commands.
For example:
public class ItemModel
{
public string MyName { get; set; }
public ICommand RemoveCommand { get; set; }
}
Where in ViewModel we have SomeInitMethod
public class ViewModel
{
public ObservableCollection<ItemModel> Items {get;set;}
public async Task SomeInitMethod
{
Items = new ObservableCollection(await _myApiService.FetchItemsAsync());
foreach(var item in Items)
{
item.Command = new RelayCommand(RemoveItem);
}
}
public void RemoveItem(ItemModel item)
{
Items.Remove(item);
}
}
But I see a drawback in SomeInitMethod where we should set RemoveCommand. What if we should to set 2 or even more commands than we duplicate code in ListItemView(somehow we need to bind all these commands)?
Next approach is somehow handle events of remove/toggle buttons and others in Listview and then delegate this commands directly to ViewModel.
Example:
ContactsListView.ItemRemoveClicked += (ItemModel model) => ViewModel.RemoveItem
Advantages is: we no longer need to handle commands in ViewModel
Drawback is: we need every time to write custom ListView and support event handling in code-behind.
The last approach is to send ViewModel to ListItem to set Commands.
Example
somewhere we have method CreateListViewItem on the view, let's say on iOS.
private void InitTableView() {
TableView.RegisterNibForCellReuse(ItemViewCell.Nib, ItemViewCell.Key);
var source = new ObservableTableViewSource <ItemModel>
{
DataSource = ViewModel.Items,
BindCellDelegate = (cell, viewModel, index) =>
{
if (cell is ItemModel memberCell)
{
memberCell.BindViewModel(viewModel);
memberCell.RemoveItem = (item) => ViewModel.RemoveItem;
}
}
};
TableView.Source = source;
}
Advantages: we no longer need to have Commands in Model, and we don't need to setup this Commands in ViewModel.
Possibly, drawback is that we somehow need to have ViewModel reference.
In WPF or UWP you have DataContext, you can binding directly to ViewModel.
Which approach you use, maybe I miss something, and it would be perfect if you provide some examples or thoughts.
Thanks.

Dynamic ViewModel navigation

I am trying to find a way to be able to set from the View to what ViewModel I have to navigate. This is to be able to change the navigation flow without changing the core project.
I thought the easier way would be creating an interface, setting the target ViewModel there and injecting the interface into the ViewModel to then perform the navigation.
public interface IModelMapping
{
MvxViewModel ViewModelToNavigate();
}
public class MyViewModel : MvxViewModel
{
readonly IMvxNavigationService navigationService;
readonly IModelMapping modelMapping;
public MyViewModel(IMvxNavigationService navigationService, IModelMapping modelMapping)
{
this.navigationService = navigationService;
this.modelMapping = modelMapping;
}
public IMvxAsyncCommand GoContent
{
get
{
IMvxViewModel vm = modelMapping.ViewModelToNavigate();
IMvxAsyncCommand navigateCommand = new MvxAsyncCommand(() => navigationService.Navigate<vm>());
return navigteCommand;
}
}
}
The problem with this code is I am getting an error setting the navigationService.Navigate(). The error is 'vm is a variable but it is used like a type'
What about using the URI navigation together with the facade? See also https://www.mvvmcross.com/documentation/fundamentals/navigation#uri-navigation
Say you are building a task app and depending on the type of task you want to show a different view. This is where NavigationFacades come in handy (there is only so much regular expressions can do for you).
mvx://task/?id=00 <– this task is done, show read-only view (ViewModelA)
mvx://task/?id=01 <– this task isn’t, go straight to edit view (ViewModelB)
[assembly: MvxRouting(typeof(SimpleNavigationFacade), #"mvx://task/\?id=(?<id>[A-Z0-9]{32})$")]
namespace *.NavigationFacades
{
public class SimpleNavigationFacade
: IMvxNavigationFacade
{
public Task<MvxViewModelRequest> BuildViewModelRequest(string url,
IDictionary<string, string> currentParameters, MvxRequestedBy requestedBy)
{
// you can load data from a database etc.
// try not to do a lot of work here, as the user is waiting for the UI to do something ;)
var viewModelType = currentParameters["id"] == Guid.Empty.ToString("N") ? typeof(ViewModelA) : typeof(ViewModelB);
return Task.FromResult(new MvxViewModelRequest(viewModelType, new MvxBundle(), null, requestedBy));
}
}
}

UI action in middle of MvxCommand

I am using MvvmCross, but this may be general command binding.
When user click a button, the application require an extra input data before proceed to what I want to do in the actual command. The problem that I cannot call an UI action in middle of ViewModel, so just binding MvxCommand (or any ICommand) would not work.
One may ask why:
1) I don't put an input on the UI and user can enter data before click button -> I don't have space.
2) Make default data, and let user change it later -> This my first though, but user tend to forget to change it later!!
So can someone come up with a solution? The only thing I can think of is forgetting command binding, and have code behind pop the ui for extra data, then call a method in view model!
Thanks
There are several ways to do this.
My personal preferred way is to use an "Interaction Request" - something that I learnt from the Prism framework from Microsoft patterns and practices.
In Mvx, you can do this using an IMvxInteraction property on your ViewModel. An example of this is shown in https://github.com/slodge/BindingTalk/blob/master/BindingTalk.Core/ViewModels/QuestionViewModel.cs
Each time an interaction is requested, the ViewModel provides an object to the View - in this case a YesNoQuestion:
public class YesNoQuestion
{
public Action YesAction { get; set; }
public Action NoAction { get; set; }
public string QuestionText { get; set; }
public YesNoQuestion()
{
YesAction = () => { };
NoAction = () => { };
}
}
The ViewModel exposes the requester using an IMvxInteraction<TQuestion> property:
public class QuestionViewModel
: MvxViewModel
{
private MvxInteraction<YesNoQuestion> _confirm = new MvxInteraction<YesNoQuestion>();
public IMvxInteraction<YesNoQuestion> Confirm
{
get { return _confirm; }
}
public IMvxCommand GoCommand
{
get
{
return new MvxCommand(() =>
{
var question = new YesNoQuestion()
{
QuestionText = "Close me now?",
YesAction = () => Close(this),
};
_confirm.Raise(question);
});
}
}
}
The view on each platform can then bind and subscribe to the interaction request property. This is a little fiddly - because it uses weak references to prevent memory leaks - especially on iOS, but also possible on other platforms too.
Some example Droid code for this is in:
https://github.com/slodge/BindingTalk/blob/master/BindingTalk.Droid/Views/2%20%20More%20Controls/QuestionView.cs
with AXML in https://github.com/slodge/BindingTalk/blob/master/BindingTalk.Droid/Resources/Layout/QuestionView.axml
Sorry for the confusing ConfirmationView and QuestionView names here - the first is an Android View, the second is an Mvvm View and an Android Activity.
Also, please note that when implementing Dialogs in Android, then you need to be careful about screen rotation - as Android's Activity lifecycle can very much confuse things here - easiest mecahnism (I find) is to just handle screen rotation yourself rather than allowing Android to handle it.

Prism Shell buttons shared by modules

I am using Prism 2, trying to add four navigation buttons (First Record, Last Record, Previous Record, Next Record) in shell to be used by modules. I also want these buttons to be disable if active View/ViewModel does not provide these functions.
I tried using events but didn't know how to achieve my second goal regarding disabling buttons. It seems I need to check current active View/ViewModel to see if they subscribed the click event during View switch. But I think publisher should be unaware of subscriber...
Somehow I tried my own way. I create an IDocNavigation interface which has four method corresponding to my four buttons. At runtime I check modules' ViewModel if they implemented that interface or not, and change the ICommand on fly. Below is my code. I include one LastRecordCommand only:
public ShellViewModel(Views.Shell shell)
{
this.Shell = shell;
shell.DataContext = this;
shell.MainDocking.ActivePaneChanged += (s, e) =>
{
if (e.NewPane.Content is UserControl &&
((UserControl)e.NewPane.Content).DataContext is IDocumentNavigate)
{
IDocumentNavigate vm = ((UserControl)e.NewPane.Content).DataContext as IDocumentNavigate;
LastRecordCommand = new RelayCommand(x => vm.GotoLastRecord(), x => true);
}
else
{
LastRecordCommand = new RelayCommand(x => { }, x => false);
}
};
//...
I feel these are quite ugly. Creating an empty RelayCommand is also stupid. How can I improve ? or how can I achieve disabling command if event is more suitable in my case ?
You can make use of CompositeCommand in prism.
Define a globally available CompositeCommand
public static readonly CompositeCommand FirstRecord= new CompositeCommand(true);
Then in your your module view models
class Module1
{
public DelegateCommand Module1Firstrecord{ get; set; }
Module1()
{
Module1Firstrecord = new DelegateCommand(this.FirstRecord, CanExecute);
}
private void FirstRecord()
{
//do whatever you want
}
private bool CanExecute()
{
return true;
}
private void Module1_IsActiveChanged(object sender, EventArgs e)
{
//Find if your window is acive
// if it is active Module1Firstrecord.IsActive = true
//else false.
}
}
With IActiveAware you can handle the active window scenario easily. According to whether your active module have a handler for the command on not the buttons will enable/disable.

mvvmlight - what's the "proper way" of picking up url parameters for a view model

I'm just switching a project across to mvvmlight and trying to do things "the right way"
I've got a simple app with a listbox
When an item is selected in the listbox, then I've hooked up a RelayCommand
This RelayCommand causes a call on an INavigationService (http://geekswithblogs.net/lbugnion/archive/2011/01/06/navigation-in-a-wp7-application-with-mvvm-light.aspx) which navigates to a url like "/DetailPage.xaml?DetailId=12"
The DetailPage.xaml is then loaded and ... this is where I'm a bit unsure...
how should the DetailPage get hooked up to a DetailView with DetailId of 12?
should I do this in Xaml somehow using a property on the ViewLocator?
should I do this in the NavigatedTo method?
Please feel free to point me to a full sample - sure this has been done a (hundred) thousand times before, but all the blogs and tutorials seem to be skipping this last trivial detail (focussing instead on the messaging and on the ioc on on the navigationservice)
Thanks!
The only place you can retrieve the URL parameter is in the view. So since your view is likely depending on it, you should fetch it in the OnNavigatedTo method.
Then, you should pass it along to your viewmodel, either using messaging (to expensive if you ask me), or by referring to your datacontext (which is the viewmodel I presume), and execeuting a method on that.
private AddTilePageViewModel ViewModel
{
get
{
return DataContext as AddTilePageViewModel;
}
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var postalCode = NavigationContext.TryGetKey("PostalCode");
var country = NavigationContext.TryGetStringKey("Country");
if (postalCode.HasValue && string.IsNullOrEmpty(country) == false)
{
ViewModel.LoadCity(postalCode.Value, country);
}
base.OnNavigatedTo(e);
}
I'm using some special extensions for the NavigationContext to make it easier.
namespace System.Windows.Navigation
{
public static class NavigationExtensions
{
public static int? TryGetKey(this NavigationContext source, string key)
{
if (source.QueryString.ContainsKey(key))
{
string value = source.QueryString[key];
int result = 0;
if (int.TryParse(value, out result))
{
return result;
}
}
return null;
}
public static string TryGetStringKey(this NavigationContext source, string key)
{
if (source.QueryString.ContainsKey(key))
{
return source.QueryString[key];
}
return null;
}
}
}
Create a new WindowsPhoneDataBound application, it has an example of how to handle navigation between views. Basically you handle the navigation part in your view, then set the view's DataContext accord to the query string. I think it plays nicely with the MVVM pattern since your ViewModels don't have to know anything about navigation (which IMO should be handled at the UI level).

Resources