All Articles

Using Redux with Xamarin Forms — Part 2

In this series I want to implement a simple shopping app using the Redux pattern in Xamarin Forms.

The hardware

Folder structure

Folder structure

Folder structure

First we need to create the nessesary folders and files.

I like to seperate app features in own feature folders. So my Order feature folder structure look like this:

Domain

The domain folder contains all domain specific parts. In this examples the Models file which contains all nessesary Models as Records.

Presentation

Contains the XAML-Page and the ViewModel

State

The state contains all Redux related parts:

  • The whole state of the Order
  • Actions which can be dispatched
  • Reducers which modify the store based on Actions
  • Selectors to select specific parts of the store for the UI

In the next parts i implement one use-case after another to fill this whole files with content.

Implement the Redux Parts

The user should see all available products to buy.

Models

Models.cs

public record ProductId(int Value);
public record ShoppingCartItemId(int Value);
public record Product(ProductId Id,decimal Price,string Description);
public record ShoppingCartItem(ShoppingCartItemId Id,Product Product,int Amount);

Actions

Actions.cs

public record InitializeOrderAction();
public record InitializeOrderActionSuccess(List<ShoppingCartItem> ShoppingCartItems);
public record InitializeOrderActionFailure(Exception Exception);
  • InitializeOrderAction — Initiate the loading of the products
  • InitializeOrderActionSuccess — Is dispatched if the loading was successfull and returns the list of ShoppingCartItems
  • InitializeOrderActionFailure — Is dispatched when the loading throws an exception

Entities

When dealing with collections, you often repeat the same process to add, update and remove entity from your collection state. Entities in ReduxSimple simplify the managing of such cases in the reducer.

public class ShoppingCartItemEntityState : EntityState<ShoppingCartItemId,ShoppingCartItem> { }

public static class Entities
{
    public static EntityAdapter<ShoppingCartItemId, ShoppingCartItem> ShoppingCartItemAdapter { get; set; }
        = EntityAdapter<ShoppingCartItemId, ShoppingCartItem>.Create(item => item.Id);
}

There is a little boilerplate code involved but you will see the benefits later.

State

The state represents the UI-State of the order

public record OrderState
{
  public decimal Sum { get; set; }
  public ShoppingCartItemEntityState ShoppingCartItems { get; set;}
  public bool IsLoading { get; set; }
  public static OrderState InitialState => new OrderState()
  {
     ShoppingCartItems = new ShoppingCartItemEntityState(),
     Sum = 0m,
     IsLoading = false
  };
}

We define ShoppingCartItems which uses the ShoppingCartItemEntityState-helper for representing the ShoppingCartItems list.

The Sum property represent the sum in € of all products.

The IsLoading property indicates the the view is busy.

Adding to RootState

After OrderState is created we can add it to our RootState:

public record RootState
{
  public OrderState Order { get; set; }
  public static RootState InitialState => new RootState()
  {
     Order = OrderState.InitialState
  };
}

Adding to RootReducer

Here all reducers from all subreducers are combined. We have only one reducer.

public static class Reducers
{
   public static IEnumerable<On<RootState>> CreateReducers()
   {
      return CombineReducers(Order.State.Reducers.GetReducers());
   }
}

Selectors

Based on what you need, you can observe the entire state or just a part of it.

Selector allows you to select parts of the State.

public static class Selectors
{
    public static ISelectorWithoutProps<RootState, OrderState> SelectOrderState = CreateSelector(
        (RootState state) => state.Order
    );

    public static ISelectorWithoutProps<RootState, decimal> SelectSum = CreateSelector(
        SelectOrderState,
        (state) => state.Sum
    );

    public static ISelectorWithoutProps<RootState, bool> SelectIsLoading = CreateSelector(
        SelectOrderState,
        (state) => state.IsLoading
    );

    // Selectors for ShoppingCartItem-List
    private static readonly ISelectorWithoutProps<RootState, ShoppingCartItemEntityState> SelectShoppingCartItemsEntityState = CreateSelector(
        SelectOrderState,
        state => state.ShoppingCartItems
    );
    private static readonly EntitySelectors<RootState, ShoppingCartItemId, ShoppingCartItem> ShoppingCartItemsSelector = ShoppingCartItemAdapter.GetSelectors(SelectShoppingCartItemsEntityState);

    public static ISelectorWithoutProps<RootState, List<ShoppingCartItem>> SelectShoppingCartItems = ShoppingCartItemsSelector.SelectEntities;
}

First we create a selector for the OrderState. This selector selects only the OrderState from the RootState and can be reused to select deeper parts of OrderState.

The second selector selects to the Sum changes.

The third selector selects to the IsLoading changes.

The fourth selector selects with a little magic from ReduxSimple to the ShoppingCartItem-List changes from OrderState.

Reducers

public static IEnumerable<On<RootState>> GetReducers()
{
    return CreateSubReducers(SelectOrderState)
                .On<InitializeOrderAction>(state => {
                    var mockShoppingCartItems = new List<ShoppingCartItem>()
                    {
                        new ShoppingCartItem(new ShoppingCartItemId(1),
                                                new Product(new ProductId(1),109.95m,"Fjallraven - Foldsack No. 1 Backpack, Fits 15 Laptops"),
                                                0),
                        new ShoppingCartItem(new ShoppingCartItemId(2),
                                                new Product(new ProductId(2),22.3m,"Mens Casual Premium Slim Fit T-Shirts"),
                                                0)
                    };
                    return state with
                    {
                        ShoppingCartItems = ShoppingCartItemAdapter.AddAll(mockShoppingCartItems, state.ShoppingCartItems),
                    };
                })
        .ToList();
}

This reducer reacts on InitializeOrderAction and fills the ShoppingCartItem list with mock data.

You can see how i’am using the previosily created ShoppingCartItemAdapter to populate the property.

I’am also using C#9 with-Expressions to create a new state without mutating it 🤩. It creates a new state where only ShoppingCartItems has a new value, the rest of the properties remain unchanged.

In the next part, when we use effects, we will get real data from an api and create a new InitializeOrderActionSuccess from the effect.

ViewModels and View

The last part is the linking of all parts in the ViewModel and the view.

OrderViewModel

using System;
using System.Collections.ObjectModel;
using static ShoppingApp.App;
using static ShoppingApp.Order.State.Selectors;

namespace ShoppingApp.Order.Presentation
{
    public class OrderViewModel : BaseViewModel
    {
        public OrderViewModel()
        {
            Title = "Order";
            var shoppingCartDisposable = Store.Select(SelectShoppingCartItems)
                .Subscribe(x => CartItems = new ObservableCollection<ShoppingCartItem>(x));
            Disposables.Add(shoppingCartDisposable);

            Store.Dispatch(new InitializeOrderAction());
        }

        public ObservableCollection<ShoppingCartItem> CartItems { get; set; }
    }
}

The ViewModel selects ShoppingCartItems from the store and fills the CartItems ObservableCollection when something changes.

To trigger the loading of the list the InitializeOrderAction must be dispatched to the store.

OrderPage

Redux Simple Nuget

In the first iteration of OrderPage we can see the loaded mock data.

Summary

We have implemented the store parts:

  • State
  • Actions
  • Selectors
  • Reducers

We also used this parts in the ViewModel and View.

You can find the current state of the app’s code here:

What’s next

  • Using effects to load data from an external api
  • Implementing the other parts of the order
  • Eventually using some other reactive concepts

Stay tuned!!

Sources

Published Oct 20, 2021

I'm software engineer from germany.