You are on page 1of 11

6/21/2020 Offline first with Flutter - Flutter Community - Medium

You have 2 free member-only stories left this month. Sign up and get an extra one for free.

O ine rst with Flutter


Christian Muehle
Nov 19, 2019 · 7 min read

https://medium.com/flutter-community/offline-first-with-flutter-be1e8335d976 1/11
6/21/2020 Offline first with Flutter - Flutter Community - Medium

Photo by NASA on Unsplash

Welcome to the 21st century where 5G, hotspots and mobile internet is available
everywhere. It’s one of the things considered to be simply there, so you could ask
yourself:

Offline first apps - is this really needed?


That’s a perfect valid question to ask — but think about it for a second and you might
find yourself in the below examples and will agree it make sense.

Commute
You are in a high speed train or in the subway on an early monday morning (I know
you don’t want to be in this situation but…) and your public transport app is not
showing the next connection because you don't have a signal.

. . .

Travel
You made it — finally the time has come for your well-deserved holiday but
unfortunately you do not have a stable internet connection or it’s way too expensive.
No way to quickly check the map to get to that pizza place you checked out before.

. . .

Overload
70.000 other people are with you in a stadium watching the game of the year. And
with them at least 70.000 other phones in a relatively small spot. Goodbye, mobile
network...

. . .

Network coverage

https://medium.com/flutter-community/offline-first-with-flutter-be1e8335d976 2/11
6/21/2020 Offline first with Flutter - Flutter Community - Medium

Driving along the country side, hiking in a forest or simply going for walk. These are
situations that might get you into the zero network zone. I know quite a few spots
around here (in good old Germany…) that are like black holes, no mobile signal will
ever come out.

No connection? No way!

Giphy — https://gph.is/1KxOYf0

In the end it comes down to what your user expects — most of them would like to get
their content without any interruption. Processes started should offer a function to
finish them or content should allow changes — offline or online should not change
this.

Offline-first, it’s about states


Think about application states, authenticated vs. none authenticated. Writing data or
reading data are all application states. Why not adding a “missing connection” as a
normal state instead of an error?
Maybe you go a step further and simply say:

Offline is my default state, a connection is a nice


addition
https://medium.com/flutter-community/offline-first-with-flutter-be1e8335d976 3/11
6/21/2020 Offline first with Flutter - Flutter Community - Medium

L
et us divide data for your app into static and dynamic resources, everything that will
not change (or doesn’t directly influence your app’s behavior) is static — like a
picture.
Everything you process, the user types in is dynamic — like a comment for a post.
Static assets can/should be added during build time. All static assets should go to your
pubspec.yaml file, the complete process is described here. Keep in mind that a static
asset can be a picture, music or a text file (like JSON).

Dynamic resources can be handled in different ways — as always in life, it depends on


your needs. If it’s about simple files (like pictures or text) have a look at the cache
manager package. Do not waste time with reinventing the wheel ;)

1 var url =
2 'https://file-examples.com/wp-content/uploads/2017/10/file_example_PNG_500kB.png';
3
4 DefaultCacheManager().getFile(url).listen((f) {
5 setState(() {
6 fileInfo = f;
7 error = null;
8 });
9 }).onError((e) {
10 setState(() {
11 fileInfo = null;
12 error = e.toString();
13 });
14 });

cachemanagerExample.dart hosted with ❤ by GitHub view raw

The above super simple example will download the image only if the local cache is
empty or expired. Sure, if the image is required, it should be a static resource but I
would like to keep it simple for the example.

M ore complex data can be handled by SQLite, actually the above mentioned
cache manager uses SQLite to keep track of the cached files. I will not explain
how to use SQLite, have a look at the related repository. Nevertheless, I would provide
one little snippet that helped me a lot, handling database upgrades for local database
can be tricky sometimes.

