ferry 0.3.1+1
ferry: ^0.3.1+1 copied to clipboard
GraphQL Client for Dart
Stream Based GraphQL Client for Dart
Warning: This library is still a work in progress. The API may change.
Features #
Feature | Progress |
---|---|
Generated Fully Typed Queries and Resposnes (using gql_build ) |
✅ |
Customizable Link s (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 #
- Code Builders (from
gql_build
):- Create dart representations of all queries (including their variables, inputs, and data)
- Using the additional
req_builder
included with this package, generate typedQueryRequest
objects which allow the client to parse typed responses.
- Client:
- Handles configuration
- Routes
QueryRequest
s to the cache or network, based on the givenFetchPolicy
- Generates streams of
QueryResponse
s for a givenQueryRequest
- Link (from
gql_link
): Handles GraphQL network requests - Cache:
- Normalizes and denormalizes data for queries and fragments (using the
normalize
package) - Maintains a collection of Optimistic Patches and handles optimistic reads and writes
- Normalizes and denormalizes data for queries and fragments (using the
- 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 QueryRequest
s 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.source != ResponseSource.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,
) {
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],
),
);
},
),
);
}
}