ferry 0.2.2

  • Readme
  • Changelog
  • Example
  • Installing
  • 72

Stream Based GraphQL Client for Dart

MIT License PRs Welcome Star on GitHub Watch on GitHub Pub Version

Warning: This library is still a work in progress. The API may change.

Features #

FeatureProgress
Generated Fully Typed Queries and Resposnes (using gql_build)
Customizable Links (using gql_link)
Optimistic Cache
Multiple data stores, including MemoryStore and HiveStore (using hive for offline persistence)
Update queries with additinal data (e.g. for pagination)
Flutter Widget
Offline Mutations🔜

Architecture #

  1. Code Builders (from gql_build):
    1. Create dart representations of all queries (including their variables, inputs, and data)
    2. Using the additional req_builder included with this package, generate typed QueryRequest objects which allow the client to parse typed responses.
  2. Client:
    1. Handles configuration
    2. Routes QueryRequests to the cache or network, based on the given FetchPolicy
    3. Generates streams of QueryResponses for a given QueryRequest
  3. Link (from gql_link): Handles GraphQL network requests
  4. Cache:
    1. Normalizes and denormalizes data for queries and fragments (using the normalize package)
    2. Maintains a collection of Optimistic Patches and handles optimistic reads and writes
  5. Store: Persists data

Usage #

Setup Client #

Add ferry and gql_http_link to your pubspec.

Simple #

import 'package:gql_http_link/gql_http_link.dart';
import 'package:ferry/ferry.dart';

final link = HttpLink("[path/to/endpoint]");

final client = Client(link: link);

This instantiates a client with the default configuration, including a Cache instance that uses a MemoryStore to store data.

With HiveStore (persisted offline data) #

Add hive (and hive_flutter if you're using flutter) to your pubspec.

import 'package:gql_http_link/gql_http_link.dart';
import 'package:ferry/ferry.dart';
import 'package:hive/hive.dart';
// *** If using flutter ***
// import 'package:hive_flutter/hive_flutter.dart';

Future<Client> initClient() async {
  Hive.init();
  // OR, if using flutter
  // await Hive.initFlutter();

  final box = await Hive.openBox("graphql");

  final store = HiveStore(box);

  final cache = Cache(dataStore: store);

  final link = HttpLink("[path/to/endpoint]");

  final client = Client(
    link: link,
    cache: cache,
  );

  return client;
}

With UpdateCacheHandlers #

The Client allows arbitrary cache updates following mutations, similar to functionality provided by Apollo Client's mutation update function. However, in order for mutations to work offline (still a WIP), the client must be aware of all UpdateCacheHandlers.

typedef UpdateCacheHandler<T> = void Function(
  CacheProxy proxy,
  QueryResponse<T> response,
);

CacheProxy provides methods to readQuery, readFragment, writeQuery, and writeFragment.

import 'package:gql_http_link/gql_http_link.dart';
import 'package:ferry/ferry.dart';

import '[path/to/MyUpdateCacheHandler]';

final link = HttpLink("https://graphql-pokemon.now.sh/graphql");

final updateCacheHandlers = <dynamic, Function>{
  "MyHandlerKey": MyUpdateCacheHandler,
};

final options = ClientOptions(updateCacheHandlers: updateCacheHandlers);

final client = Client(
  link: link,
  options: options,
);

This handler can then be called using its key "MyHandlerKey" from a QueryRequest.

Generate Dart GraphQL Files #

The Client is fully typed, so we must use the gql_build package to generate dart representations of our GraphQL queries. We will also use the req_builder included in the Client package to build typed QueryRequests for each GraphQL query.

Download GraphQL Schema #

First, we need to downoad our GraphQL in SDL format to any location within the lib project directory. You can use the get-graphql-schema tool to download a schema from a GraphQL endpoint:

First, install the tool:

npm install -g get-graphql-schema

Next, download the schema:

get-graphql-schema ENDPOINT_URL > lib/schema.graphql

Add Queries to .graphql files #

gql_build will generate dart code for all files located in the lib folder that end in a .graphql extention.

For example, we might have the following in all_pokemon.graphql:

query AllPokemon($first: Int!) {
  pokemons(first: $first) {
    id
    name
    maxHP
    image
  }
}

Build Generated Queries #

Add gql_build and build_runner to your dev_dependencies in your pubspec file.

Next add a build.yaml file to your project root:

targets:
  $default:
    builders:
      gql_build|schema_builder:
        enabled: true
      gql_build|ast_builder:
        enabled: true
      gql_build|op_builder:
        enabled: true
        options:
          schema: your_package_name|lib/schema.graphql
      gql_build|data_builder:
        enabled: true
        options:
          schema: your_package_name|lib/schema.graphql
      gql_build|var_builder:
        enabled: true
        options:
          schema: your_package_name|lib/schema.graphql

      ferry|req_builder:
        enabled: true

Now we can build our dart generated files by calling:

pub run build_runner build

Or, if we are using flutter

flutter pub run build_runner build

Queries #

import 'path/to/client.dart';
import './[my_query].req.gql.dart';

// Instantiate a `QueryRequest` using the generated `.req.gql.dart` file.
final query = MyQuery(buildVars: (b) => b..id = "123");

// Listen to responses for the given query
client.responseStream(query).listen((response) => print(response));

Mutations #

Mutations are executed in the same way as queries

import 'path/to/client.dart';
import './[my_mutation].req.gql.dart';

// Instantiate a `QueryRequest` using the generated `.req.gql.dart` file.
final mutation = MyMutation(buildVars: (b) => b..id = "123");

// If I only care about the first non-optimistic response, I can do:
client
  .responseStream(mutation)
  .firstWhere((response) => !response.optimistic)
  .then((response) => print(response));

With Flutter #

The library includes a Query flutter widget, which is a simple wrapper around the StreamBuilder widget.

This example assumes we've registered our Client instance with get_it, but you can use any dependency injection.

import 'package:flutter/material.dart';
import 'package:ferry/ferry.dart';
import 'package:get_it/get_it.dart';

import './my_query.data.gql.dart';
import './my_query.req.gql.dart';

class AllPokemonScreen extends StatelessWidget {
  final client = GetIt.I<Client>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('All Pokemon'),
      ),
      body: Query(
        client: client,
        queryRequest: AllPokemon(
          buildVars: (vars) => vars..first = 500,
        ),
        builder: (
          BuildContext context,
          QueryResponse<$AllPokemon> response,
          Object clientError,
        ) {
          if (response.loading)
            return Center(child: CircularProgressIndicator());

          final pokemons = response.data?.pokemons ?? [];

          return ListView.builder(
            itemCount: pokemons.length,
            itemBuilder: (context, index) => PokemonCard(
              pokemon: pokemons[index],
            ),
          );
        },
      ),
    );
  }
}

