Managing local data involves challenges the architecture of every mobile app needs to address. Redux is a JavaScript library to solve that very issue for web apps, but its core concepts also fit perfectly into the the mobile app space.
Published on Tue, February 20, 2018
tl;dr: This post explains why Redux might be a good thing for mobile apps, why C# is far from ideal for implementing it, why and how F# solves that problem, and finally how to implement a basic Redux Store with F# for a C# Xamarin app. Still not interested in the details? You can check out the result on GitHub, too.
When you’re developing mobile apps that work with some API you know the pitfalls. It’s nice to get data as easy as with a simple GET request. As long as the API is available, the device is online, and online means its connection is not only fast but also reliable. So you often end up with some kind of offline support to not render the app completely unusable if the circumstances are not ideal.
The moment you introduce offline capabilities, you introduce caching. And that implies that after adding, updating, or removing some of the data you have cached locally, the cache must be invalidated and/or repopulated. And last but not least you want those changes to be reflected on the surface immediately. That's a lot to do.
What's usually very common in the wild is a mix of relational, document, or key/value based storage in conjunction with some messaging to keep the UI up to date.
So you’re storing and updating data locally more or less elegantly in a database or some files on disk, and send a message around when anything has been changed so your view models can react.
For example when a view is displaying say a list of users, its view model will be a subscriber to a specific message that’s sent whenever a user is changed so it can re-render the list (be it a full reload or just an update to an observable data source).
That will work. But caching is opening its own can of worms. And sending messages criss-cross also quickly becomes hardly comprehensible and understandable as soon as the app grows.
Redux is a predictable state container for JavaScript apps. (Source)
So what does that mean? The good news is: It's not as complicated as it may sound.
The library is around since 2015 and has since become almost mainstream, at least within the ever-growing React community. While it's of course no silver bullet and there are of course alternatives around, I liked the simplicity and power of the concept behind the actual JavaScript implementation from the beginning.
The point is to have one single truth of all the data that's important for the app to handle. That truth is called the State which is stored in the Store. Think of a JSON document or a POCO containing all the data you normally would put in a cache like described before.
One of the most important things to note is that the State is immutable. Meaning whenever something changes, a new version of the whole State is being created. This makes it not only easier to reason about the State and therefore the eventual behavior of your app, it also allows to implement some cool stuff like time travel debugging (which I won't cover here).
New versions of the State are being created by Reducers, which are small and pure functions (meaning having no side effects) triggered by Actions, which you can think of as messages.
Last but not least anyone can subscribe to the Store to be notified when the State changes. Which directly leads to a strict unidirectional data flow as pictured in the graphic above. (Sorry for my horrible handwriting.)
Let's say we want to build a simple app with 3 views: a list of users, a user's profile, and a form to update a user's profile data:
All of our three views are acting as Subscribers to the Store, so they get notified as soon as any changes occur to the State.
When our selected user's profile is being updated, an Action is being dispatched, containing the update information as payload.
The Store is routing that action internally to all registered Reducers. We will have one specific reducer in place that's taking care of creating a new version of our State containing the updated user.
That new version of our State will then be set as the Current State and all subscribers are getting notified about that change.
Which eventually leads to "automatic" updates on all subscribed views, which are then able to re-render themselves.
There are already implementations of Redux for .NET available, for example redux.NET and reducto – check them out! But as the core concept is as simple as it is, I don't think it is a bad idea to go your own way. That's what I did, and those were my basic requirements:
I want the State not to be mutable. Simply because I think enforcing that every change to it is applied by a reducer function from inside the Store is a good idea. So it's always clear where an update is coming from and the possibility of side effects is limited.
Implementing Actions and Reducers must not come with a lot of boilerplate code involved so it is possible to maintain a reasonable amount of code for those jobs as the overall complexity is growing over time.
It must be possible to constrain the surface a single subscriber receives updates for, so updates are only triggered for those subscribers that are particularily interested in the specific branch of the State that has really been changed.
Example: Let's say we have a collection of users and next to it a collection of cities in our State. Now I don't want a list of users in the UI to be re-rendered just because some city has been added or removed.
I am not (yet) a functional fundamentalist. And as a team lead I not only have to deal with technical issues but first of all with reality out there. So the first implementation has obviously been written in the language my team and most of my clients do prefer as their main programming language, which still is C#.
Designing the State to be immutable is tricky with the current state of C# as of early 2018. Not so much for collections, but for particular properties. Making it impossible to change a value for a property directly (e.g. with private setters) results in a lot of code which has to be written (or generated).
class User
{
public string FirstName { get; private set; }
public string LastName { get; private set; }
// ...
public User(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
private User() {}
public User WithFirstName(string firstName)
{
return new User
{
FirstName = firstName,
LastName = LastName
};
}
public User WithLastName(string lastName)
{
return new User
{
FirstName = FirstName,
LastName = lastName
};
}
// ...
}
var miss = new User("Sophie", "X"); // Sophie X
var mrs = miss.WithLastName("Y"); // Sophie Y
Technically FirstName
and LastName
aren't even immutable here, the private setters just make it impossible to change their values from the outside. But from within an instance of a user any change would be possible. Using read-only fields or leaving out the setter on the other hand would make it impossible to implement convenient methods like WithFirstName()
and require you to go constructor-only. So using private setters in conjunction with a clear convention looks like an acceptable compromise.
Pattern matching came just in time with C# 7, but even with that neat feature some simple operations produce a lot of code. Let's take a basic scenario again: Sophie got married (or divorced, whow knows) and therefore has changed her name:
public interface IReduxAction {}
public class AppState
{
public ImmutableList<User> Users { get; private set; }
public AppState(IEnumerable<User> users)
{
Users = users.ToImmutableList();
}
private AppState() {}
public AppState WithUsers(IEnumerable<User> users)
{
return new AppState
{
Users = users.ToImmutableList()
};
}
}
public class UserChangedLastNameAction : IReduxAction
{
public Guid UserId { get; }
public string NewLastName { get; }
public UserChangedLastNameAction(Guid userId, string newLastName)
{
UserId = userId;
NewLastName = newLastName;
}
}
public static class UserReducer
{
public static AppState Reduce(AppState state, IReduxAction action)
{
switch (action)
{
case UserChangedLastNameAction userChangedLastName:
{
var user = state.Users
.SingleOrDefault(u => u.Id == userChangedLastName.UserId);
if (user == null)
{
return state;
}
var updatedUser = user
.WithLastName(userChangedLastName.NewLastName);
var updatedUsers = state.Users
.Where(u => u.Id != userChangedLastName.UserId)
.Concat(new[] { updatedUser });
return state.WithUsers(updatedUsers);
}
}
return state;
}
}
Phew... That's a lot of code and quite the opposite of "little boilerplate code".
I wrote earlier that I only want to receive updates for the part of the State I am specifically interested in. Let's imagine an app that is running on a tablet having a split aka master detail view, which displays all users we know in a list on the left, and the currently selected one with some more information on the right. Let's also say we selected Sophie and now some background job is updating the user list in the State because on another device someone added just another user and that change just got synchronized.
What we want to do now for the detail view model is something like this:
var userId = ... // Sophie's id we already know here
Store.Subscribe(
state => state.Users.FirstOrDefault(u => u.Id == userId),
user =>
{
// Callback function that is only
// called when Sophie did change
}
);
Meaning: fire the callback whenever Sophie is being updated, but ignore any other update to the state, including other users.
So what we need here is something that is able to evaluate the expression we pass as the first argument to Subscribe()
, to apply it to the last known State first and the [by our Reducers] newly computed State second, and to compare the two results. But that comparison must work "on the content".
Example:
var user1 = new User("Sophie", "X");
var user2 = new User("Sophie", "X");
Even if those two users are two different instances they must be considered to be the same, as we're not interested in the memory addresses but their "content" ("Sophie" is equal to "Sophie" and "X" is equal to "X", too).
Turns out this is a tough job in C#, because it must be implemented by yourself on every type you want to compare that way. And whatever approach you chose, doubts about the correctness will always remain, as there's no built-in mechanism that checks that kind of stuff, say in the compiler. You are going to rely completely on conventions (again!) and developer tooling, or third-party libraries.
Would it be possible to fulfil the requirements with C#? Absolutely. Would it require you to write or generate tons of possibly fragile and unmaintainable code? Yes.
And then there was Abraham Maslow, saying about C# in 1966:
"I suppose it is tempting, if the only tool you have is a hammer, to treat everything as if it were a nail."
(Little Joke ;-))
Lucky us, there's this rising star in the .NET ecosystem, with that strange community and that awkward syntax. But maybe it's worth trying. So let's swallow our C# developer's pride and have a look.
F# has come a long way in the .NET world and it's shape today seems to be better than ever. The community is growing, tooling is okayish with support in Visual Studio [for Mac], Rider, and Visual Studio Code. And, believe it or not, it's possible to mix up C# and F# in a .NET solution and to consume F# stuff from C# parts.
Yes, tooling is not perfect yet, but I didn't come across any issue while implementing this Redux stuff with F# in a real-world project that turned out to be a show-stopper. And if you could ask people familiar with the matter, they would tell you how sceptial I've been just one year ago ;-).
Now let's take a look on how F# is helping us here.
type User =
{ FirstName : string
LastName : string }
let miss = { FirstName = "Sophie"; LastName = "X" }
let mrs = { miss with LastName = "Y" }
That's it. Immutability comes built-in by default, and so comes the nice copy and update syntax, which we tried to emulate in our C# version with methods like WithFirstName()
.
type AppState =
{ Users : User array }
type Action =
| UserChangedLastName of Guid * string
let updateLastName user userId newLastName =
if user.Id = userId then
{ user with LastName = newLastName }
else
user
let reduce state action =
match action with
| UserChangedLastName (userId, newLastName) ->
{ state with
Users =
state.Users
|> Array.map (fun u -> updateLastName u userId newLastName) }
That's it. Not half the amount of code of the previous C# sample. (And if you don't care about readability of your code, you could even write it in only 4 lines.)
If you're already familiar with F# and wonder why I use an array of users instead of a list – that's for better interoperability with C# later. As
list
is a specific part of F# itself andarray
is not, it makes life a lot easier when consuming this stuff from C# code.
If you're already hooked, wait for this one. My requirement didn't change – I want a subscription kind of thing like presented earlier, and that subscription must only include updates I am specifically interested in.
That's now coming for free, as most F# types have built-in Structural Equality. So if you compare those two users...
let user1 = { FirstName = "Sophie"; LastName = "X" }
let user2 = { FirstName = "Sophie"; LastName = "X" }
let isEqual = user1 = user2 // is true
...they will always turn out to be equal. Which will make it fairly easy to build that subscription thing, as we can easily compare any complex object within our Store without needing anything to weave in or code to be generated by our IDE. It's all done by the compiler for us.
There are a lot of valid arguments against the usage of F# I usually accept, mainly from a business point of view. But if you are open to it and if you want to implement something like this Redux concept, the question should not be if you use F# for it, but how. The time is right to give it a spin!
For the purpose of this exercise we're going to build a simple Xamarin.Forms application for both iOS and Android. Our project structure may look like:
- XamarinReduxDemo.Core (.NET Standard Library, C#)
- XamarinReduxDemo.Store (.NET Standard Library, F#)
- XamarinReduxDemo.Droid (Xamarin.Android Project)
- XamarinReduxDemo.iOS (Xamarin.iOS Project)
Core contains everything you may want to share between your two platform targets, as view models and the like. Droid and iOS are self-explaining, only Store is falling out of alignment.
That's our F# project containing Actions, Reducers, the Store itself, and all Domain objects. The latter is important.
If you want to benefit from all that sweet stuff that F# provides, like structural equality, immutability by default, and other things like sum types (you are going love it, trust me), you obviously need to define the types in F# rather than C#. So for the start you will end up with some structure within the Store project like:
- XamarinReduxDemo.Store
-- Domain.fs (All your Entities, Value Objects, and other types.)
-- Actions.fs (Store Actions)
-- State.fs (Defines the [initial] State of your Store.)
-- Reducers.fs (Store Reducers, processing Actions to "update" the State.)
-- Store.fs (The actual implementation of the Store itself.)
The concrete implementation for your application lies in the first four files.
Maybe you even want to put Store.fs
into its own assembly, as it is unlikely to change often and may scare away fellow colleagues ;-). I have to admit it's "a bit" more complex than the other parts, but it's coming with some features that simply make it nice to work with.
It's designed to be easily consumable by C# code, so first of all it provides an interface called IStore
that makes it possible to work with dependency injection as you're used to. That interface provides:
CurrentState
Unsurprisingly the current State. You may want to use it for example when initializing a view model.Subscribe()
The method that allows anyone who's interested in updates to the State to subscribe to it. It's possible to get any updates to the State, or only those of a particular subtree.Unsubscribe()
As we've learned, cleaning up is important, so we can not only subscribe to but also unsubscribe from the Store, for example when closing a view [model].Dispatch()
The method that enables you to send Actions to the Store, so any (internally) registered Reducer can process it.Apropos reducers. You will want to create a "Root Reducer" that is bundling all others.
That "Root Reducer" is then being passed along with the initial State to the Store when it is being created. The Store itself will live as a singleton in your application.
A good starting point may be your application class (Forms), AppDelegate (iOS), or your main activity (Android):
private void SetUpReduxStore()
{
IStore store = CreateStore();
MvvmNanoIoC.RegisterAsSingleton(store);
}
private static IStore CreateStore()
{
IStore store = new Store.Store(
// Loads up the instance of the Store with the last known State.
// In this example a new state is always being created, but that's
// the place where you should load your current State from disk
// or whatever you prefer for persisting data.
InitialState.Create(),
// Register all Reducers that should be applied to dispatched
// actions.
RootReducer.Create()
);
store.Subscribe(SubscriptionType<AppState>.NewStateSubscription(state =>
{
// The State was updated. That's the moment where you
// should persist the current state.
}));
return store;
}
Consuming F# stuff from C# is generally possible, as both languages are running on the same platform, share the same base class library, etc. That being said, it makes sense to take care to use the lowest common denominator such as Action<T>
or System.Array
.
From my experience you will usually have the same workflow for a view model all the time. First you want to set up the subscription, second you want to initialize your view model with all the stuff from the Store, third you want to be notified as soon as anything changes that's of particular interest for you at this point. That's where the following method can help you when put in a base class for all of your view models:
protected void ConnectToStore<TSelection>(
Func<AppState, TSelection> selector,
Action<TSelection> callback
)
{
// Subscribe to the Store
var subscription = Store.Subscribe(
SubscriptionType<TSelection>.NewSelectionSubscription(
selector,
callback
)
);
// Remember that subscription so we can clean up later
_disposables.Add(subscription);
// Execute the callback immediately so we can initialize
// the view model with the same logic we're doing "updates"
var selection = selector(Store.CurrentState);
callback(selection);
}
That way you end up with just one single method in your view model that is handling both initialization and updates.
That may now look like:
public override void Initialize(UserId userId)
{
_userId = userId;
ConnectToStore(
state => state.Users.SingleOrDefault(u => Equals(u.Id, _userId)),
user =>
{
if (user == null)
{
return;
}
Debug.WriteLine($"User Profile: Loading {user.Name}.");
Name = user.Name;
ImageName = user.Id.Item + ".jpg";
}
);
}
So your view model does not need to care anymore whether it is initializing or updating, it's all becoming the same thing.
Once you want the State to be updated, you are going to dispatch an Action:
type StoreAction = UserUpdated of User
private MvvmNanoCommand _saveCommand;
public MvvmNanoCommand SaveCommand
=> _saveCommand ?? (_saveCommand = new MvvmNanoCommand(OnSave));
private void OnSave()
{
var updatedUser = new User(_userId, Name);
Store.Dispatch(StoreAction.NewUserUpdated(updatedUser));
}
One more F# kindness here: The New*()
function listet above is automatically being generated by the F# compiler for us. Personally I think that's a very neat way to create sumtypes.
Enough words! If you want to learn more, take a look at the GitHub repository where you can find a small demo done with Xamarin.Forms.
When interested in learning more about Redux and its concepts, visit the official website. If you're looking for a good introduction to F#, check out F# for fun and profit and get yourself a copy of Get Programming with F#.