Fetching data from the server into flutter app using GraphQL

Introduction to GraphQL


Till now developers relied on REST API for fetching the data from the server, and it works fine though some of the limitations are there in using REST API like there is a limitation of flexibility and efficiency.


GraphQL overcomes this limitation of REST API. GraphQL is an open-source data query and manipulation language for the API's, and a runtime for fulfilling queries with existing data. GraphQL is developed by Facebook and was released initially in 2015.

We fetch the data using the REST API by using the "Base URL + Endpoints", For Example in http://www.baseurl.com/endpoint, the specific endpoints are used for fetching some data, in such case although if we require only some of the data from a server the endpoints fetches full data that the endpoints are configured of, so there are chances of over or under fetching of the data using REST API.

In GraphQL this can be overcome by asking the server to send only the required data and not the full data.

Integrating GRAPHQL into Flutter app


To Add/Install the graphql library in flutter app, please open the "pubspec.yaml" from flutter project structure and add the Graphql pub below the dependencies section :


dependencies:
  graphql_flutter: ^3.0.0

Execute the command "packages get" or "packages update" to fetch the GraphQL dart package from the development server.

Now you are ready to use GraphQL in your flutter app!

"graphql_flutter" pub provides a Widget level implementation for Graph Query, to get the details of this pub or its version log, please visit to GRAPHQL FLUTTER PUB

There are two kinds of graphql functions i.e "GRAPHQL MUTATION" and "GRAPHQL QUERY",  GraphQL do not have any endpoints, and just a single api URL, rest of things are handled by Mutation or Query.

MUTATION


Mutation in graphQL is almost as of "POST" of REST API, it is generally used to write data on the server. The Mutation query modifies data in the data store and returns a value. It can be used to insert, update, or delete data.

QUERY


Query in graphQL is almost as of "GET" of REST API, it is generally used to read data from the server.

Implementation


To use the graphQL into flutter app, one needs to import the following file into the dart code :

import 'package:graphql_flutter/graphql_flutter.dart';

To use the client it first needs to be initialized with a link and cache. For this example, we will be using an HttpLink as our link and InMemoryCache as our cache.

If your endpoint requires authentication you can concatenate the AuthLink, it resolves the credentials using a "Future" variable. (A Future variable is used to represent a potential value, or error, that will be available in the future. Receivers of a Future can register callbacks that handle the value or error once it is available), so you can authenticate asynchronously.

Sample example of GraphQL usage with using Github public API.

...

import 'package:graphql_flutter/graphql_flutter.dart';

void main() {
final HttpLink httpLink = HttpLink(
uri: 'https://api.github.com/graphql',
);

final AuthLink authLink = AuthLink(
getToken: () async => 'Bearer <YOUR_PERSONAL_ACCESS_TOKEN>',
// OR// getToken: () => 'Bearer <YOUR_PERSONAL_ACCESS_TOKEN>',);

final Link link = authLink.concat(httpLink);

ValueNotifier<GraphQLClient> client = ValueNotifier(
GraphQLClient(
cache: InMemoryCache(),
link: link,
),
);

...
}

...

GRAPHQL Provider


To use the client's query widget or mutation widget one needs to wrap those widgets into GRAPHQL provider widget:

...

return GraphQLProvider(
client: client,
child: MaterialApp(
title: 'Flutter Demo',
...
),
);

...

Mutation Implementation


First create MUTATION String :

String addStar = """
  mutation AddStar(\$starrableId: ID!) {
    addStar(input: {starrableId: \$starrableId}) {
      starrable {
        viewerHasStarred
      }
    }
  }
""";


To trigger the mutation with above mutation string :

Mutation(
  options: MutationOptions(
    documentNode: gql(addStar), // this is the mutation string you just created
    // you can update the cache based on results
    update: (Cache cache, QueryResult result) {
      return cache;
    },
    // or do something with the result.data on completion
    onCompleted: (dynamic resultData) {
      print(resultData);
    },
  ),
  builder: (
      RunMutation runMutation,
      QueryResult result,
      ) {
    return FloatingActionButton(
      onPressed: () => runMutation({
      'starrableId': ,
      }),
      tooltip: 'Star',
      child: Icon(Icons.star),
    );
  },
);
 


Complete dart file with mutation example :


// bool get starred => repository['viewerHasStarred'] as bool;
// bool get optimistic => (repository as LazyCacheMap).isOptimistic;
        Mutation(
          options: MutationOptions(
            documentNode: gql(starred ? mutations.removeStar : mutations.addStar),
            // will be called for both optimistic and final results
            update: (Cache cache, QueryResult result) {
              if (result.hasException) {
                print(['optimistic', result.exception.toString()]);
              } else {
                final Map updated =
                Map.from(repository)
                  ..addAll(extractRepositoryData(result.data));
                cache.write(typenameDataIdFromObject(updated), updated);
              }
            },
            // will only be called for final result
            onCompleted: (dynamic resultData) {
              showDialog(
                context: context,
                builder: (BuildContext context) {
                  return AlertDialog(
                    title: Text(
                      extractRepositoryData(resultData)['viewerHasStarred'] as     bool
                          ? 'Thanks for your star!'
                          : 'Sorry you changed your mind!',
                    ),
                    actions: [
                      SimpleDialogOption(
                        child: const Text('Dismiss'),
                        onPressed: () {
                          Navigator.of(context).pop();
                        },
                      )
                    ],
                  );
                },
              );
            },
          ),
          builder: (RunMutation toggleStar, QueryResult result) {
            return ListTile(
              leading: starred
                  ? const Icon(
                Icons.star,
                color: Colors.amber,
              )
                  : const Icon(Icons.star_border),
              trailing: result.loading || optimistic
                  ? const CircularProgressIndicator()
                  : null,
              title: Text(repository['name'] as String),
              onTap: () {
                toggleStar(
                  { 'starrableId': repository['id'] },
                  optimisticResult: {
                    'action': {
                      'starrable': {'viewerHasStarred': !starred}
                    }
                  },
                );
              },
            );
          },
        );



Query Implementation

First create QUERY String :

String readRepositories = """
  query ReadRepositories(\$nRepositories: Int!) {
    viewer {
      repositories(last: \$nRepositories) {
        nodes {
          id
          name
          viewerHasStarred
        }
      }
    }
  }
""";

In your widget:

// ...
        Query(
          options: QueryOptions(
            documentNode: gql(readRepositories), // this is the query string you just created
            variables: {
              'nRepositories': 50,
            },
            pollInterval: 10,
          ),
          // Just like in apollo refetch() could be used to manually trigger a refetch
          // while fetchMore() can be used for pagination purpose
          builder: (QueryResult result, { VoidCallback refetch, FetchMore fetchMore }) {
            if (result.hasException) {
              return Text(result.exception.toString());
            }

            if (result.loading) {
              return Text('Loading');
            }

            // it can be either Map or List
            List repositories = result.data['viewer']['repositories']['nodes'];

            return ListView.builder(
                itemCount: repositories.length,
                itemBuilder: (context, index) {
                  final repository = repositories[index];

                  return Text(repository['name']);
                });
          },
        );
// ...



Here only the data that is requested in the above graphQL query only that will be returned in the JSON response i.e like below



{
   "data": {
      "nodes ": [
         {
            "id": "S1001",
            "name": "Darshit Chokshi",
            "viewerHasStarred": true
         },
         
         {
            "id": "S1002",
            "name": "John Doe"
            "viewerHasStarred": false
         },
         
         {
            "id": "S1003",
            "name": "Will Power"
            "viewerHasStarred": true
         }
      ]
   }
}


Comments