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.
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.