Flutter

Implementing the offline first database using the GraphQL API

Introduction

Since you are here, you must have gone through the rest of the tutorials and are familiar with executing GraphQL queries and mutations to fetch and mutate the data. In this docs, we are going to explore how to implement an offline first user interface with Flutter and GraphQL.

Goals

  • Understand internal architecture of the flutter graphql offline client
  • Allowing application to run graphql queries even though application is offline
  • Implement Offline data persistence

Prerequisites

  • We require that the user has some basic understanding of Dart and Flutter.
  • Though not necessary, the GraphQL cookbook will be useful in understanding some of the GraphQL concepts.
  • We require that you have completed the prerequisite topic Flutter Graphql Setup and have previous code setup and back4app backend implemented.

Step 1: Setting up offline cache

Flutter GraphQl client supports “offline queries” by default, that is it will not throw errors if we query some GraphQL data when offline and would fetch the data from cache.

We have to note that this is different from persisting the cache across app sessions and specifically the flutter graphql client does not have cache persistence to disk. So if the app closed from the system tray and reopened the data would still need to be fetched.

To enable the same we have to enable the offline cache:

Go to main.dart:

1
2
3
4
5
6
7
8
return MaterialApp(
      home: GraphQLProvider(
        child: CacheProvider(   // cache provider widget that provides the offline queries support.
          child: MyHomePage(),
        ),
        client: client,
      ),
    );

Step 2: Setting up stored preferences

One caveat while using the flutter-graphql client is that it does not store any cache of its own when the application is closed, nor does it hydrate the cache when the application is opened again.

For implementing the same we would be leveraging the flutter shared_prefrencces library. It wraps platform-specific persistent storage for simple data (NSUserDefaults on iOS and macOS, SharedPreferences on Android, etc.), essentially allowing to store data offline in a very simple manner.

For installing the library please add in the pubspec.yml file

1
 shared_preferences: ^0.5.12+4

In main.dart add the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import 'package:shared_preferences/shared_preferences.dart';

class SharedPreferencesHelper {
  static final String _offline_cache_key = 'programmingLanguageListResponse';

  static Future<ProgrammingLanguageList> getCache() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final cache = prefs.getString(_offline_cache_key);
    final offlineData =
        cache != null ? programmingLanguageListFromJson(cache) : null;

    return offlineData;
  }

  static Future<bool> setCache(dynamic value) async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();

    return prefs.setString(_offline_cache_key, jsonEncode(value));
  }
}

Shared Preferences library stores data in a key-value form where value gets stringified into a JSON string. We will need to parse this data to our data model.

Step 3: Parsing the locally stored data

We will create a new file called programing_languages_model.dart. Which will store the parsing logic. We will generate this logic by pasting our graphql response in the JSON to dart model converter at https://app.quicktype.io/

We will copy the generated code and create a file programing_languages_model.dart https://github.com/templates-back4app/Flutter-GraphQL/blob/flutter-graphql-offline/lib/programing_languages_model.dart

Step 4: Integrating Offline Storage logic

If the data does not exist we would be using the data from shared preferences. If the data is also not in the shared preferences we would simply show a loading icon.

We will now implement changes to integrate all the changes together, in the build method of our _MyHomePageState we would change our build method. We would use the FutureBuilder widget to consume data from the SharedPreferencesHelper class.

1
2
3
4
5
6
7
return FutureBuilder<ProgrammingLanguageList>(
        future: SharedPreferencesHelper.getCache(),
        builder: (prefs, snapshot) {
          final offlineData = snapshot.data;
          if (!snapshot.hasError) {
            return SafeArea(
               .

Using the FutureBuilder widget allows us to write code without having to use state. It is a relatively quick process to get the data from shared preferences. We could also show a loader while we are initialising the shared preferences and are getting data from an offline store.

We now use this offline data object and render while data from GraphQL is not available. We will also refactor the code a little bit. Following will be our code for the Query https://github.com/templates-back4app/Flutter-GraphQL/blob/flutter-graphql-offline/lib/main.dart widget.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
   body: Query(
                  options: QueryOptions(
                    documentNode: gql(query),
                  ),
                  builder: (
                    QueryResult result, {
                    Refetch refetch,
                    FetchMore fetchMore,
                  }) {
                    final data = result.data == null
                        ? offlineData
                        : programmingLanguageListFromJson(
                            jsonEncode(result.data));
                    if (data == null) {
                      return Center(
                          child: Text(
                        "Loading...",
                        style: TextStyle(fontSize: 20.0),
                      ));
                    } else {
                      SharedPreferencesHelper.setCache(data);
                      return ListView.builder(
                        itemBuilder: (BuildContext context, int index) {
                          if (index == 0) {
                            return Center(
                              child: RaisedButton(
                                onPressed: refetch,
                                child: result.loading == true
                                    ? Text("Loading...")
                                    : Text("Refetch"),
                              ),
                            );
                          }
                          return ListTile(
                            title: Text(data.programmingLanguages
                                .edges[index - 1].node.name),
                            trailing: Text(data.programmingLanguages
                                    .edges[index - 1].node.stronglyTyped
                                ? "Strongly Typed"
                                : "Weekly Typed"),
                          );
                        },
                        itemCount: data.programmingLanguages.edges.length + 1,
                      );
                    }
                  },
                ),
              ),

We should get the following:

Flutter Graphql Offline

Conclusion

We are now able to ensure a very good mobile experience by storing the data offline and revalidating the data when the application gets connected to the internet. Also, one important aspect that is enhancing the user experience is that the flutter-graphql client caches the old response and while sending a new request automatically. Because of which we don’t have to keep showing clumsy loading screens, while re-fetching data.

The code for the article is available at: https://github.com/templates-back4app/Flutter-GraphQL/tree/flutter-graphql-offline