Jul
21

Advanced Redux in Xamarin Part 3: Database Middleware

posted on 21 July 2017 in programming

Warning: Please consider that this post is over 6 years old and the content may no longer be relevant.

In this final post in the series on advanced Redux in Xamarin, we’ll look at how to integrate a local database with Redux, we’ll write Middleware that intercepts CRUD Actions and applies them to our database.

In the previous post in this series we covered what Middleware is in Redux and we wrote Middleware to store all Actions as they are dispatched, then reloaded those actions on app startup. This approach can be useful where storage costs are cheap, but for a Xamarin app this every growing list of Actions is going to consume unnecessary device storage.

Saving new items

Using our Middleware pattern again with LiteDb, we’re going to save a new todo item to our database whenever we receive an AddTodoAction.

public class DatabaseMiddleware<TState>
{
    private LiteCollection<TodoItem> _todoCollection;

    public DatabaseMiddleware(String databaseName)
    {
        var db = new LiteDatabase(databaseName);
        _todoCollection = db.GetCollection<TodoItem>("Todos");
    }

    public Middleware<TState> CreateMiddleware()
    {
        return store => next => action =>
        {
            if (action is AddTodoAction)
            {
                AddAction((AddTodoAction)action);
            }
            return next(action);
        };
    }

    private void AddAction(AddTodoAction action)
    {
        _todoCollection.Insert(new TodoItem
        {
            Id = action.Id,
            Text = action.Text
        });
    }
}

And we wire that up in App.xaml.cs like this

var dbPath = DependencyService.Get<IFileHelper>().GetLocalFilePath("todo.db");
Store = new Store<ApplicationState>(
        Reducers.Reducers.ReduceApplication, 
        new ApplicationState(),
        new DatabaseMiddleware<ApplicationState>(dbPath).CreateMiddleware());

Restoring items

The next step is to restore the current state of the database on app startup. We’re going to do this by introducing a FetchTodosAction. Our DatabaseMiddleware will listen for this event, and populate it with all the current todos from the database, and our Reducer will reset our ApplicationState accordingly.

We’ll add the logic to DatabaseMiddleware to load all the todos from the database

public Middleware<TState> CreateMiddleware()
{
    return store => next => action =>
    {
        if (action is AddTodoAction)
        {
            AddAction((AddTodoAction)action);
        }
        if (action is FetchTodosAction)
        {
            FetchTodos((FetchTodosAction)action);
        }
        return next(action);
    };
}

private void FetchTodos(FetchTodosAction action)
{
    action.Todos = _todoCollection.FindAll();
}

We’ll create a FetchTodoAction

public class FetchTodosAction : IAction
{
    public IEnumerable<TodoItem> Todos { get; internal set; }
}

We’ll need to handle the FetchTodoAction in our Reducer by resetting the todos collection

private static ImmutableArray<TodoItem> FetchTodosReducer(ImmutableArray<TodoItem> previousState, FetchTodosAction action)
{
    return ImmutableArray.CreateRange(action.Todos);
}

And we’ll dispatch the FetchTodoAction on app startup

Store.Dispatch(new FetchTodosAction());

Updating and deleting items

The final piece of the puzzle is to intercept the update and remove actions and apply them appropriately to the database. This requires just extending the DatabaseMiddleware

public Middleware<TState> CreateMiddleware()
{
    return store => next => action =>
    {
        if (action is AddTodoAction)
        {
            AddAction((AddTodoAction)action);
        }
		if (action is RemoveTodoAction)
		{
			RemoveAction((RemoveTodoAction)action);
		}
		if (action is UpdateTodoAction)
		{
			UpdateAction((UpdateTodoAction)action);
		}
		if (action is FetchTodosAction)
        {
            FetchTodos((FetchTodosAction)action);
        }
        return next(action);
    };
}

private void UpdateAction(UpdateTodoAction action)
{
	_todoCollection.Update(new TodoItem
	{
		Id = action.Id,
		Text = action.Text
	});
}

private void RemoveAction(RemoveTodoAction action)
{
    _todoCollection.Delete(action.Id);
}

And we’re done. Our system now applies actions to a database and reloads them on application start.

Caution: Ordering of Middleware

One thing to consider with this solution is how the order of Middleware will be applied. If you have other Middleware that takes an Action, makes a server request then fires one or more Actions, do you want this to happen before or after the database middleware? It will depend on your scenario but if that server request is sending our item changes, we probably want to listen to the result of that operation and only persist those operations that were successful, so the database middleware would come last.

Conclusion

We’ve come to the end of this series on advanced Redux in Xamarin, I hope it was helpful. We’ve covered how to use Action Creators for async operations (e.g. server requests), we’ve built Middleware that persisted all our Actions and replayed them at app startup, and we’ve built Middleware that will read our Actions and apply them to a local database, reloading the database state at startup.

At PageUp we’ve been using these techniques in the development of our Xamarin App, using SQLite instead of LiteDb and pairing Redux with FreshMvvm, we feel it’s improved the application architecture and made the app easy to reason about.