App Architecture & Folder Structure

In this article, I’m going to go into details about the code of this slide puzzle: the app architecture, the folder structure, the application structure, and so on.

Dashtronaut Flutter Slide Puzzle App Architecture

The App Architecture

Dashtronaut’s codebase is divided into 3 main layers: Data, Business Logic, and Presentation.

1. The Data Layer

Contains the data sources, in this case, the local storage service using Hive and the service locator pattern to inject the service (more on that in a bit)

This layer also contains the models, which define the data format of the entities in the app: Puzzle, Tile, Position, Location, and Score. Most of those models also handle serialization into raw data (json) that is stored in/fetched from local storage. This is needed for the functionality of storing the user’s progress and score history in local storage.

2. The Domain Layer

This is where most of the business logic is. Aside from some logic inside the models, more complex application business logic is written in this layer inside ChangeNotifier providers.

For example, the PuzzleProvider has logic for generating the puzzle, updating tile locations, handling keyboard events, and so on. The StopWatchProvider handles the stop-watch logic. And the PhrasesProvider handles the logic for the phrases that Dash speaks in phrase-bubbles throughout the user flow.

3. The Presentation Layer

What the user sees and interacts with. It contains the UI Widgets for the various application modules like the background, the puzzle, the app drawer, Dash, and the phrases along with the styles and layout delegates. The ChangeNotifier providers also belong to this layer as they receive user input from the UI and call notifyListeners() to update that UI accordingly.


The Folder Structure

Let’s see how that architecture is reflected in the folder structure. Inside the lib folder, you will find the following folders:

  • helpers - helper classes for minor functionalities in the app
    • models - Data Layers - app entities (Puzzle, Tile, Position, Location, and Score)
      • presentation - Presentation Layer
        • background
          • utils - the background layers list & the StarsPainter
            • widgets
            • Common - Widgets shared across the app code
              • animations
                • utils
                  • animations_manager.dart - manages animations' durations, tweens, and curves across the app
                • widgets - shared animation widgets like fade-in and pulse transitions
                • dialogs - alert dialog wrapper widget for unified dialog style
                • dash - dash's Rive animation widget
                  • drawer
                    • home
                      • layout - layout delegates & code that handles responsiveness
                        • phrases - widgets for the phrase bubbles that Dash speaks
                          • puzzle - widgets for the puzzle itself, including the share dialog, the puzzle view and the surrounding ui elements (e.g. reset button, moves count, and stop-watch)
                            • styles - application colors and text styles
                              • tile - widget for tiles' Rive animation and inner content
                              • providers - Business Logic & Presentation Layers - ChangeNotifier provider that contain the application logic and update the ui
                                • puzzle_provider.dart - logic for different puzzle functionalities like generating a solvable puzzle, updating tile locations, handling keyboard events, and so on
                                • stop_watch_provider.dart - logic for the stop watch
                                • phrases_provider.dart - logic for the phrase bubbles that Dash speaks
                              • services - Data Layer - shared animation widgets like fade-in and pulse transitions
                                • storage - The local storage service
                                  • hive_storage_service.dart - Implementation of the StorageService interface using Hive
                                  • storage_service.dart - abstract class of the StorageService interface, also contains StorageKey constants
                                • storage_locator.dart - Registering services with the GetIt package
                              • app.dart
                              • main.dart - app entry point

                              Application Structure & Widget Tree


                              Dashtronaut Flutter Slide Puzzle Widget Tree

                              The Service Locator Pattern

                              (What's happening in the services folder?)

                              The service locator pattern is a design pattern in which an abstraction layer is introduced when obtaining a service, and there is a central registry, the “Service Locator” that is responsible for registering those services and in turn allows you to access them from anywhere in your app.

                              In practice, you create the interface of the service (the abstract class), and you implement this interface separately. Because the service locator returns the abstract interface, this separation allows for the ability to swap the implementation with a mock easily in testing, or swap it with an implementation that uses another package.

                              Let’s see this in action. For the local storage functionality in dashtronaut, you can find the abstract class lib/services/storage/storage_service.dart

                              abstract class StorageService {
                                Future<void> init();
                              
                                Future<void> remove(String key);
                              
                                dynamic get(String key);
                              
                                Future<void> clear();
                              
                                bool has(String key);
                              
                                Future<void> set(String? key, dynamic data);
                              }
                              

                              This interface is implemented in the lib/services/storage/hive_storage_service.dart file by populating the methods with Hive-specific functions.

                              But how is this service accessed throughout the app? Using the GetIt package as a service locator. This is the code for locating the storage service, found in the lib/services/service_locator.dart file:

                              final getIt = GetIt.instance;
                              
                              setupServiceLocator() {
                                getIt.registerSingleton<StorageService>(HiveStorageService());
                              }
                              

                              After calling the setupServiceLocator function before your runApp function, you can call this service anywhere in your app like so:

                              final StorageService storageService = getIt<StorageService>();

                              (This came in very handy for me when I migrated from the shared_preferences to the hive package in a previous project)



                              Share this article