GitHub - flutterwtf/Flutter-HTTP-vs-DIO-vs-GraphQL at blog.flutter.wtf
Contribute to flutterwtf/Flutter-HTTP-vs-DIO-vs-GraphQL development by creating an account on GitHub.

Se trata de una aplicación básica en la que los usuarios pueden registrarse, crear publicaciones y dejar comentarios en las publicaciones. Como backend utilizamos un proyecto de open source, por lo que en este artículo vamos a considerar las características de la creación de un cliente.

HTTP


Dart tiene un paquete integrado llamado http para manejar todas tus necesidades HTTP.

Ventajas

  1. Suministrado por los programadores de Dart;
  2. Estándar para la interacción cliente-servidor.

Desventajas

  1. Sólo ofrece funciones básicas. Funcionalidad adicional debe ser implementada independientemente;
  2. Requiere escribir funciones adicionales para el manejo de errores.

Cómo Utilizarlo


En primer lugar, añada esta dependencia a nuestro  pubspec.yaml.

First, add this dependency to our pubspec.yaml.

dependencies:
  http: ^0.13.5

Vamos a ver ahora cómo realizar operaciones CRUD utilizando http (http_crud.dart).

Consideremos un ejemplo de ejecución de un post petición:

@override
  Future<RestResponseWrapper> post(String path, dynamic data) async {
    final response = await http.post(
      Uri.parse('${ConnectivityStrings.baseUrl}$path'),
      headers: _header,
      body: jsonEncode(data),
    );
    return RestResponseWrapper(data: response.body, status: response.statusCode);
  }

El método un objeto de la RestResponseWrapper clase.

Se encarga de gestionar las respuestas y, en el caso de http, de gestionar los errores.

💡
Las clases y extensiones para procesar respuestas se encuentran en client/data/utils/remote_utils.dart
class RestResponseWrapper {
  final dynamic data;
  final int status;
  final Type _type =
      ConnectivityStrings.networkModule == NetworkModule.http.name ? http.Response : dio.Response;

  RestResponseWrapper({required this.data, this.status = 200});

  bool isSuccessful() => status >= 200 && status < 400;

  bool wrongUserData() => status >= 400 && status < 500;

  T? retrieveResult<T>() {
    if (isSuccessful()) {
      if (data.isNotEmpty && data != 'OK') {
        if (_type == http.Response) {
          return _createFromJSON<T>(json.decode(data.toString()))!;
        } else {
          return _createFromJSON<T>(data)!;
        }
      } else {
        return null;
      }
    } else if (wrongUserData()) {
      throw WrongUserDataException();
    } else {
      throw Exception();
    }
  }

  List<T> retrieveResultAsList<T>() {
    if (isSuccessful()) {
      if (_type == http.Response) {
        return (json.decode(data.toString()) as List).map((e) => (_createFromJSON<T>(e)!)).toList();
      } else {
        return (data as List).map((e) => (_createFromJSON<T>(e)!)).toList();
      }
    } else {
      throw Exception();
    }
  }
}

Usando status, comprobamos si nuestra petición se ha completado con éxito. Si se ha producido un error, lanzamos la excepción apropiada. Si no, decodificamos el cuerpo de la petición y lo deserializamos usando el método _create From JSON<T>.

T? _createFromJSON<T>(Map<String, dynamic> json) {
  Type typeOf<V>() => V;
  final type = typeOf<T>();
  if (type == typeOf<User>()) {
    return User.fromJson(json) as T;
  } else if (type == typeOf<Post>()) {
    return Post.fromJson(json) as T;
  } else if (type == typeOf<Comment>()) {
    return Comment.fromJson(json) as T;
  } else if (typeOf<dynamic>() == type || typeOf<void>() == type) {
    return null;
  }
  throw ArgumentError('Looks like you forgot to add processing for type ${type.toString()}');
}

DIO

