External Interface Layer

This piece of the Framework is the most flexible one, since it work as a wrapper for any external dependency code from libraries and modules. If coded properly, they will protect you from dependencies migrations and version upgrades.

External Interface Layer

As usual, let's study the example first:

class TestInterface extends ExternalInterface<TestRequest, TestResponse> {
  @override
  void handleRequest() {
    // For normal Gateways
    on<FutureTestRequest>(
      (request, send) async {
        await Future.delayed(Duration(milliseconds: 100));
        send(TestResponse('success'));
      },
    );

    // For WatcherGateways
    on<StreamTestRequest>(
      (request, send) async {
        final stream = Stream.periodic(
          Duration(milliseconds: 100),
          (count) => count,
        );

        final subscription = stream.listen(
          (count) => send(TestResponse(count.toString())),
        );

        await Future.delayed(Duration(milliseconds: 500));
        subscription.cancel();
      },
    );
  }
}

First let's understand the constructor. It requires a list of Gateway references, which are normally retrieved from providers. During tests, you can add the object reference directly.

When the External Interface gets created by its Provider, this connection will attach the object to the mechanism that the Gateway uses to send Requests.

The handleRequest method will have one or multiple calls of the on method, each one associated to a Request Type. These types must extend from the Response type specified on the generics class declaration.

Each of the on calls will send back a SuccessResponse or a FailureResponse.

External Interfaces are meant to listen to groups of Requests that use the same dependency. Clean Framework has default implementations of external interfaces for Firebase, GraphQL and REST services, ready to be used in any application, you just need to create the providers using them.

Coding the External Interface#

Here, we will create a simple External Interface that will use the Dio library to make a request to a PokeAPI.

For the external interface, we first need to create a Request and a Response class. The Request class will be used to send the request to the External Interface, and the Response class will be used to receive the response from the External Interface.

lib/features/home/external_interface/pokemon_request.dart#

abstract class PokemonRequest extends Request {
  Map<String, dynamic> get queryParams => {};
}

abstract class GetPokemonRequest extends PokemonRequest {
  String get resource;
}

lib/features/home/external_interface/pokemon_success_response.dart#

class PokemonSuccessResponse extends SuccessResponse {
  const PokemonSuccessResponse({required this.data});

  final Map<String, dynamic> data;
}

Then, we can create the External Interface class.

lib/features/home/external_interface/pokemon_external_interface.dart#

class PokemonExternalInterface extends ExternalInterface<PokemonRequest, PokemonSuccessResponse> {
  PokemonExternalInterface({
    Dio? dio,
  }) : _dio = dio ?? Dio(BaseOptions(baseUrl: 'https://pokeapi.co/api/v2/'));

  final Dio _dio;

  @override
  void handleRequest() {
    on<GetPokemonRequest>(
      (request, send) async {
        final response = await _dio.get<Map<String, dynamic>>(
          request.resource,
          queryParameters: request.queryParams,
        );

        final data = response.data!;

        send(PokemonSuccessResponse(data: data));
      },
    );
  }

  @override
  FailureResponse onError(Object error) {
    return UnknownFailureResponse(error);
  }
}

After the completion of external interface, we now need to connect the external interface with our domain layer. We'll do that by creating a gateway in next step, which act as an adapter between the external interface layer and the domain layer.