GraphQL brings many benefits, both to the client: devices will need less requests, and therefore reduce data useage. And to the programer: requests are arguable, they have the same structure as the request.
The team at Apollo did a great job implenting GraphQL in Swift, Java and Javascript. But unfortunately they're not planning to release a Dart implementation.
This project is filling the gap, bringing the GraphQL spec to yet another programming language. We plan to implement most functionality from the Apollo GraphQL client and from most features the React Apollo components into Dart and Flutter respectively.
With that being said, the project lives currently still inside one package. We plan to spilt up the project into multiple smaler packages in the near future, to follow Apollo's modules design.
First depend on the library by adding this to your packages pubspec.yaml
:
dependencies:
graphql_flutter: ^0.8.0
Now inside your Dart code you can import it.
import 'package:graphql_flutter/graphql_flutter.dart';
To use the client it first needs to be initialized with an endpoint and cache. If your endpoint requires authentication you can provide it to the client contructor. If you need to change the api token at a later stage, you can call the setter apiToken
on the Client
class.
For this example we will use the public GitHub API.
...
import 'package:graphql_flutter/graphql_flutter.dart';
void main() {
ValueNotifier<Client> client = ValueNotifier(
Client(
endPoint: 'https://api.github.com/graphql',
cache: InMemoryCache(),
apiToken: '<YOUR_GITHUB_PERSONAL_ACCESS_TOKEN>',
),
);
...
}
...
In order to use the client, you app needs to be wrapped with the GraphqlProvider
widget.
...
return GraphqlProvider(
client: client,
child: MaterialApp(
title: 'Flutter Demo',
...
),
);
...
Creating a query is as simple as creating a multiline string:
String readRepositories = """
query ReadRepositories(\$nRepositories) {
viewer {
repositories(last: \$nRepositories) {
nodes {
id
name
viewerHasStarred
}
}
}
}
"""
.replaceAll('\n', ' ');
In your widget:
...
Query(
readRepositories, // this is the query you just created
variables: {
'nRepositories': 50,
},
pollInterval: 10, // optional
builder: ({
bool loading,
var data,
String error,
}) {
if (error != '') {
return Text(error);
}
if (loading) {
return Text('Loading');
}
// it can be either Map or List
List repositories = data['viewer']['repositories']['nodes'];
return ListView.builder(
itemCount: repositories.length,
itemBuilder: (context, index) {
final repository = repositories[index];
return Text(repository['name']);
});
},
);
...
Again first create a mutation string:
String addStar = """
mutation AddStar(\$starrableId: ID!) {
addStar(input: {starrableId: \$starrableId}) {
starrable {
viewerHasStarred
}
}
}
"""
.replaceAll('\n', ' ');
The syntax for mutations is fairly similar to that of a query. The only diffence is that the first argument of the builder function is a mutation function. Just call it to trigger the mutations (Yeah we deliberately stole this from react-apollo.)
...
Mutation(
addStar,
builder: (
runMutation, { // you can name it whatever you like
bool loading,
var data,
String error,
}) {
return FloatingActionButton(
onPressed: () => runMutation({
'starrableId': <A_STARTABLE_REPOSITORY_ID>,
}),
tooltip: 'Star',
child: Icon(Icons.star),
);
},
onCompleted: (Map<String, dynamic> data) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Thanks for your star!'),
actions: <Widget>[
SimpleDialogOption(
child: Text('Dismiss'),
onPressed: () {
Navigator.of(context).pop();
},
)
],
);
}
);
}),
...
The syntax for subscriptions is again similar to a query, however, this utilizes WebSockets and dart Streams to provide real-time updates from a server.
Before subscriptions can be performed a global intance of socketClient
needs to be initialized.
We are working on moving this into the same
GraphqlProvider
stucture as the http client. Therefore this api might change in the near future.
socketClient = await SocketClient.connect('ws://coolserver.com/graphql');
Once the socketClient
has been initialized it can be used by the Subscription
Widget
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Subscription(
operationName,
query,
variables: variables,
builder: ({
bool loading,
dynamic payload,
dynamic error,
}) {
if (payload != null) {
return Text(payload['requestSubscription']['requestData']);
} else {
return Text('Data not found');
}
}
),
)
);
}
}
Once the socketClient
is initialized you could also use it without Flutter.
final String operationName = "SubscriptionQuery";
final String query = """subscription $operationName(\$requestId: String!) {
requestSubscription(requestId: \$requestId) {
requestData
}
}""";
final dynamic variables = {
'requestId': 'My Request',
};
socketClient
.subscribe(SubscriptionRequest(operationName, query, variables))
.listen(print);
You can always access the client direcly from the GraphqlProvider
but to make it even easier you can also use the GraphqlConsumer
widget.
...
return GraphqlConsumer(
builder: (Client client) {
// do something with the client
return Container(
child: Text('Hello world'),
);
},
);
...
The in-memory cache can automatically be saved to and restored from offline storage. Setting it up is as easy as wrapping your app with the CacheProvider
widget.
Make sure the
CacheProvider
widget is inside theGraphqlProvider
widget.
...
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GraphqlProvider(
client: client,
child: CacheProvider(
child: MaterialApp(
title: 'Flutter Demo',
...
),
),
);
}
}
...
This is currently our roadmap, please feel free to request additions/changes.
Feature | Progress |
---|---|
Queries | ✅ |
Mutations | ✅ |
Subscriptions | ✅ |
Query polling | ✅ |
In memory cache | ✅ |
Offline cache sync | ✅ |
Optimistic results | 🔜 |
Client state management | 🔜 |
Modularity | 🔜 |
Feel free to open a PR with any suggestions! We'll be actively working on the library ourselves.
This package was originally created and published by the engineers at Zino App B.V.. Since then the community has helped to make it even more useful for even more developers.
Thanks goes to these wonderful people (emoji key):
Eustatiu Dima 🐛 💻 📖 💡 🤔 👀 |
Zino Hofmann 🐛 💻 📖 💡 🤔 🚇 👀 |
Harkirat Saluja 📖 🤔 |
Chris Muthig 💻 📖 💡 🤔 |
Cal Pratt 🐛 💻 📖 💡 🤔 |
Miroslav Valkovic-Madjer 💻 |
Aleksandar Faraj 🐛 |
---|
This project follows the all-contributors specification. Contributions of any kind are welcome!