<!--
 * Copyright (C) 2022 WYATT GROUP
 * Please see the AUTHORS file for details.

 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
-->

# Flutter - Wyatt Architecture

<p align="left">
  <a href="https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_analysis">

    <img src="https://img.shields.io/badge/Style-Wyatt%20Analysis-blue.svg?style=flat-square" alt="Style: Wyatt Analysis" />

  </a>
  <img src="https://img.shields.io/badge/SDK-Flutter-blue?style=flat-square" alt="SDK: Flutter" />
</p>

The Wyatt Architecture for Flutter.

## Features

- Usecase
- Repository
- DataSource
- Entity

## Usage

### Domain

Create your entities by extending `Entity` :

```dart
class Photo extends Entity {
  final int id;
  final String url;

  const Photo({
    required this.id,
    required this.url,
  });
}
```

Then create the data sources by extending `BaseLocalDataSource` or `BaseRemoteDataSource` depending the type of data source.

```dart
abstract class PhotoRemoteDataSource extends BaseRemoteDataSource {
  Future<Photo> getPhoto(int id);
  Future<List<Photo>> getAllPhotos({int? start, int? limit});
}

```

Then you can create your repositories by extenting `BaseRepository` :

```dart
abstract class PhotoRepository extends BaseRepository {
  FutureResult<Photo> getPhoto(int id);
  FutureResult<List<Photo>> getAllPhotos({int? start, int? limit});
}
```

> Here the repository is just a proxy of the data sources with result type (to have beautiful error handling).

And finaly create your different usecases :

Several use cases are supported :

- Classic usecase :

```dart
class Test extends AsyncUseCase<QueryParameters, List<Photo>>> {

  @override
  FuturOrResult<List<Photo>>> call(QueryParameters? params) {
     final photos = _photoRepository.getAllPhotos(
      start: params.start,
      limit: params.limit,
    );
    return photos;
  }
}
```

You can add alternatve scenarios and check pre/post conditions using our extensions :

```dart
class SearchPhotos extends AsyncUseCase<QueryParameters, List<Photo>>> {

  @override
  FutureOrResult<List<Photo>>> call(QueryParameters? params) {
     final photos = _photoRepository.getAllPhotos(
      start: params.start,
      limit: params.limit,
    );
    return photos;
  }


  @override
  FutureOr<void> onStart(QueryParameters? params) {
    if(params.start == null || params.limit == null){
      throw ClientException('Préconndition non valides');
    }
  }

}
```

You can implement error scenarios overriding `onError`, or check postconditions by overriding `onComplete` .

- Stream usecase :

```dart
class SearchPhotos extends StreamUseCase<QueryParameters, List<Photo>>> {

  @override
  FutureOrResult<Stream<List<Photo>>>> call(QueryParameters? params) {
     final photos = _photoRepository.getAllPhotos(
      start: params.start,
      limit: params.limit,
    );
    return photos;
  }

}
```

On this case, observers allow you to add alternative scénarios when data changed, overriding `onData` or `onDone`.

Please note that to use handlers, call `call` method and not `execute`.

> In fact, here we need a new parameter object, so let's create it:

```dart
class QueryParameters {
  final int? start;
  final int? limit;

  QueryParameters(this.start, this.limit);
}
```

### Data

We start by creating models for photos and list of photos. You can use `freezed`. The `PhotoModel` extends `Photo` with some de/serializer capabilities. And those are used only in data layer.

Then implements your data sources:

```dart
class PhotoApiDataSourceImpl extends PhotoRemoteDataSource {
  final MiddlewareClient _client;

  PhotoApiDataSourceImpl(this._client);

  @override
  Future<Photo> getPhoto(int id) async {
    final response = await _client.get(Uri.parse('/photos/$id'));
    final photo =
        PhotoModel.fromJson(jsonDecode(response.body) as Map<String, Object?>);
    return photo;
  }

  @override
  Future<List<Photo>> getAllPhotos({int? start, int? limit}) async {
    final startQuery = start.isNotNull ? '_start=$start' : '';
    final limitQuery = limit.isNotNull ? '_limit=$limit' : '';
    final delimiter1 =
        (startQuery.isNotEmpty || limitQuery.isNotEmpty) ? '?' : '';
    final delimiter2 =
        (startQuery.isNotEmpty && limitQuery.isNotEmpty) ? '&' : '';
    final url = '/photos$delimiter1$startQuery$delimiter2$limitQuery';
    final response = await _client.get(Uri.parse(url));
    final photos =
        ListPhotoModel.fromJson({'photos': jsonDecode(response.body)});
    return photos.photos;
  }
}
```

> 1: Note that here we use `MiddlewareClient` from our http package.

> 2: You can create multiple implementations (one real and one mock for example).

And implement the repositories:

```dart
class PhotoRepositoryImpl extends PhotoRepository {
  final PhotoRemoteDataSource _photoRemoteDataSource;

  PhotoRepositoryImpl(
    this._photoRemoteDataSource,
  );

  @override
  FutureResult<Photo> getPhoto(int id) => Result.tryCatchAsync(
        () => _photoRemoteDataSource.getPhoto(id),
        (error) => ServerException('Cannot retrieve photo $id.'),
      );

  @override
  FutureResult<List<Photo>> getAllPhotos({int? start, int? limit}) async =>
      Result.tryCatchAsync(
        () => _photoRemoteDataSource.getAllPhotos(start: start, limit: limit),
        (error) => ServerException('Cannot retrieve all photos.'),
      );
}
```

That's all.
