Open p-sheth opened 7 years ago
Both works. I've seen viewmodels used; personally I just use the codebehind, because I don't need much logic in there for my app (and also I want to make it easy to port from Xamarin.Forms to native Xamarin.Android/iOS for experimentation, which AFAIK do not support data binding out of the box).
Unless it’s a throwaway prototype, you shouldn’t put any access to the Store into the code-behind.
MVVM allows you to write tests that interact with your ViewModel and simulate view interactions without the view by giving you a clear separation of UI from non-UI logic. Communicating with your Store in the code behind is therefore technically an MVVM violation because, without the view, you can’t test this code.
MVVM and Redux don’t fit well together because MVVM and bindings break the one-way data flow of Redux/React Architecture. MVVM and bindings also make direct changes to the target property they are bound to on the model, but this is impossible when you have an immutable store with immutable models.
A typical pattern you see with MVVM is some data retrieval, via a service or repository data source, in the ViewModel. SOLID and SRP purists would have a problem with this as a ViewModel should only have one responsibility, and that’s to adapt your model to the view. Once your application grows, it makes more sense to keep the logic in the Model and wrap it with a ViewModel when interactions like Commands are required.
A possible solution to your question:
This will keep your Store and MVVM cleanly separated, which in my opinion is a good thing, as they are solving two different problems.
I've moved to F# (not really relevant for this comment) and am using a solution I find very clean and easily testable, which allows for unidirectional data flow of most state while using MVVM/INotifyPropertyChanged for e.g. input field state (which doesn't work well with redux - dispatching every new value of text fields to the store has several problems, e.g. poor performance and chance of triggering infinite loops since you're in a sense setting the text field value from its TextChanged event handler, with no optimal solutions).
What I do is this (using a sign-in page as example, and rough pseudocode as I'm writing this from the top of my head and haven't used C# in a while):
The store state (and everything in my main business logic) is immutable and works according to normal Redux patterns.
My view model, SignInVm
, has a corresponding class called SignInVmData
that is simply the relevant parts of the Redux state. The view model itself, SignInVm
, has a mutable property of type SignInVmData
. (The reason I use a separate class called SignInVmData
instead of just using the root redux state is that only having to deal with the relevant subset of the root state makes the VM much easier to test.) The view model also has any other relevant mutable fields for state that is difficult to keep in the redux state, e.g. text fields (username and password) and current time (when needed to e.g. calculate the time since some timestamp in the redux state).
class SignInVmData {
SignInVmData(RootState state) {
// The constructor is uninteresting to test since it just maps the redux state
IsSigningIn = state.SignIn.IsSigningIn;
LastSignIn = state.SignIn.LastSignIn;
}
// for easier testing (F# solves this more robustly with less boilerplate)
SignInVmData() { }
public bool IsSigningIn {get; set;}
public DateTimeOffset LastSignIn {get; set;}
}
class SignInVm {
SignInVm(SignInVmData data) {
_data = data;
}
public SignInVmData _data {get; set;}
public DateTimeOffset _now {get; set;} = DateTimeOffset.Now
public string Username {get; set;}
public string Password {get; set;}
public bool InputFieldsEnabled =>
!_data.IsSigningIn;
public bool CanSignIn =>
!string.IsNullOrEmpty(Username)
&& !string.IsNullOrEmpty(Password)
&& !_data.IsSigningIn;
// Fairly pointless thing on a sign-in screen, it's here just to demo the time stuff
public string SecondsSinceLastSignIn =>
(_now - _data.LastSignIn).TotalSeconds.ToString();
public bool SignInAction =>
CanSignIn ? SignInAction(Username, Password) : null;
}
As you can see above, all other properties (InputFieldsEnabled
, CanSignIn
, etc.) are derived from the mutable properties. This makes things trivially testable. It does of course require some way to handle INotifyPropertyChanged dependencies among the properties - I use Nito.Mvvm.CalculatedProperties which is great, you might also want to check out PropertyChanged.Fody for automatic weaving with no boilerplate but it's much more limited (e.g. no cross-class dependencies if you ever need that, and no lambdas in F# at least because those are compiled as separate classes).
Note that I also have (calculated) properties for all actions that the view can dispatch to the store. These are surfaced as simple action objects, and bound to from XAML ICommand
properties using a custom IValueConverter
(that gets the store injected in static property at app startup) that converts an action object to an ICommand
that dispatches this action to the store. The commands are "immutable" in that CanExecute
never changes. It can always execute unless the action is null
(None
in F#), and as you have seen, it's the VM that controls this. This makes the action dispatching simple to test and fairly foolproof in practical use, since a null
action of course can not possibly lead to an erroneous action being dispatched to the store (at worst you get a NullReferenceException
in your converter which you discover quickly, fix once and never hear from again for any properties on any VM).
All views take in a store instance in the constructor, instantiates the VM (parameterless constructor), and wires up changes to the store state (mapped to SignInVmData
) to the mutable property on the VM. If the VM has a mutable property used for the current time, that is wired up too (using Observable.Interval
or your favorite timer function). I use weak references for all of this so I don't have to think about unsubscribing to avoid memory leaks.
class SignInPage : MyViewBaseClass {
SignInPage(Store store) {
var vm = new SignInVm(new SignInVmData(store.State));
this.BindingContext = vm;
store.StateChanged += state => vm._data = SignInVmData(state)
SomeOnceASecondTimer.Elapsed += () => vm._now = DateTimeOffset.Now
}
}
All in all, the advantages are:
ICommand
s that dispatch the action to the store using well-defined (and, for once, robust) null-value semantics. It's simple to test - if an action property on the VM is null
, you know that nothing can be dispatched to the store and that the command will have CanExecute
set to false
(using your IValueConverter
).
Hi,
I am working on Xamarin application and have been using MVVM so far. I am now trying to incorporate Redux to manage the state.
My question is, which a good place to call Store.Dispatch()? Should I keep it in code behind of Xaml or viewmodel?
It works either ways, but I want to understand which is a better practice? Should I continue using ViewModels after adopting Redux or just wire the state directly to View?
Thanks