Skip to content

upsidr/flutter_architecture_blueprint

Repository files navigation

flutter_architecture_blueprint

flutter_architecture_blueprint is a flutter project that demonstrates a three layers architecture in the form of ToDo app.

Detailed explanations and background information are provided in the related article on our technical blog.

πŸ‘‰ https://tech.up-sider.com/entry/2024/04/15/110000

Demo

https://upsidr.github.io/flutter_architecture_blueprint/

Overview

Packages

Purpose Package
State Management hooks_riverpod, rxdart
Modeling freezed
Routing auto_route

Modeling View

typedef TaskListContract
    = BaseContract<TaskListUiState, TaskListAction, TaskListEffect>;

@freezed
class TaskListUiState with _$TaskListUiState {
  const factory TaskListUiState({
    @Default([]) List<EditableUserTask> taskList,
  }) = _TaskListUiState;
}

@freezed
sealed class TaskListAction with _$TaskListAction {
  const factory TaskListAction.onAppear() = OnAppear;
  const factory TaskListAction.newTaskButtonTapped() = NewTaskButtonTapped;
  const factory TaskListAction.taskTapped(EditableUserTask task) = TaskTapped;
  const factory TaskListAction.toggleIsCompleted(EditableUserTask task) =
      ToggleIsCompleted;
  const factory TaskListAction.onTaskSwiped(EditableUserTask task) =
      OnTaskSwiped;
}

@freezed
sealed class TaskListEffect with _$TaskListEffect {
  const factory TaskListEffect.none() = None;
  const factory TaskListEffect.goDetail({
    required EditableUserTask? task,
  }) = GoDetail;
  const factory TaskListEffect.showAlert({
    required AlertState state,
  }) = ShowAlert;
}

Testing

Steps of Unit Testing for presentation layer:

  1. Call notifier.send(Action) to trigger View events
  2. Assert UiState / Effect
test('Tap NewTaskButton, navigate detail', () async {
  final (notifier, uiState, effect) = buildAccessors();

  todoRepository.handler.fetchTaskList = () async =>
      fakeTodoState.update((value) => value.copyWith(taskList: []));

  await notifier.send(const TaskListAction.onAppear());
  expect(uiState().taskList.isEmpty, true);

  await notifier.send(const TaskListAction.newTaskButtonTapped());
  expect(
    effect(),
    const TaskListEffect.goDetail(task: null),
  );
});

Directory Structure

β”œβ”€β”€ app_router.dart
β”œβ”€β”€ app_router.gr.dart
β”œβ”€β”€ core
β”‚   β”œβ”€β”€ data
β”‚   β”‚   β”œβ”€β”€ network
β”‚   β”‚   β”‚   └── fake_todo_api_client.dart
β”‚   β”‚   └── repository
β”‚   β”‚       └── todo
β”‚   β”‚           β”œβ”€β”€ fake_todo_repository.dart
β”‚   β”‚           β”œβ”€β”€ fake_todo_repository.freezed.dart
β”‚   β”‚           β”œβ”€β”€ todo_repository.dart
β”‚   β”‚           └── todo_repository.freezed.dart
β”‚   β”œβ”€β”€ domain
β”‚   β”‚   β”œβ”€β”€ model
β”‚   β”‚   β”‚   β”œβ”€β”€ editable_user_task.dart
β”‚   β”‚   β”‚   └── editable_user_task.freezed.dart
β”‚   β”‚   └── todo
β”‚   β”‚       β”œβ”€β”€ edit_task_usecase.dart
β”‚   β”‚       β”œβ”€β”€ edit_task_usecase.freezed.dart
β”‚   β”‚       β”œβ”€β”€ task_list_usecase.dart
β”‚   β”‚       └── task_list_usecase.freezed.dart
β”‚   β”œβ”€β”€ model
β”‚   β”‚   β”œβ”€β”€ user_task.dart
β”‚   β”‚   β”œβ”€β”€ user_task.freezed.dart
β”‚   β”‚   └── user_task.g.dart
β”‚   └── util
β”‚       β”œβ”€β”€ alert_state.dart
β”‚       β”œβ”€β”€ alert_state.freezed.dart
β”‚       β”œβ”€β”€ base_contract.dart
β”‚       β”œβ”€β”€ datetime_formatted.dart
β”‚       └── stream_extensions.dart
β”œβ”€β”€ feature
β”‚   └── todo
β”‚       β”œβ”€β”€ edit_task
β”‚       β”‚   β”œβ”€β”€ edit_task_contract.dart
β”‚       β”‚   β”œβ”€β”€ edit_task_contract.freezed.dart
β”‚       β”‚   β”œβ”€β”€ edit_task_notifier.dart
β”‚       β”‚   β”œβ”€β”€ edit_task_notifier.g.dart
β”‚       β”‚   β”œβ”€β”€ edit_task_page.dart
β”‚       β”‚   └── ui_components
β”‚       β”‚       β”œβ”€β”€ task_text_field.dart
β”‚       β”‚       └── toggle_complete_button.dart
β”‚       └── task_list
β”‚           β”œβ”€β”€ task_list_contract.dart
β”‚           β”œβ”€β”€ task_list_contract.freezed.dart
β”‚           β”œβ”€β”€ task_list_notifier.dart
β”‚           β”œβ”€β”€ task_list_notifier.g.dart
β”‚           β”œβ”€β”€ task_list_page.dart
β”‚           └── ui_components
β”‚               β”œβ”€β”€ task_list_item.dart
β”‚               └── task_list_placeholder.dart
β”œβ”€β”€ main.dart
└── main_device_preview.dart

Development

Getting Started