Dio es un poderoso cliente HTTP para Dart. Tiene soporte para interceptores, configuración global, FormData, cancelación de peticiones, descarga de archivos y tiempo de espera, entre otros.

Ventajas

  1. Proporciona muchas funcionalidades. Además de realizar cosas básicas de red, dio también proporciona algunas funcionalidades extra como interceptores, log, caché, etc;
  2. Permite implementar el cliente lo más rápido posible. Tiene abstracciones adicionales que reducen el número de código y aceleran el proceso de desarrollo.

Desventajas

  1. Confiabilidad. Debido al gran número de funciones proporcionadas, la probabilidad de un bug en el paquete es más alta.

Cómo Utilizarlo

En primer lugar, añada el paquete Dio a su pubspec.yaml:

dependencies:
  dio: ^4.0.6

El siguiente paso es la Inicialización de Dio:

final _dio = Dio();

DioRestRemoteDataSource() {
  _dio.options.baseUrl = ConnectivityStrings.baseUrl;
}

En este caso, la ejecución de la solicitud es un poco diferente:

@override
  Future<RestResponseWrapper> post(String path, dynamic data) async {
    try {
      final response = await _dio.post(path, data: data.toJson());
      return RestResponseWrapper(data: response.data);
    } on DioError catch (e) {
      throw _getExceptionByStatusCode(e);
    }
  }

El tratamiento de errores se realiza de forma diferente. Necesitamos envolver la ejecución de la petición en un try-catch. Para determinar el origen del error, utilizamos el método _getExceptionByStatusCode:

Exception _getExceptionByStatusCode(DioError e) {
    return e.response?.statusCode == 401 || e.response?.statusCode == 409
        ? WrongUserDataException()
        : Exception('Error: ${e.response?.statusMessage} \\n Error code: ${e.response?.statusCode}');
  }

En este caso, al llamar al método retrieveResult, sólo realizamos la deserialización.

http vs DIO

Teniendo en cuenta las peculiaridades de estos enfoques, podemos destacar los siguientes casos de uso:

Comparando el paquete http con Dio, Dio cubre la mayoría de los casos de red estándar con un esfuerzo mínimo, mientras que http sólo proporciona funcionalidad básica. Así que para la mayoría de los proyectos en los que la velocidad de desarrollo es importante, Dio es perfecto.

Pero para proyectos grandes con apoyo a largo plazo, a veces es mejor elegir http y luego escribir los métodos que faltan uno mismo, ya que Dio no es de los programadores de Dart.

Si todavía no sabes qué elegir echa un vistazo a GraphQL.

GraphQL (bonus)

GraphQL es un idioma de consulta para APIs web, que fue creado por Facebook en 2012 y publicado en 2015.

El paquete GraphQL es un cliente para dart basado en el cliente de apollo, y actualmente es el cliente GraphQL más popular para dart.

Ventajas

  1. No hay overfetching ni underfetching. Puede obtener sólo la información que necesita;
  2. Compatible con suscripciones para datos en tiempo real. Las suscripciones son útiles para notificar al cliente en tiempo real sobre cambios en los datos, como la creación de un nuevo objeto o la actualización de algunos campos;
  3. Seguridad de tipos. El esquema de respuesta describe el tipo de cada campo y su obligatoriedad en la petición.

Desventajas

  1. Grandes consultas. Si disponemos de entidades con un gran número de campos, la escritura de consultas se vuelve monótona y requiere tiempo extra;
  2. Notificación de errores. Las consultas siempre devuelven un código de estado HTTP de 200, incluso si la solicitud falló con un error. Si su consulta no tiene éxito, su respuesta JSON tendrá una clave de error con un mensaje de error. Esto dificulta la gestión de errores.

Cómo Utilizarlo

Añada una dependencia a su pubspec.yaml :

dependencies:
  graphql: ^5.1.1

Vamos a ver qué aspecto tienen la consulta y la mutación:

