• Home
  • Blog
  • Utilising the Strategy Design Pattern in Android for a flexible checkout flow

Utilising the Strategy Design Pattern in Android for a flexible checkout flow

Hamidreza Sahraei

By Hamidreza Sahraei

In Android development, it's common to encounter scenarios where different flows share similar structures but require slight variations in specific business logic. The Strategy Pattern is a nice and useful design pattern in such cases, allowing different algorithms or behaviours to be encapsulated and swapped out based on the flow.

In this article, I’ll share how we applied the Strategy Pattern while refactoring the market order flows in the Bitpanda application. We needed to support two market order flows—buy and sell—with shared UI components and a single ViewModel while maintaining distinct logic for each flow.


The Problem

Our market order flow was split into two types:

  • Buy Market Order

  • Sell Market Order

Both flows used the same amount of screen UI, but the underlying business logic differed. For example, the calculation for estimating the amount the user would receive varied. In a buy order, the calculation divides the amount by the price, whereas in a sell order, the calculation multiplies the amount by the price (just for a sample use case). 

Additionally, the price is updated dynamically. We needed a clean solution that allowed us to handle both flows in a unified way while keeping the logic flexible and maintainable.

In this article, we will demonstrate a simplified use case with just an example. This does not reflect the exact implementation in our codebase, but the core idea of utilising the Strategy Pattern to handle varying logic is crucial, especially in a logic with many functions.

The Solution: Strategy Pattern

The Strategy Pattern is ideal for this problem. It allows us to define separate strategies for buy and sell orders and swap them out depending on the flow. This makes the ViewModel agnostic to specific calculations while keeping the code clean and modular.

Key Benefits:

  • No repeated 'if/else' or 'when' statements: With this approach, we avoid repeating 'if/else' or 'when' conditions in every method that has different business logic for buy and sell orders.

  • Improved testability: The buy and sell strategies can be tested independently in a clean and straightforward way, as each strategy is isolated and doesn’t depend on complex conditional logic.

  • Flexible dependencies: Each strategy can have different dependencies (e.g., services, repositories) based on its logic. 

To keep things, simple we've provided fewer examples, but in a production-ready scenario, the strategies might handle more complex interactions while the core idea remains the same.

Code Implementation

Here’s how we implemented the Strategy Pattern for this use case.

1. Defining the strategy interface

We start by defining a common interface that both strategies will implement:

2. Implementing concrete strategies

Next, we create concrete classes for each strategy: 'BuyMarketOrderStrategy' and 'SellMarketOrderStrategy':

Note: In more complex scenarios, each strategy could inject different dependencies (e.g., a pricing service, external API, or repository) to perform their specific logic.

3. Integrating the strategy in the ViewModel with State Flow

In the ViewModel, we use a 'StateFlow' to manage the UI state, And the price is updated dynamically. Here’s how the state and strategy integration look:

The 'MarketOrderViewModel' might include additional methods that handle various business logic specific to buy and sell orders. One prominent example is the logic for estimating the received value based on the order type, but other methods might include validation, price format adjustments, and more.

4. Providing the strategy based on the order type

You can decide which strategy to use based on whether it’s a buy or sell order:

5. Composable and UI Integration

In your composable, you observe the 'StateFlow' and update the UI accordingly:

Testing the Strategies

The Strategy Pattern makes it easy to independently test the buy and sell strategies. Here’s an example of how you can write unit tests for each strategy:

Key Points:

  • Each strategy is isolated, making it easy to test specific business logic.

  • The tests focus solely on the calculation logic, free from conditional branches like 'if/else' or 'when' statements, leading to more readable and maintainable test code.

Conclusion

The Strategy Pattern allowed us to refactor the market order flow, enabling a single composable and ViewModel to handle both buy and sell orders. The strategy pattern provides flexibility, allowing us to swap logic as needed while maintaining clean and modular code.

This approach eliminates the need for repeating 'if/else' or 'when' statements throughout different methods, making the code more concise and maintainable. Additionally, the strategies can be easily tested in isolation, ensuring robust and accurate business logic.

Although this example focuses on simple cases, in a production-ready environment, each strategy could be more complex with additional dependencies. Nevertheless, the core idea remains the same—encapsulating varying behaviour into strategies and swapping them as needed.

By managing these variations in behaviour using strategies, we achieve a scalable solution that keeps the code maintainable, testable, and adaptable to future changes.

Hamidreza Sahraei

Hamidreza Sahraei