Джентльменский набор инструментов для работы с Flutter и GraphQL

Приветствую! Последнее время я активно занимаюсь исследованием Flutter на предмет пригодности для продакшена с учетом наших потребностей. Одна из них — использование GraphQL для получения данных. Для этого я параллельно с разработчиками на ReactNative пишу то же приложение на Flutter (кстати, по ссылке — моя статья на DOU, посвященная сравнению Flutter и ReactNative). Я уже вдоволь наигрался и версткой, и анимацией, так что настало время переходить к разбору полетов в области GraphQL.

Вступление

Около 6 месяцев назад, когда я только начинал эксперименты с GraphQL, в экосистеме Flutter все было не так радужно. Основному плагину для работы с GraphQL был год отроду, а пакет artemis для генерации типов был в условном релизе всего пару месяцев. Документация разрознена, целостного решения нет, в общем — печаль.

На днях я снова вернулся к этой теме, и у меня получилось собрать довольно неплохой набор инструментов для работы с GraphQL, который удовлетворяет все мои желания, чем спешу с вами поделиться.

Я буду рассматривать инструменты на примере Android Studio, что также подходит для всего семейства продуктов от JetBrains.

Начнем сначала: если вы не знакомы с Flutter и/или GraphQL (я не знаю, зачем вы открыли эту статью), начните с небольшой вводной:

Увидимся после прочтения :)

Подготовка

Что бы хотелось получить:

  • генерацию типов для GraphQL API;
  • полноценную работу автокомплита;
  • обновление схемы для генерации типов в один клик;
  • автодополнение и валидацию GraphQL запросов;
  • возможность работать с API, не дожидаясь имплементации на бэке.

Что для этого потребуется:

Практика

Создаем новый Flutter проект

File -> New -> New Flutter Project

В открывшемся окне оставляем выбранный пункт по умолчанию — Flutter Application.

Жмем Next до самого конца (ничего не меняя в настройках), и в конце — Finish.

Более красочно процесс описан в документации в разделе Test Drive.

В итоге у нас получится демо приложение с любимым каунтером.

Устанавливаем graphql_faker

npm install -g graphql-faker

или

yarn global add graphql-faker

Запускаем graphql-faker ./fake.schema --open

Запустится сервер, автоматически откроется страница в браузере для редактирования API.

Параметр ./fake.schema указывает файл, который будет взят за основу схемы. Так как у нас нет такого файла — за основу будет взят дефолтный мок, поставляемый с сервером.

Попробуем кое-что добавить: после 20-й строки пишем id: ID! @fake(type: uuid) и нажмем кнопку Save, в папке с проектом появится файл fake.schema с внесенными вами изменениями. Его будет удобно в последующем использовать для старта сервера, закоммитить для шеринга между командой или как вам заблагорассудится.

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

Приятный бонус — проект поддерживает наш земляк Иван Гончаров. Сейчас активно готовится версия v2.0.0, нелишним будет поддержать коллегу пиаром или детальным баг-репортом.

Плагин JSGraphQL

Создаем файл .graphqlconfig в корне проекта. Заполняем копи/пастой

{
 "name": "Schema",
 "schemaPath": "my.schema.json",
 "extensions": {
   "endpoints": {
     "Default": {
       "url": "http://localhost:9002/graphql",
       "introspect": true
     }
   }
 }
}

Сохраняем изменения, переходим на вкладку GraphQL, дважды щелкаем на Default, выбираем Get graphql schema from endpoint:

Будет создан файл my.schema.json, содержащий свежую схему вашего АПИ.

Плагин будет удобен для контроля над ошибками и помощи в автозаполнении при написании запросов. Это легко проверить: создаем папку graphql в корне проект, в ней — файл employee_data.graphql, пробуем набрать несложный запрос:

query EmployeeData($id: ID!) {
   employee(id: $id) {
       firstName
       id
   }
}

...и радуемся работе автозаполнения.

Возвращаемся в редактор graphql_faker, меняем у employee firstName на firstName1, жмем Save, обновляем локальную схему и радуемся отображению ошибки.

Flutter-часть

Нам понадобится два плагина: flutter_graphql для работы непосредственно с GraphQL и artemis для генерации типов.

Добавляем в pubspeck.yaml следующие строки:

dependencies:
  …
 graphql_flutter: ^3.0.0-beta.3
 path_provider: ^1.5.1
 equatable: ^1.0.2
 json_serializable: ^3.2.3
 gql: 0.12.0


dev_dependencies:
 ...
 build_runner: ^1.7.2
 artemis: ^2.1.4

Выполняем в консоли flutter packages get.

Artemis

Начнем с настройки artemis (подробнее о настройках — здесь).

Создаем файл build.yaml в корне проекта со следующим содержимым:

