Flutter Clean Architecture [2]: An Overview & Project Structure
Tags: frontend, flutter, mobile, architecture
Published: 2023-06-13 15:14:51

> **You can find the completed project repository [here ](https://github.com/marwaMejri/flutter_architecture)**

In the [**initial section**](https://dev.to/marwamejri/flutter-clean-architecture-1-an-overview-project-structure-4bhf), we learn the fundamentals of clean architecture and set up the necessary folders to organize each layer. Now let's dive into these empty folders, starting with the Domain layer.
So let's start the party, enough talking.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iqwif6g2do4wbi7pn7ul.gif)

### Domain Layer 

#### Entity
First, we add our entity that represents the weather data.
To determine which fields this class must have, we must examine the Weather API response. The following JSON response is returned by this API:

```JSON
{
  "coord": {
    "lon": 10.637,
    "lat": 35.8254
  },
  "weather": [
    {
      "id": 801,
      "main": "Clouds",
      "description": "few clouds",
      "icon": "02d"
    }
  ],
  "base": "stations",
  "main": {
    "temp": 304.03,
    "feels_like": 307.27,
    "temp_min": 303.02,
    "temp_max": 304.03,
    "pressure": 1014,
    "humidity": 58
  },
  "visibility": 10000,
  "wind": {
    "speed": 5.66,
    "deg": 100
  },
  "clouds": {
    "all": 20
  },
  "dt": 1663066385,
  "sys": {
    "type": 1,
    "id": 1193,
    "country": "TN",
    "sunrise": 1663045105,
    "sunset": 1663090127
  },
  "timezone": 3600,
  "id": 2464915,
  "name": "Sousse",
  "cod": 200
}
```
Except for the coord, base, and cod fields, we are interested in the majority of the fields in this answer. As a result, our entity will resemble this:

```Dart
class WeatherInfoEntity extends Equatable {
  const WeatherInfoEntity({
    this.weather,
    this.main,
    this.visibility,
    this.wind,
    this.clouds,
    this.dt,
    this.sys,
    this.timezone,
    this.id,
    this.name,
    this.weatherTheme,
  });
  final List<WeatherDescriptionEntity?>? weather;
  final MainWeatherInfoEntity? main;
  final String? visibility;
  final WindInfoEntity? wind;
  final CloudsEntity? clouds;
  final String? dt;
  final SunsetSunriseEntity? sys;
  final int? timezone;
  final int? id;
  final String? name;
  final WeatherThemeEntity? weatherTheme;

  @override
  List<Object?> get props => <Object?>[
        weather,
        main,
        visibility,
        wind,
        clouds,
        dt,
        sys,
        timezone,
        id,
        name,
        weatherTheme,
      ];
}
```
The WeatherInfoEntity class extends the Equatable class from the **[equatable ](https://pub.dev/packages/equatable)** plugin to allow for simple value comparisons without all the boilerplate, as Dart only supports referential equality by default. Also, if you notice that I added a new field of type WeatherThemeEntity, this field will help me specify the proper theme for a certain weather. It is not required to provide such a field, but it allows for a greater connection with the application.

```Dart
class WeatherThemeEntity {

  WeatherThemeEntity({
    this.firstColor,
    this.secondColor,
  });
  Color? firstColor;
  Color? secondColor;
}

```
So, the WeatherThemeEntity class will have two color type fields, in which I will add a gradient background for each type of weather.

#### Repository & Error handling

As indicated earlier, the repository belongs to both the domain and data layers. In fact, it will be defined in the domain first and then implemented in data. This enables 100% independence of the domain layer and also helps with developing tests for the use cases without having an actual repository implementation. So, how will the repository contract look?

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w0sbzt7wp9rfy5re9job.gif)

In our contract, we define three methods for our weather functionality: one to retrieve weather data by coordinates, another to obtain weather for a specific city, and a third to retrieve all saved weather information locally. Logically, the return types for the first two methods should be **Future<WeatherInfoEntity>**, and for the final method, **Future<List<WeatherInfoEntity>>**. However, error handling is also an important consideration. We have two options: allowing exceptions to propagate and handling them elsewhere, or catching exceptions early and returning an error object from the methods.

In our architecture, I have chosen the second approach. This means that any method in the repository or use case will return both the entity and failure objects, providing a clear way to handle both successful outcomes and potential errors.

So , to implement it, I used the **[freezed ](https://pub.dev/packages/freezed)**  plugin to create a sealed class named **ApiResultModel ** that will return either failure with an error object or success with the specific data.

> **sealed classes** are used to represent restricted class hierarchies, where a value can have one of a restricted set's types but not another. They are also an extension of enum classes, except that an enum constant can only have one instance, but a sealed class can have several instances.

Furthermore, the freezed plugin will generate extra functionality like copyWith and toString methods, but more importantly, it will give us access to some pattern-matching syntax.

To use this plugin, you must first install the following dependencies:

```Dart
  cupertino_icons: ^1.0.5
  equatable: ^2.0.5
  freezed: ^2.3.3
  get_it: ^7.6.0
  injectable: ^2.1.2
  json_annotation: ^4.7.0
```
Following that, I created a 'commondomain' subfolder under the 'core' folder. This 'commondomain' subfolder will further include an 'entities' subfolder. Additionally, I have introduced a 'based_api_result' subfolder intended for storing entities that will be shared across different sections of our project:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xroh9eh055ymou5a0q5d.png)

#### Creating the sealed class 

The **ApiResultModel** sealed class will have two states: **success** and **failure**. The success state will return the data type specified when calling our sealed class. For example, if we want the method to return a WeatherInfoEntity on success, the return type should be **ApiResultModel<WeatherInfoEntity>**. When a problem occurs, the return type is an **ErrorResultModel** object, as defined in the failure state. 

To create the two states, we employ factory constructors and include the necessary annotations and keywords. Make sure to add the '@freezed' annotation and the 'part' keyword at the top of the file so the plugin can build the methods specified previously:

```Dart
part 'api_result_model.freezed.dart';

@freezed
class ApiResultModel<T> with _$ApiResultModel<T> {
  const factory ApiResultModel.success({required T data}) = Success<T>;

  const factory ApiResultModel.failure(
      {required ErrorResultModel errorResultEntity}) = Failure<T>;
}
```
The error object includes two attributes: one for the API error status code and one for the message:

```Dart
class ErrorResultModel extends Equatable {
  const ErrorResultModel({
    this.statusCode,
    this.message,
  });

  final int? statusCode;
  final String? message;

  @override
  List<Object?> get props => <Object?>[
        statusCode,
        message,
      ];
}
```
#### Creating the repository contract with the use of the sealed class

So, in our abstract repository class, we'll have three methods: getWeatherDataByCoordinates, getWeatherDataByCity, and getAllLocalWeathers. These methods return Future<ApiResultModel<T>>, where T is the data type:

```Dart
abstract class WeatherRepository {
  Future<ApiResultModel<WeatherInfoEntity?>> getWeatherDataByCoordinates(
      {WeatherByCoordinatesRequestModel? weatherByCoordinatesRequestModel});

  Future<ApiResultModel<WeatherInfoEntity?>> getWeatherDataByCity(
      {String? cityName});

  Future<ApiResultModel<List<WeatherInfoEntity?>?>> getAllLocalWeathers();
}

```
#### Use cases & callable classes

Any use case must have the **WeatherRepository ** object, which will be provided in the constructor, as we know the use case corresponds to a repository method. In light of this, we need to add three use cases to our application: one for retrieving weather data based on coordinates, one for retrieving weather data based on city, and one for obtaining all previously stored weathers from a local database.

To begin, I will add the required repository for the **GetWeatherDataByCoordinates** use case:

```Dart
@injectable
class GetWeatherDataByCoordinates {
  GetWeatherDataByCoordinates(this.weatherRepository);

  final WeatherRepository weatherRepository;
  
}
```
Next, all that remains is to add the function that will retrieve the data from the repository:

```Dart
@injectable
class GetWeatherDataByCoordinates {
  GetWeatherDataByCoordinates(this.weatherRepository);

  final WeatherRepository weatherRepository;

  Future<ApiResultModel<WeatherInfoEntity?>> getWeatherData(
      WeatherByCoordinatesRequestModel? weatherByCoordinatesRequestModel) {
    return weatherRepository.getWeatherDataByCoordinates(
        weatherByCoordinatesRequestModel: weatherByCoordinatesRequestModel);
  }
}
```
So far, we have made good progress on our weather app. We have created an Entity, a Repository contract, and the first use case. To complete the work at the domain layer, we only need to create the remaining two use cases.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bt4r587in9n6ukqr3m2a.gif)

In regards to use cases, it is important to remember that each should have a return method that interacts with the repository. It makes no difference whether the logic inside the useCase retrieves a WeatherInfoEntity or something else, the goal is the same. And to improve the solution further, we could create a base class with a method that returns a generic type that can be extended by each specific use case.

#### creating the base use case class

To create the base useCase class, I implemented an explicit interface (in Dart, this is an abstract class) located in the **core/commondomain/usecases** directory since this class can be shared across multiple features of our app:

```Dart
abstract class BaseParamsUseCase<Type, Request> {
  Future<ApiResultModel<Type>> call(Request? params);
}

class NoParams extends Equatable {
  @override
  List<Object> get props => <Object>[];
}

```
As you can see, we added two type parameters to the **BaseParamsUseCase**: one represents the data type in case of success, and the Request type is for the parameters that will be passed through the use case, such as the latitude and longitude for the previous **'GetWeatherDataByCoordinates'**. So, in order to implement the base class, each use case must specify the parameter Request that will be passed to the "call" method. However, if a use case does not require any parameters, we can use the NoParams class as the Request type, as in the case of **'GetAllLocalWeathers'**.
The name of the 'call' method was not picked randomly. In fact, the 'call' method in the Dart programming language allows you to define a class as callable, meaning that you can use an instance of the class as if it were a function. This method is well-suited for use cases where the class names are verbs, such as 'GetWeatherDataByCoordinates'.

#### Extending the Base Class

Adding a use case is now very simple: each use case should implement the previous base class and override the calling method. For example, for 'GetWeatherDataByCoordinates', we implement the 'BaseParamsUseCase' with the addition of two parameters: one representing the return type of this use case and the other representing the parameters to be passed into it, which in our case is the 'WeatherByCoordinatesRequestModel', which holds the required latitude and longitude values:
 

```Dart
class GetWeatherDataByCoordinates
    implements
        BaseParamsUseCase<WeatherInfoEntity?,
            WeatherByCoordinatesRequestModel> {
  GetWeatherDataByCoordinates(this.weatherRepository);

  final WeatherRepository weatherRepository;

  @override
  Future<ApiResultModel<WeatherInfoEntity?>> call(
      WeatherByCoordinatesRequestModel? weatherByCoordinatesRequestModel) {
    return weatherRepository.getWeatherDataByCoordinates(
        weatherByCoordinatesRequestModel: weatherByCoordinatesRequestModel);
  }
}
```

```Dart
class WeatherByCoordinatesRequestModel {
  WeatherByCoordinatesRequestModel({
    this.lat,
    this.lon,
  });

  final double? lat;
  final double? lon;
}
```
The other two use cases will follow the same logic as the one previously mentioned:

```Dart
@injectable
class GetWeatherDataByCity
    implements BaseParamsUseCase<WeatherInfoEntity?, String> {
  GetWeatherDataByCity(this.weatherRepository);

  final WeatherRepository weatherRepository;

  @override
  Future<ApiResultModel<WeatherInfoEntity?>> call(String? cityName) {
    return weatherRepository.getWeatherDataByCity(cityName: cityName);
  }
}

```

```Dart
@injectable
class GetAllLocalWeathers
    implements BaseParamsUseCase<List<WeatherInfoEntity?>?, NoParams> {
  GetAllLocalWeathers(this.weatherRepository);

  final WeatherRepository weatherRepository;

  @override
  Future<ApiResultModel<List<WeatherInfoEntity?>?>> call(NoParams? params) {
    return weatherRepository.getAllLocalWeathers();

  }
}
```
### Next up

In the next part, we will start working on the data layer containing the Repository implementation and Data Sources: [**Next**](https://dev.to/marwamejri/flutter-clean-architecture-3-an-overview-project-structure-536g)