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 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 #
- Code Builders (from
gql_build):- Create dart representations of all queries (including their variables, inputs, and data)
- Using the additional
req_builderincluded with this package, generate typedQueryRequestobjects which allow the client to parse typed responses.
- Client:
- Handles configuration
- Routes
QueryRequests to the cache or network, based on the givenFetchPolicy - Generates streams of
QueryResponses for a givenQueryRequest
- Link (from
gql_link): Handles GraphQL network requests - Cache:
- Normalizes and denormalizes data for queries and fragments (using the
normalizepackage) - 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 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.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],
),
);
},
),
);
}
}