Flutter Use Case: License Flow with Strategy Pattern

Veli Bacık
Flutter Community
Published in
4 min readJan 23, 2024

--

Welcome to my first article in the series #usecase. Here are some sample solutions for real-life scenarios. Let’s start with a solution for using strategy patterns.

Use:

We are working with our customer to implement a licensing approval flow. Flow consists of three pages, a license check view, an approve dialog, and a bottom sheet. Steps to follow:

  • In a flow, the first check is the approve checkbox. The flow continues when the user approves the checkbox.
  • The second step is to approve the dialog. Using the dialog, processes will be approved, and then the final step will be redirected.
  • In the last step, only a checkbox icon indicates whether the steps have been completed successfully.

Case-Coding:

Actually, many solutions have been prepared for this request, but this time is for strategy design principles. This solution is based on the following idea:

  • Adding new flows is easy
  • Manage flow classes in a basic way
  • Business request separately
  • Any flow can change rapidly

Okay, let’s take the first step towards this solution. In this solution, a basic abstract class will be created as a mixin:

mixin Operation {
Future<StrategyResult?> execute(BuildContext context);
}

Flows will use and return ‘StrategyResult’

enum StrategyResult {
success,
failure,
}

First flow: Let’s write the page for the strategy license.

class StrategyLicense with Operation {
const StrategyLicense();

@override
Future<StrategyResult?> execute(BuildContext context) async {
final response = await Navigator.of(context).push<StrategyResult?>(
MaterialPageRoute<StrategyResult>(
builder: (_) => const StrategyLicenseView(),
),
);

return response;
}
}

The license page contains a checkbox for the approve dialog box. This is a very simple page because it only shows the real-life replica.

If the checkbox is updated to true, the next step will be passed; otherwise, it will return false and our flow will be cancelled.

Second flow: In this page, the user is asked to confirm their decision when they agree to the license.

@immutable
class StrategyApprove with Operation {
const StrategyApprove();
@override
Future<StrategyResult?> execute(BuildContext context) async {
final response = await showDialog<StrategyResult?>(
context: context,
builder: (context) {
return const StrategyApproveDialog();
},
);
return response;
}
}

Again, the Strategy Approve dialog consists of just text and a button. When you press the button with the check icon, it will return successful.

Third Flow: The flow will display a success icon or an error message. It contains the result parameter for the flow control.

@immutable
class StrategyBottom with Operation {
const StrategyBottom(this.result);

final StrategyResult result;
@override
Future<StrategyResult?> execute(BuildContext context) async {
final response = await showModalBottomSheet<StrategyResult?>(
context: context,
builder: (context) {
return StrategyBottomSheet(
result: result,
);
},
);
return response;
}
}

The last page is the bottom sheet. This is a simple page that shows how the flow ends.

Before one last point, the flows are almost ready for use. It is necessary to manage how to continue and change to the strategy when the steps are completed.

class StrategyContext {
StrategyContext(this._operation, {required this.context});
Operation _operation;
final BuildContext context;

void changeStrategy(Operation operation) {
_operation = operation;
}

Future<StrategyResult?> run() {
return _operation.execute(context);
}
}

It is now possible to use the core layer for every step, manager, and view item. After pressing the floating button on the home page, the flow will begin. A flow context will be created and the flow will automatically continue.

This code will start showing and managing flow after you click this floating button.

mixin StrategyHomeMixin on State<StrategyHomeView> {
Future<void> onStartFlow() async {
final strategyContext = StrategyContext(
const StrategyLicense(),
context: context,
);
var response = await strategyContext.run();

if (response == StrategyResult.success) {
if (!mounted) return;
strategyContext.changeStrategy(const StrategyApprove());
response = await strategyContext.run();
}

response ??= StrategyResult.failure;
if (!mounted) return;
strategyContext.changeStrategy(StrategyBottom(response));
await strategyContext.run();
}
}

Then it’s making every flow for any time, but be careful, some flow depends on before, so order is important.

  • Setting up a strategy for managing these flows
  • When you set the constructor (this simple license), start the first flow
  • Decide what to do based on the first flow result.
  • The first response is success when strategy approval starts.
  • If the home page closes or any other issues happen, strategy will fail and show error.
https://github.com/vb10

That’s all for this article. Here’s a strategy pattern for view usage and how it’s implemented easily. I hope you find it helpful!

This series will continue with the next article.

Videos

(Source code path)

--

--

Veli Bacık
Flutter Community

We always change the world, so just want it. [OLD]Google Developer Expert Flutter & Dart, Gamer, Work More!