При разработке приложения Flutter вы, вероятно, хотите свести затраты на разработку к минимуму, сделать решение быстрым, надежным и безопасным. В этом руководстве (точнее, техническом обзоре с комментариями) я объясню, как подключить ваше приложение Flutter к серверной части с помощью HTTP REST API и gRPC. Вы узнаете, как правильно выбрать серверную часть для своего приложения, как ее настроить и как подключить к ней ваше приложение.

Что выбрать?

Не секрет, что REST API является наиболее распространенным способом связи между интерфейсом и сервером в современных веб-приложениях и инфраструктурах на основе микросервисов. Однако стоит отметить, что микросервисы могут использовать и другие способы связи.

HTTP REST API использует протокол HTTP для передачи данных между клиентом и сервером. Он прост в использовании и понятен большинству разработчиков. Чтобы использовать его, вам нужно определить запросы, которые ваше приложение должно отправлять на сервер, а также структуру ответов, которые вы ожидаете получить. В этой статье приведен пример использования пакета Dio.

gRPC (вызов удаленных процедур Google) — это новый подход к обмену данными между клиентом и сервером, основанный на архитектуре RPC с открытым исходным кодом. Он использует формат обмена сообщениями Protobuf, который является высокоэффективным форматом для обмена сообщениями с высокой степенью упаковки (благодаря принудительному использованию возможностей http/2) для сериализации структурированных данных. В некоторых случаях использование gRPC API может быть более эффективным, чем REST API.

При разработке приложения фронтенд и бэкенд обычно создают разные люди с разной компетенцией. Такие инструменты, как swagger и redoc, используются для предоставления инструкций по использованию REST. В случае с gRPC фронтенд получает практически готовый к реализации код. Также стоит учитывать тот факт, что если в проекте задействовано веб-приложение, использование gRPC для мобильного приложения может оказаться слишком дорогим.

Соединение с HTTP REST

Для работы с REST API я рекомендую использовать пакет Dio, поскольку он более функционален и удобен, чем стандартный http-пакет. Если вы создали веб-приложения, это похоже на axios.

Для использования Dio вам необходимо создать класс для работы с сетевыми подключениями, в котором вы будете определять запросы, которые ваше приложение должно отправлять на сервер, а также структуру ответов, которые вы ожидаете получить.

"флаттер паб добавить дио"

Добавьте пакет dio в свой проект

Создадим класс для работы с сетевыми подключениями:

import 'package:dio/dio.dart';

class NetworkService {
  late final Dio _dio;
  final JsonEncoder _encoder = const JsonEncoder();
  static final NetworkService _instance = NetworkService.internal();

  NetworkService.internal();

  static NetworkService get instance => _instance;

  Future<void> initClient() async {
    _dio = Dio(
      BaseOptions(
        baseUrl: Constant.baseUrl,
        connectTimeout: 60000,
        receiveTimeout: 6000,
      ),
    );
// A place for interceptors. For example, for authentication and logging
  }

  Future<dynamic> get(
    String url, {
    Map<String, dynamic>? queryParameters,
  }) async {
    try {
      final response = await _dio.get(url, queryParameters: queryParameters);
      return response.data;
    } on DioError catch (e) {
      final data = Map<String, dynamic>.from(e.response?.data);
      throw Exception(data['message'] ?? "Error while fetching data");
    } catch (e) {
      rethrow;
    }
  }

  Future<dynamic> download(String url, String path) async {
    return _dio.download(url, path).then((Response response) {
      if (response.statusCode! < 200 || response.statusCode! > 400) {
        throw Exception("Error while fetching data");
      }
      return response.data;
    }).onError((error, stackTrace) {
      throw Exception(error);
    });
  }

  Future<dynamic> delete(String url) async {
    return _dio.delete(url).then((Response response) {
      if (response.statusCode! < 200 || response.statusCode! > 400) {
        throw Exception("Error while fetching data");
      }
      return response.data;
    }).onError((DioError error, stackTrace) {
      _log(error.response);
    });
  }

  Future<dynamic> post(String url, {body, encoding}) async {
    try {
      final response = await _dio.post(url, data: _encoder.convert(body));
      return response.data;
    } on DioError catch (e) {
      throw Exception(e.response?.data['detail'] ?? e.toString());
    } catch (e) {
      rethrow;
    }
  }

