Advanced Redux in Xamarin Part 3: Database Middleware
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.