1 class DBProvider {
2 DBProvider._();
3 static final DBProvider db = DBProvider._();

https://medium.com/flutter-community/offline-first-with-flutter-be1e8335d976 5/11
6/21/2020 Offline first with Flutter - Flutter Community - Medium

Some concepts shown here are for Flutter but the general idea works also for websites,
have a look at Google Docs, a perfect example of an offline capable website. Google
Maps also has a nice offline function, allowing you to download an area and use it
offline.

Let’s talk code

Giphy — https://gph.is/1Ko0IQ6

One short section before we look at code — let’s break our effort down into three
implementation steps:

1. Offline mode
Local cache / DB and general data storage

2. Online mode
Synchronization

3. On/Off UI pattern

Offline mode — local data


https://medium.com/flutter-community/offline-first-with-flutter-be1e8335d976 4/11
6/21/2020 Offline first with Flutter - Flutter Community - Medium
4 static final int version = 2;
5 static Database _database;
6
7 Future<Database> get database async {
8 if (_database != null) return _database;
9
10 // if _database is null we instantiate it
11 _database = await initDB();
12 return _database;
13 }
14
15 Future<bool> checkIfDbExists() async {
16 String path = join(await getDatabasesPath(), "OwlyTodo.db");
17 return new File(path).exists();
18 }
19
20 String _createTopicTableScript() {
21 return "CREATE TABLE Topic ("
22 "id TEXT PRIMARY KEY,"
23 "name TEXT,"
24 "color TEXT,"
25 "pinned BIT); ";
26 }
27
28 String _createTodoTopicTable() {
29 return "CREATE TABLE TodoTopic ("
30 "Id TEXT PRIMARY KEY,"
31 "TodoId TEXT,"
32 "TopicId TEXT)";
33 }
34
35 Future initDB() async {
36 String path = join(await getDatabasesPath(), "OwlyTodo.db");
37 return await openDatabase(path, version: version, onOpen: (db) {},
38 onCreate: (Database db, int version) async {
39 var dbBatch = db.batch();
40 dbBatch.execute("CREATE TABLE Todo ("
41 "id TEXT PRIMARY KEY,"
42 "title TEXT,"
43 "description TEXT,"
44 "done BIT,"
45 "dueDate INTEGER,"
46 "doneDate INTEGER"
47 "); ");
48 dbBatch.execute(_createTopicTableScript());
49 dbBatch.execute(_createTodoTopicTable());
50 await dbBatch.commit(noResult: true);
51 }, onUpgrade: (Database db, currentVersion, newVersion) async {
https://medium.com/flutter-community/offline-first-with-flutter-be1e8335d976 6/11
6/21/2020 Offline first with Flutter - Flutter Community - Medium
}, pg ( , , ) y {
52 final upgradeCalls = {
53 2: (Database db, Batch dbBatch) async {
54 dbBatch.execute(_createTopicTableScript());
55 dbBatch.execute(_createTodoTopicTable());
56 },
57 };
58 var dbBatch = db.batch();
59 upgradeCalls.forEach((vesion, call) async {
60 if (version > currentVersion) await call(db, dbBatch);
61 });
62 await dbBatch.commit(noResult: true);
63 });
64 }
65 }

The above code is a DB provider I use for some Flutter apps. Let’s quickly cover the
database upgrade part:

1. Requesting the database will create it one time due to the static property that is
shared between all instances (see the get database property)

2. If it’s the first time (_database == null), initDB() will be called

3. initDB() makes sure to generate the database and upgrade it if required

4. Creation and upgrade makes use of batch execution, all statements are run in one
request

5. Upgrades are stored in a Map<int,Future<Null> Function(Database, Batch)>


this might look complicated but actually it’s super simple — later more

6. During update all elements of the map will be added to the batch (in the right
order) and executed

The map mentioned in step 5 can be seen as a book — the first element is the page
number (as int) and the second element the content of the page. If you would start
reading a book somewhere in the middle you most likely have no clue what’s going on.
If you continue to read page by page from where you left (in our case the
currentVersion) the story will make sense.
Our database get’s updates from the currentVersion up to the latest available, all
executed in the right order in a single batch operation.