targets:
 $default:
   sources:
     - lib/**
     - graphql/**
     - my.schema.json
   builders:
     artemis:
       options:
         schema_mapping:
           - schema: my.schema.json
             queries_glob: graphql/*.graphql
             output: lib/graphql_api.dart

Запускаем генерацию типов

pub run build_runner build

или

flutter pub run build_runner build

Спустя 10-20 секунд генерируется два файла:

  • graphql_api.dart;
  • graphql_api.g.dart.

flutter_graphql

Теперь попробуем добыть список компаний и посмотреть, как все работает в связке.

Возвращаемся в редактор graphql_faker и после 14-й строки добавляем id для компании id: ID! @fake(type: uuid) — это нам понадобится для корректной работы кеша. Сохраняем. Обновляем локальную схему.

Создаем новый файл graphql/companies_data.graphql.

targets:
 $default:
   sources:
     - lib/**
     - graphql/**
     - my.schema.json
   builders:
     artemis:
       options:
         schema_mapping:
           - schema: my.schema.json
             queries_glob: graphql/*.graphql
             output: lib/graphql_api.dart

Генерируем типы flutter pub run build_runner build.

Создаем файл lib/graphql_provider.dart.

import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:flutter/material.dart';

String uuidFromObject(Object object) {
 if (object is Map<String, Object>) {
   final String typeName = object['__typename'] as String;
   final String id = object['id'].toString();
   if (typeName != null && id != null) {
     return <String>[typeName, id].join('/');
   }
 }
 return null;
}

final OptimisticCache cache = OptimisticCache(
 dataIdFromObject: uuidFromObject,
);

ValueNotifier<GraphQLClient> clientFor({
 @required String uri,
 String subscriptionUri,
}) {
 Link link = HttpLink(uri: uri);
 if (subscriptionUri != null) {
   final WebSocketLink websocketLink = WebSocketLink(
     url: subscriptionUri,
     config: SocketClientConfig(
       autoReconnect: true,
       inactivityTimeout: Duration(seconds: 30),
     ),
   );

   link = link.concat(websocketLink);
 }

 return ValueNotifier<GraphQLClient>(
   GraphQLClient(
     cache: cache,
     link: link,
   ),
 );
}

/// Wraps the root application with the `graphql_flutter` client.
/// We use the cache for all state management.
class GraphqlProvider extends StatelessWidget {
 GraphqlProvider({
   @required this.child,
   @required String uri,
   String subscriptionUri,
 }) : client = clientFor(
         uri: uri,
         subscriptionUri: subscriptionUri,
       );

 final Widget child;
 final ValueNotifier<GraphQLClient> client;

 @override
 Widget build(BuildContext context) {
   return GraphQLProvider(
     client: client,
     child: child,
   );
 }
}

В файле main.dart добавляем функцию host, которая в зависимости от платформы подправит адрес localhost’a.

String get host {
 if (Platform.isAndroid) {
   return '10.0.2.2';
 } else {
   return 'localhost';
 }
}

А MaterialApp (в том же файле main.dart) виджет оборачиваем в:

GraphqlProvider(
 uri: 'http://$host:9002/graphql',
 child: MaterialApp(...),
)

Параметр body меняем полностью на:

Query(
 options: QueryOptions(
   documentNode: CompaniesDataQuery().document,
 ),
 builder: (
   QueryResult result, {
   Future<QueryResult> Function() refetch,
   FetchMore fetchMore,
 }) {
   if (result.hasException) {
     return Text(result.exception.toString());
   }

   if (result.loading) {
     return const Center(
       child: CircularProgressIndicator(),
     );
   }

   final allCompanies = CompaniesData.fromJson(result.data).allCompanies;

   return ListView.builder(
     itemBuilder: (_, index) {
       return ListTile(
         leading: Icon(Icons.card_travel),
         title: Text(allCompanies[index].name),
         subtitle: Text(allCompanies[index].industry),
       );
     },
     itemCount: allCompanies.length,
   );
 },
)

Перезапустите приложение, и у вас получится что-то наподобие:

Немного подробностей реализации:

Виджет Query предоставляет нам возможность отправить запрос.

В параметр documentNode мы передаем непосредственно сам запрос, сгенерированный artemis.

options: QueryOptions(
   documentNode: CompaniesDataQuery().document,
 ),

После того как flutter_graphql перешел в версии 3 на documentNode формат, а artemis добавил генерацию кверей, эти две системы стали замечательно работать вместе.

В функции builder три стандартных части:

Вывод ошибки

if (result.hasException) {
     return Text(result.exception.toString());
   }

Вывод спиннера

  if (result.loading) {
     return const Center(
       child: CircularProgressIndicator(),
     );
   }

Вывод содержимого результата

   final allCompanies = CompaniesData.fromJson(result.data).allCompanies;

   return ListView.builder(
     itemBuilder: (_, index) {
       return ListTile(
         leading: Icon(Icons.card_travel),
         title: Text(allCompanies[index].name),
         subtitle: Text(allCompanies[index].industry),
       );
     },
     itemCount: allCompanies.length,
   );
 },

Итог

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

Весь код по ссылке.

Поддержите проекты, отмеченные в этой статье, звездочкой, баг-репортом или пулл-реквестом.

В комментариях оставляйте ссылки на свои любимые библиотеки для работы с GraphQL.

А также присоединяйтесь к нашей группе «Art Flutter» в телеграме — нас много, и мы рады помочь вам с решением проблем :)

Похожие статьи:
Компания Apple объявила о создании первого европейского центра разработки приложений для iOS в Италии с целью предоставить студентам...
.NET — це платформа від Microsoft для створення програмного забезпечення. Сьогодні вона досить популярна, про що свідчить велика...
Всем доброго дня! Недавно для одного из заказчиков проводил нагрузочное тестирование веб портала. Результат оказался...
Німецький deftech-єдиноріг Quantum Systems і українська Frontline Robotics запускають спільне виробництво дронів у Німеччині. Для цього...
Олег Карпенко — колишній військовий та співробітник найбільшого українського фонду компетентної допомоги армії —...
Яндекс.Метрика