Changelog #

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased] #

[0.2.2] - 2020-02-29 #

Changed #

  • QueryRequest now extends Request
  • update req_builder to use new URI fragments
  • add QueryReqeust.copyWith
  • add clientError object to Query Widget
  • req_builder no longer assigns a unique queryId if none is provided

Removed #

  • Removed Options objects

[0.2.1] - 2020-02-22 #

Changed #

  • use latest versions of gql_build and gql_code_builder

[0.2.0] - 2020-02-22 #

Changed #

  • rename to "ferry"
  • move repo to gql-dart

[0.1.3] - 2020-02-21 #

Changed #

  • remove 'GQL' prefix
  • use latest version of gql_build
  • update example

[0.1.2] - 2020-02-14 #

Changed #

  • update dependencies
  • fix erroneous import

[0.1.1] - 2020-02-14 #

Changed #

  • export CacheProxy

[0.1.0] - 2020-02-12 #

Changed #

  • BREAKING convert options to use built_value

[0.0.3] - 2020-02-08 #

Changed #

  • update example

[0.0.2] - 2020-02-08 #

Removed #

  • Remove custom network error
  • Remove unnecessary readme code

[0.0.1] - 2020-02-05 #

Added #

  • Initial release

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:ferry/ferry.dart';

import './src/client.dart';
import './src/app.dart';

void main() async {
  final client = await initClient();
  GetIt.I.registerLazySingleton<Client>(() => client);
  runApp(App());
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  ferry: ^0.2.2

2. Install it

You can install packages from the command line:

with Flutter:


$ flutter pub get

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:ferry/ferry.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
53
Health:
Code health derived from static analysis. [more]
98
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
80
Overall:
Weighted score of the above. [more]
72
Learn more about scoring.

We analyzed this package on Apr 7, 2020, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.7.1
  • pana: 0.13.6
  • Flutter: 1.12.13+hotfix.8

Health suggestions

Fix lib/src/req_builder/req_builder.dart. (-1.49 points)

Analysis of lib/src/req_builder/req_builder.dart reported 3 hints:

line 5 col 8: Don't import implementation files from another package.

line 6 col 8: Don't import implementation files from another package.

line 7 col 8: Don't import implementation files from another package.

Fix lib/src/client/client.dart. (-1 points)

Analysis of lib/src/client/client.dart reported 2 hints:

line 25 col 9: Close instances of dart.core.Sink.

line 81 col 28: This function has a return type of 'Stream<QueryResponse

Maintenance suggestions

The package description is too short. (-20 points)

Add more detail to the description field of pubspec.yaml. Use 60 to 180 characters to describe the package, what it does, and its target use case.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.7.0 <3.0.0
build ^1.2.2 1.2.2
built_collection ^4.3.2 4.3.2
code_builder ^3.2.1 3.2.1
collection ^1.14.11 1.14.11 1.14.12
flutter 0.0.0
gql ^0.12.0 0.12.2
gql_build ^0.0.10 0.0.10
gql_code_builder ^0.0.9 0.0.9
gql_exec ^0.2.0 0.2.2
gql_link ^0.2.2 0.2.3
hive ^1.3.0 1.4.1+1
meta ^1.1.8 1.1.8
normalize ^0.1.1 0.1.4
rxdart ^0.23.1 0.23.1 0.24.0-dev.1
Transitive dependencies
_fe_analyzer_shared 2.1.0
analyzer 0.39.6
args 1.6.0
async 2.4.1
built_value 7.0.9
charcode 1.1.3
convert 2.1.1
crypto 2.1.4
csslib 0.16.1
dart_style 1.3.4
fixnum 0.10.11
glob 1.2.0
html 0.14.0+3
js 0.6.1+1
logging 0.11.4
matcher 0.12.6
node_interop 1.0.3
node_io 1.0.1+2
package_config 1.9.3
path 1.6.4
pedantic 1.9.0
pub_semver 1.4.4
quiver 2.1.3
sky_engine 0.0.99
source_span 1.7.0
stack_trace 1.9.3
string_scanner 1.0.5
term_glyph 1.1.0
typed_data 1.1.6
vector_math 2.0.8
watcher 0.9.7+14
yaml 2.2.0
Dev dependencies
build_runner ^1.6.7
build_test ^0.10.12+1
mockito ^4.1.1
test ^1.9.4