UPDATE — Some fresh new ideas


https://medium.com/flutter-community/offline-first-with-flutter-be1e8335d976 7/11
6/21/2020 Offline first with Flutter - Flutter Community - Medium

Since I wrote this article I checked out a lot of other local storage solutions. Have a look
at the linked article, moor is a nice package to assist you with offline first storage.

Flutter package at a glance — local storage with Moor


Welcome to the rst episode of posts called “Flutter packages at a glance”!
This time it’s all about o ine storage…
medium.com

Round 2: Online mode, ready ?

Giphy — https://gph.is/2HPEMQz

https://medium.com/flutter-community/offline-first-with-flutter-be1e8335d976 8/11
6/21/2020 Offline first with Flutter - Flutter Community - Medium

There are a billion options on how you can do this, as pointed out earlier this depends
on your needs. I can just hand over some advises learned over the years, all our mobile
apps have to be offline first, all of them have a sync build in.

If you can, use an already build sync service — do net reinvent the wheel

Please do not use integers for primary keys, instead use UUID offered by this super
simple package, otherwise you might find yourself in the unlucky case of creating
integers in given ranges for given clients. UUID’s are always unique¹

Keep the exchanged data as small as possible, implement a change flag or last
change date. Setup a data contract for all your data — this contract should describe
the minimum required for all data (ID, last change), only send out data newer as
the last sync

Store timestamps in UTC to don’t end up in the timezone hell. Consider saving
them as plain numeric values, this works for any kind of backend

Keep the mobile side of the API as dumb as possible, if you have a bug in the app, a
fix might take 2 days before it reaches your customer. Fixing a server side bug
should be much faster

Think about your API architecture twice, over/under fetching data is an issue of
REST, a possible solution would be GraphQL

¹ The probability to find a duplicate within 103 trillion version-4 UUIDs is one in a billion.
From Wikipedia

Last round: Offline first UX

M ake sure your content offers interaction, even if you end up with “Sorry, no
network, try later”, you still get further as hiding parts of your app.
Ensure that the workflows are the same, offline or online, there should be no
difference.
In case you can’t handle a request, please provide a meaningful error message.
Messages like the ones below are useless:

An error occurred

Internal error

https://medium.com/flutter-community/offline-first-with-flutter-be1e8335d976 9/11
6/21/2020 Offline first with Flutter - Flutter Community - Medium

No response

Try something like: “Sorry, you seem to be offline. The current request can’t be
completed.” If you have a certain function that will require a connection in any case,
check the current connection state and disable the button if you are offline.
Pro mode — leave the button active but inform the user after he clicked that this can’t
be done right now.
Think about your empty states, showing a big white rectangle because a list view is
empty doesn’t help you. Include a nice image and a short text that get’s the user
started/informed. Asking a user to confirm an action that will require a network
connection can also be a helpful tool in case you need to perform a longer running
operation.

Please, never ever clear your cache before checking


the connection state. In the worst case you will end
up with an empty app in the middle of nowhere.
Conclusion
Offline first, done right, get’s you the best of two worlds. Your users can use the app at
anytime and have no fear of loosing their data. Thanks to caches and local data you
should be able to deliver content quickly and reliable. Sure, it is an extra step of work
for you but it might also be the extra feature you can offer but others not.

How about you? Did you ever made an offline-first app? I would love to see some
examples or any experience in the comments :)

Thanks for reading.

Flutter Community
The latest Tweets from Flutter Community (@FlutterComm). Follow to get
noti cations of new articles and packages from…

www.twitter.com

https://medium.com/flutter-community/offline-first-with-flutter-be1e8335d976 10/11
6/21/2020 Offline first with Flutter - Flutter Community - Medium

iOS Android Programming Technology Flutter

About Help Legal

Get the Medium app

https://medium.com/flutter-community/offline-first-with-flutter-be1e8335d976 11/11

You might also like