static const String getCommentsByPostId = r'''
  query GetComments($postId: ID!) {
    comments (postId: $postId) {
        id,
        authorName,
        text,
        date,
        userId,
        postId
    }
  }
''';

static const String createUser = r'''
  mutation CreateUserMutation($name: String!, $password: String!) {
    createUserMutation(input: { name: $name, password: $password }) {
      error
    }
  }
''';
💡
Todas las consultas y mutaciones se encuentran en el client/data/utils/graphql_query_strings.dart file.

Ahora vamos a cuidar del propio cliente.

En el constructor de la clase, inicializamos link y el cliente GraphQl:

late final GraphQLClient _client;

GraphqlRemoteDataSource() {
  final Link link = HttpLink(
    ConnectivityStrings.baseUrl,
  );
  _client = GraphQLClient(
    cache: GraphQLCache(),
    defaultPolicies: DefaultPolicies(
      query: Policies(
        fetch: FetchPolicy.networkOnly,
      ),
    ),
    link: link,
  );
}

Usado DefaultPolicies, puede configurar varias opciones de uso de caché en su aplicación.

Consideremos un ejemplo de realización de una mutación:

@override
Future<void> createComment(Comment comment) async {
  final MutationOptions options = MutationOptions(
    document: gql(GraphQLQueryStrings.createComment),
    variables: <String, dynamic>{
      'postId': comment.postId,
      'userId': comment.userId,
      'authorName': comment.authorName,
      'text': comment.text,
      'date': comment.date.toString(),
    },
  );

  final QueryResult result = await _client.mutate(options);
  return result.retrieveResult();
}

Pasamos la cadena de mutación como parámetro del document parameter, y especificamos los valores necesarios en las variables .

En este caso, utilizamos la cadena de mutación para procesar las respuestas:

extension ResponseTo on QueryResult {
  T? retrieveResult<T>({String? tag}) {
    T? result;
    if (exception != null) {
      throw Exception('Error: ${exception.toString()}');
    } else {
      if (tag != null) {
        result = data![tag] == null
            ? null
            : data![tag]['error'] != null
                ? throw WrongUserDataException()
                : _createFromJSON<T>(data![tag]);
      }
      return result;
    }
  }

  List<T> retrieveResultAsList<T>({required String tag}) {
    List<T> result;
    if (exception != null) {
      throw Exception('Error: ${exception.toString()}');
    } else {
      result = (data![tag] as List).map((e) => (_createFromJSON<T>(e)!)).toList();
      return result;
    }
  }
}

La diferencia entre los ejemplos anteriores estriba únicamente en el acceso a los datos necesarios.

HTTP REST vs GraphQL

HTTP REST es la tecnología de construcción de API más popular y utilizada. Sin embargo, tiene una desventaja, que en algunos casos es un aspecto importante - Overfetching y underfetching. Debido a los puntos finales estrictamente definidos, hay casos en los que recibimos más datos de los necesarios, o necesitamos ejecutar varias peticiones a la vez.

Como vimos anteriormente, GraphQL resuelve este problema devolviendo sólo los datos necesarios, pero también tiene sus propios matices.

La elección de la tecnología depende del tamaño, la complejidad y las características de la aplicación.

Para aplicaciones pequeñas, utilizar graphql no es muy conveniente, el proceso puede complicarse y llevar más tiempo. Tampoco es la mejor opción para equipos pequeños. GraphQL requiere un gran equipo capaz de minimizar los riesgos de rendimiento.

De todas formas el líder de tu equipo sabe más que tú y ellos decidirán qué tecnología sería mejor para el proyecto en concreto 😁.

Vínculos útiles:

GitHub - flutterwtf/Flutter-HTTP-vs-DIO-vs-GraphQL
Contribute to flutterwtf/Flutter-HTTP-vs-DIO-vs-GraphQL development by creating an account on GitHub.
GitHub - flutterwtf/Flutter-gRPC-Sample
Contribute to flutterwtf/Flutter-gRPC-Sample development by creating an account on GitHub.
Share this post