Skip to content

Architecture

This section offers a detailed overview of the architectural design behind the Sizzle Starter, providing insight into the patterns employed and their implementation.

Overview

The Sizzle Starter architecture revolves around several key patterns:

  • BLoC (Business Logic Component): Primarily used for state management.
  • State Pattern: A design pattern enabling dynamic behavior based on internal state. More information can be found here.
  • Repository Pattern: Ensures a clean and efficient way to manage data operations.
  • Dependency Injection: Allows the architecture to maintain modularity and flexibility. Further details are here.

When visualized, the flow of dependencies appears as:

Widget -> BLoC -> Repository -> Data Source -> External sources (Database, API, etc.)

This structure bears resemblance to the MVVM (Model-View-ViewModel) architectural pattern.

BLoC - Business Logic Component

At the heart of this architecture lies the BLoC pattern, which aims to distinctively separate business logic from UI (User Interface) elements, ensuring:

  • Widgets are purely dedicated to UI rendering and interactions.
  • BLoC exclusively handles business logic.

One fundamental rule is that BLoC remains unaware of any Widgets. Failing to abide by this rule can lead to states like ShowDialogState or ShowSnackBarState within BLoC. Instead, Widgets should decide their display elements, like dialogs or snackbars, based on the state updates received from BLoC.

Worth noting is that BLoCs are written in pure Dart and don’t rely on external frameworks like Flutter. Their design is deeply influenced by the State Pattern.

BLoC Structure

A BLoC is composed of three main components:

  • State: Represents the current state of the BLoC.
  • Event: Represents an event that triggers a state change.
  • Bloc: Orchestrates the state transitions based on the received events.

There is a well-known library called bloc that provides a generic implementation of the BLoC pattern.

Some BLoC rules

  • BLoC shouldn’t have anything related to UI.
  • BLoC shouldn’t know anything about end consumers.
  • BLoC shouldn’t have any public methods and fields.
  • BLoC should have only one input and one output.
  • BLoC should be testable in isolation.
    • BLoC should not have any dependencies on other BLoCs.
  • BLoC should do only one thing (For example, it shouldn’t be responsible for the whole screen logic).
  • BLoC get data only from repositories.

Understanding the State Pattern

The State Pattern is a design methodology that permits an object to exhibit different behaviors based on its internal state transitions. As a result, an object seems to modify its class during runtime.

Conceptually, this aligns with the Finite State Machine (FSM). An FSM is designed around a set of defined states with specified transitions between them. Transitions are event-driven; when an event takes place, the FSM shifts its state in accordance with the predefined transition.

Diving into the Data Layer

The Data Layer, as the name suggests, is dedicated to data management and retrieval. It comprises:

  • Data Sources
  • Repositories

Data Source

Acting as the foundational layer of the Data Layer, Data Sources are tasked with fetching data directly from specific origins, such as databases or APIs.

The chief objective of a Data Source is to abstract away the actual source details, making it possible to switch data sources with minimal impact on the Repository.

Data Sources yield DTOs (Data Transfer Objects), which are lightweight structures encapsulating data without associated business logic.

A sample Data Source might be represented as:

abstract class UserDataSource {
Future<UserDto> getUser(String id);
}

Given this abstraction, switching or updating data sources simply requires implementing this interface.

Repository

Sitting a level above, Repositories handle data extraction from the underlying Data Sources. They are the exclusive component knowledgeable about these Data Sources.

A Repository might engage with multiple Data Sources, consolidating the data as necessary. A typical use case might involve merging data from two distinct sources.

Repositories may adopt a reactive paradigm, utilizing streams for effective data synchronization between BLoCs.

Unlike Data Sources, which return DTOs, Repositories typically yield Entities. Entities not only contain data but also encapsulate some logic, offering capabilities to act on this data.

Here’s a typical Repository interface:

abstract class UserRepository {
/// Provides a continuous stream of user entities.
Stream<UserEntity> getUser(String id);
}

This architecture overview serves to provide clarity on the Sizzle Starter’s design, breaking down its various components and their interconnectedness.