  Future<dynamic> postFormData(String url, {required FormData data}) async {
    try {
      final response = await _dio.post(url, data: data);
      return response.data;
    } on DioError catch (e) {
      throw Exception(e.response?.data['detail'] ?? e.toString());
    } catch (e) {
      rethrow;
    }
  }

  Future<dynamic> patch(String url, {body, encoding}) async {
    try {
      final response = await _dio.patch(url, data: _encoder.convert(body));
      return response.data;
    } on DioError catch (e) {
      throw Exception(e.response?.data['detail'] ?? e.toString());
    } catch (e) {
      rethrow;
    }
  }

  Future<dynamic> put(String url, {body, encoding}) async {
    try {
      final response = await _dio.put(url, data: _encoder.convert(body));
      return response.data;
    } on DioError catch (e) {
      throw e.toString();
    } catch (e) {
      rethrow;
    }
  }
}

Пример

final NetworkService _client;

Future<String> login(LoginRequest loginRequest) async {
  try {
    final jsonData = await _client.post(
      "${Constant.baseUrl}/v1/auth/login",
      body: loginRequest.toJson()
    );
    return jsonData['access_token']
  } catch (e) {
    rethrow;
  }
}

Соединение с gRPC

Для работы с gRPC необходимо использовать сгенерированный код на основе протофайлов. Создайте сервис для работы, который будет использовать класс HelloClient, сгенерированный с помощью gRPC. Когда вы создаете экземпляр HelloClient, вам нужно будет передать ему канал, который будет использоваться для отправки запросов.

Теперь интегрируйте сгенерированный клиент gRPC в ваше приложение Flutter:

"флаттер паб добавить grpc"

Добавьте пакет grpc в свой проект

Создание сервиса для работы:

import 'package:grpc/grpc.dart';
//import your autogenerate code
import '../services/proto/hello.pbgrpc.dart';

class HelloService {

  ///here enter your host without the http part
  String baseUrl = "example.com";

  HelloService._internal();
  static final HelloService _instance = HelloService._internal();

  factory HelloService() => _instance;

  ///static HelloService instance that we will call when we want to make requests
  static HelloService get instance => _instance;
   ///HelloClient is the  class that was generated for us when we ran the generation command
  ///We will pass a channel to it to intialize it
  late HelloClient _helloClient;

  ///this will be used to create a channel once we create this class.
  ///Call HelloService().init() before making any call.
  Future<void> init() async {
    final channel = ClientChannel(
      baseUrl,
      port: 443,
      options: const ChannelOptions(),
    );
    _helloClient = HelloClient(channel);
  }

  ///provide public access to the HelloClient instance
  HelloClient get helloClient {
    return _helloClient;
  }
}

В вашей основной функции инициализируйте класс HelloService, как показано ниже.

HelloService().init();

Сделать вызов gRPC

Future<void> sayHello() async {
  try {
    HelloRequest helloRequest = HelloRequest();
    helloRequest.name = "Itachi";
    var res = await HelloService.instance.helloClient.sayHello(helloRequest);
  } catch (e) {
    print(e);
  }
}

Сравнение REST и gRPC для приложений Flutter

  • Преимущества REST: простой, понятный, широко поддерживаемый, кэшируемый и подходящий для большинства приложений.
  • Недостатки REST: более высокая задержка, менее эффективная сериализация, ограниченные возможности потоковой передачи.
  • Преимущества gRPC: низкая задержка, эффективная сериализация, поддержка потоковой передачи, мощные клиентские библиотеки и идеальное решение для приложений и микросервисов, работающих в реальном времени.
  • Недостатки gRPC: более крутая кривая обучения, не такая удобочитаемая для человека, меньшая поддержка в веб-браузерах и может быть излишним для простых приложений.

Выбирая между REST и gRPC для своего приложения Flutter, учитывайте конкретные потребности и требования вашего приложения. REST — хороший выбор для большинства приложений, в то время как gRPC хорошо подходит для приложений реального времени или приложений со сложными шаблонами связи между службами.

В этой статье мы рассмотрели, как организовать приложение Flutter с серверной частью API, сосредоточившись на двух основных подходах: HTTP REST и gRPC. Мы обсудили разработку API, реализацию клиентов во Flutter.

Поняв преимущества и недостатки каждого подхода, вы сможете принять взвешенное решение при разработке приложения Flutter с серверной частью API. Выберите наилучший подход для своего приложения с учетом его конкретных потребностей и создавайте эффективные, масштабируемые и удобные в обслуживании приложения.