summaryrefslogtreecommitdiffstats
path: root/documentation/articles/OfflineModeForTouchKit4MobileApps.asciidoc
blob: e215c0921bb73d6b34ebc39b2014b6abe2a90e64 (plain)
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
[[offline-mode-for-touchkit-4-mobile-apps]]
Offline mode for TouchKit 4 mobile apps
---------------------------------------

[.underline]#*_Note:_* _Vaadin Touchkit has been discontinued. A community-supported version is
available https://github.com/parttio/touchkit[on GitHub]._#

[[background]]
Background
~~~~~~~~~~

Vaadin is primarily a server-side framework. What happens with the
application when the server is not available? Although this is possible
on desktop computers, more often it happens when using a mobile device.
This is why Vaadin TouchKit allows
you to define offline behavior. In this article I will tell you all the
details you need to know about offline mode and how to use it. It is
written based on Vaadin 7.3 and TouchKit 4.0.0.

Touchkit is a Vaadin
addon that helps in developing mobile applications. I assume that you
have some knowledge in Vaadin and how to develop client-side Vaadin
(GWT) code. I will mention the http://demo.vaadin.com/parking/[Parking
demo] here a few times and you can find its sources
https://github.com/vaadin/parking-demo[here]. I suggest that you read
this article before you try to understand the Parking demo source code,
it will help you grasp the concepts demonstrated in the demo.

[[demystifying-offline-mode]]
Demystifying offline mode
~~~~~~~~~~~~~~~~~~~~~~~~~

As said before, Vaadin is a server-side framework and that implies that
when an application is running, there is a lot of communication going on
between the server and the client. Thus server-side views are not
accessible when there is no connection. On the other hand, offline
enabled applications run pure client-side Vaadin (GWT) code without
connecting the server.

There are a couple of approaches you might take to specify offline
behavior on the client-side.

1.  Write a fully client-side application for the user to interact with
when the server is offline.
2.  Write some views as client-side widgets and, in case the connection
is lost, disable all the components that might need a server connection.

Let’s take a look at the technical details you need to know.

[[client-side-offline-mode-handling---method-1-checking-the-status]]
Client-side offline mode handling - method 1: checking the status
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The simplest way to know if the application is online or offline is to
use this code:

[source,java]
....
OfflineModeEntrypoint.get().getNetworkStatus().isAppOnline()
....

You might use it before sending something to the server or calling an
RPC, for example. However, the network status might change at any time.
Method 2 helps you react to those changes.

[[client-side-offline-mode-handling---method-2-handling-events]]
Client-side offline mode handling - method 2: handling events
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

In order to use this method you need an `ApplicationConnection` instance.
We are going to use its event bus to handle online/offline events.
Usually you get an `ApplicationConnection` instance from a component
connector. Here is an example:

[source,java]
....
@Connect(MyComponent.class)
public class MyConnector extends AbstractComponentConnector {
  @Override
  protected void init() {
    super.init();

    getConnection().addHandler(OnlineEvent.TYPE, new OnlineEvent.OnlineHandler() {
      @Override
      public void onOnline(final OnlineEvent event) {
      // do some stuff
      }
    });

    getConnection().addHandler(OfflineEvent.TYPE, new OfflineEvent.OfflineHandler() {
      @Override
      public void onOffline(final OfflineEvent event) {
      // do some stuff
      }
    });
  }
}
....

Note that this connector will only be created if an instance of
`MyComponent` is created on the server side and attached to the UI. As an
option, it might be a `UI` or `Component` extension connector. Otherwise
your connector will never be instantiated and you will never receive
these events, so you can rely on them only if you want to show some
changes in the view or disable some functionality of a view when
offline. In order to get true offline capabilities, use method 3.

[[client-side-offline-mode-handling---method-3-implementing-offlinemode-interface]]
Client-side offline mode handling - method 3: implementing OfflineMode interface
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Implementing client-side OfflineMode interface allows you to specify
true offline-mode behavior: you will receive events also in case the
page is loaded from cache without network connection at all.

Fortunately, there is a default implementation and you don’t need to
worry about the implementation details. `DefaultOfflineMode` provides an
OfflineMode implementation for any TouchKit application. It shows a
loading indicator and a sad face when the network is down. In most cases
all you want to do is replace this sad face with something more useful
(for example Minesweeper or Sudoku), here’s a sample:

[source,java]
....
public class MyOfflineMode extends DefaultOfflineMode {
  @Override
  protected void buildDefaultContent() {
    getPanel().clear();
    getPanel().add(createOfflineApplication()); // might be a full blown GWT UI
  }
}
....

Then you need to specify the implementation in your widgetset definition
file (*.gwt.xml):

[source,xml]
....
<replace-with class="com.mybestapp.widgetset.client.MyOfflineMode">
  <when-type-is class="com.vaadin.addon.touchkit.gwt.client.offlinemode.OfflineMode" />
</replace-with>
....

This is enough for showing an offline UI, it will be shown and hidden
automatically, `DefaultOfflineMode` will take care of this. If you need a
more complex functionality, like doing something when going
offline/online, you might want to override additional methods from
`DefaultOfflineMode` or implement OfflineMode from scratch. I briefly
sketch what you need to know about it.

The OfflineMode interface has three methods:

[source,java]
....
void activate(ActivationReason);
boolean deactivate();
boolean isActive();
....

Pretty clear, but there are some pitfalls.

Counterintuitively, not all `ActivationReason`{empty}(s) actually require
activating the offline application view. On
`ActivationReason.APP_STARTING` you can just show a loading indicator and
on `ActivationReason.ONLINE_APP_NOT_STARTED` you might want to display a
reload button or actually hide the offline view. Take a look at the
`DefaultOfflineMode` implementation and the `TicketViewWidget` in the
Parking demo.

Second thing to note: `deactivate()` will never be called if i`sActive()`
returns `false`. So you must track whether the offline mode is active or
just take a shortcut like this:

[source,java]
....
boolean isActive() {
  return true;
}
....

And the last one: regardless of what JavaDoc says, the return value of
the `deactivate()` method is ignored. You might want to check if this
changes in future versions.

Note that this client-side
http://demo.vaadin.com/javadoc/com.vaadin.addon/vaadin-touchkit-agpl/4.0.0/com/vaadin/addon/touchkit/gwt/client/offlinemode/OfflineMode.html[com.vaadin.addon.touchkit.gwt.client.offlinemode.OfflineMode]
interface has nothing to do with server-side extension
http://demo.vaadin.com/javadoc/com.vaadin.addon/vaadin-touchkit-agpl/4.0.0/com/vaadin/addon/touchkit/extensions/OfflineMode.html[com.vaadin.addon.touchkit.extensions.OfflineMode]
class (unfortunate naming).

[[setting-up-the-offline-mode]]
Setting up the offline mode
~~~~~~~~~~~~~~~~~~~~~~~~~~~

You can turn a Vaadin application into an offline-enabled TouchKit
application by using an extension of `TouchKitServlet` as your servlet
class. For example, the following might be your servlet declaration in
your UI class:

[source,java]
....
@WebServlet(value = "/*")
public static class Servlet extends TouchKitServlet /* instead of VaadinServlet */ {}
....

Below are some details that you might need at some point (or have read
about in other places and are wondering what they are). You may skip to
the “Synchronizing data between server and client” section if you just
want a quick start.

You can check network status (method 1) in any TouchKit application
(i.e. any application using `TouchKitServlet`), nothing special is
required.

In order to use the application connection event bus (method 2), offline
mode must be enabled or no events will be sent. As of TouchKit 4, it is
enabled by default whenever you use TouchKit. If for some reason you
want offline mode disabled, annotate your UI class with
`@OfflineModeEnabled(false)`. Although this is not recommended in TouchKit
applications, because no message will be shown if the app goes offline,
not even the standard Vaadin message.

For method 3 (implementing the OfflineMode interface), besides enabling
offline mode, the
http://en.wikipedia.org/wiki/Cache_manifest_in_HTML5[HTML5 cache
manifest] should be enabled. The cache manifest tells the browser to
cache some files, so that they can be used without a network connection.
As with the offline mode, it is enabled by default. If you want it
disabled, annotate your UI class with  `@CacheManifestEnabled(false)`.
That way your application might be fully functional once starting online
and then going offline (if it does not need any additional files when
offline), but will not be able to start when there is no connection.

[[caching-additional-files-for-example-a-custom-theme]]
Caching additional files, for example a custom theme
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

If you need some additional files to be cached for offline loading (most
likely your custom theme), you can add this property to your *.gwt.xml
file:

[source,xml]
....
<set-configuration-property
    name='touchkit.manifestlinker.additionalCacheRoot'
    value='path/relative/to/project/root:path/on/the/server' />
....

Only files having these extensions will be added to the cache manifest:
.html, .js, .css, .png, .jpg, .gif, .ico, .woff);

If this is a directory, it will be scanned recursively and all the files
with these extensions will be added to the manifest.

[[offlinemode-extension]]
OfflineMode extension
^^^^^^^^^^^^^^^^^^^^^

In addition, you can slightly tweak the offline mode through the
OfflineMode UI extension.

You can set offline mode timeout (if there’s no response from the server
during this time, offline mode will be activated), or manually set
application mode to offline/online (useful for development). There’s
also a less useful parameter: enable/disable persistent session cookie
(enabled by default if you use `@PreserveOnRefresh`, which you should do
for offline mode anyways). That’s all there is in this extension. Usage:

[source,java]
....
// somewhere among UI initializaion
OfflineMode offline = new OfflineMode();
offline.extend(this);
offlineModeSettings.setOfflineModeTimeout(5);
....

Note: it is not compulsory to use this extension, but it helps the
client side of the Touchkit add-on to find the application connection.
Without it, it tries to get an application connection for 5 seconds. If
you suspect that your connection is too slow or the server is very slow
to respond, you might add a new `OfflineMode().extend(this);` to your UI
just in case. That should be very rarely needed.

This extension is usually used for synchronizing data between the server
and the client (covered in the next section), but it can be done through
any other extension/component -- there is no special support for it in
OfflineMode extension.

[[synchronizing-data-between-server-and-client]]
Synchronizing data between server and client
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

In a sense, the client is always in “offline mode” between requests from
the server point of view. Therefore the regular Vaadin way of
synchronizing data between the client-side widget and the server-side
(https://vaadin.com/book/-/page/gwt.rpc.html[Vaadin RPC mechanism] and
https://vaadin.com/book/-/page/gwt.shared-state.html[shared state]) is
still valid, the difference being that the offline widget is probably
more complex and the amount of data is greater than that of an average
component.

As mentioned, the server is not necessarily aware that the client went
offline for some time, therefore the synchronization should be initiated
from the client side. So using method 2 or 3, the client side gets an
event that the connection is online and it sends an RPC call to the
server. New data might be sent with the notification or asked
separately, e.g. using
http://demo.vaadin.com/javadoc/com.vaadin.addon/vaadin-touchkit-agpl/4.0.0/index.html?com/vaadin/addon/touchkit/extensions/LocalStorage.html[LocalStorage]
(TouchKit provides easy access to
http://www.w3schools.com/html/html5_webstorage.asp[HTML5 LocalStorage]
from the server side). The server might send new data through shared
state.

If we reuse OfflineMode (mentioned in the end of the last section), the
code might look like this:

[source,java]
....
public class MyOfflineModeExtension extends OfflineMode {
  public MyOfflineModeExtension() {
    registerRpc(serverRpc);
  }

  private final SyncDataServerRpc serverRpc = new SyncDataServerRpc() {
    @Override
    public void syncData(final Object newData) {
      doSmth(newData); // update data
      getState().someProperty = newServerData; // new data from the server to the client
    }
  };
}

@Connect(MyOfflineModeExtension.class)
public class MyOfflineConnector extends OfflineModeConnector {
  private final SyncDataServerRpc rpc = RpcProxy.create(SyncDataServerRpc.class, this);

  @Override
  protected void init() {
    super.init();

    getConnection().addHandler(OnlineEvent.TYPE, new OnlineEvent.OnlineHandler() {
      @Override
      public void onOnline(final OnlineEvent event) {
        Object new Data = … // get updated data
        rpc.syncData(newData);
      }
    });
  }
}
....

As already said, this does not necessarily have to be done through the
OfflineMode extension, it can be done using any component connector,
there is nothing special about OfflineMode.

Another option, a less wordy and more decoupled one, could be done by
using JavaScript function call.

On the server side:

[source,java]
....
JavaScript.getCurrent().addFunction("myapp.syncData",
    (args) -> { /*sync data, e.g. get it from LocalStorage */});
....

On the client side:

[source,java]
....
// in any connector
getConnection().addHandler(OnlineEvent.TYPE, new OnlineEvent.OnlineHandler() {
  @Override
  public native void onOnline(final OnlineEvent event) /*-{
    myapp.syncData();
  }-*/;
});
....

Or similar code in client-side OfflineMode implementation:

[source,java]
....
MyOfflineMode extends DefaultOfflineMode {
  @Override
  public native boolean deactivate() /*-{
    myapp.syncData();
  }-*/;
}
....

This option is less “the Vaadin way”, but in some cases might be useful.

[[creating-efficient-offline-views]]
Creating efficient offline views
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

There are two main concerns with offline-enabled applications:

1.  Maximizing code sharing between online and offline mode.
2.  Seamlessly switching between offline and online mode.

To share the code for a view that is used both in online and offline,
you will probably need to create the view as a custom widget, including
connector and a server-side component class. If you know how to do this
and understand why it is needed, you can skip to the “Switching between
online and offline” subsection .

As Vaadin is a server-side framework, the views and the logic are
usually implemented using server-side Java code. During application
lifetime, a lot of traffic is sent between the server and the client
even in a single view. Thus server-side implemented views are not usable
when there is no connection between server and client.

For very simple views (e.g. providing a list, no data input) it might be
appropriate to have two separate implementations, one client-side and
one server-side, as it is quick and easy to build these and you avoid
the development and code overhead of using client-side views online,
keeping the server-side advantages for the online version.

For more complex functionality you will need to implement a fully
client-side view for both online and offline operation and then
synchronize the data as described in the previous section. Using it
during a completely offline operation is straightforward: just show the
view on the screen by an OfflineMode interface implementation in an
overlay. For server-side usage you will probably need to create a
https://vaadin.com/book/-/page/gwt.html[server-side component and a
connector].

[[switching-between-online-and-offline]]
Switching between online and offline
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

What we want to achieve is that the user doesn’t feel that the
application went offline or online if he doesn’t need to know that. We
might show an indicator so that the user is aware, but he should be able
to do what he did before the switch happened, if this is possible. Also,
no data should be lost during switching.

[[a-navigatormanager-issue-and-workaround]]
A NavigatorManager issue and workaround
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Before we go to some deeper details, note that there is an annoying
`NavigatorManager` behavior related to offline mode: when you click a
`NagivationButton` while the connection is down (but before offline mode
was activated) and the target view is not in the DOM yet, the server
does not respond the system switches to offline mode and then when
coming back from offline mode, we’re stuck in an empty view.

A workaround for this is to call `NavigatorManagerConnector` to redraw on
an online event, so this might be put in some connector (you might use
deferred binding to put this in `NavigatorManagerConnector` itself):

[source,java]
....
getConnection().addHandler(OnlineEvent.TYPE, new OnlineEvent.OnlineHandler() {
  @Override
  public void onOnline(final OnlineEvent event) {
    final JsArrayObject<ComponentConnector> jsArray =
        ConnectorMap.get(getConnection()).getComponentConnectorsAsJsArray();

    for (int i = 0; jsArray.size() > i; i++) {
      if (jsArray.get(i) instanceof NavigationManagerConnector) {
        final NavigationManagerConnector connector =
            (NavigationManagerConnector) jsArray.get(i);
        connector.forceStateChange();
      }
    }
  }
});
....

[[user-experience-considerations-related-to-switching]]
User experience considerations related to switching
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Here’s an example of what we want to achieve: if the user is filling a
form, which by design can be filled offline or online, and the network
suddenly goes down, he should be able to continue filling the form
without much interference. That means, if we’re using method 3 by
implementing OfflineMode and showing an overlay on the screen (which is
done in the Parking demo), the offline overlay will be hiding the real
online form. At that point the data from the online form is copied to
the offline form and the user barely notices that something happened.
That means there are two instances of the form, online one and offline
one. Another option would be that you have only one instance of the form
and instead of copying the data, you attach the whole form to a
different view (thanks to Tomi Virkku for the tip).

In the Parking demo, the ticket view jumps, because the scroll position
changes and an indicator is added. If the user was in the middle of
something, he is suddenly interrupted, although no data is lost.

If we want to improve user experience, we could implement it in a better
way. In case the network goes offline when the user is filling a form,
we disable all the elements that might fire a request to the server and
let the user continue filling the form. Of course, the form should be
implemented completely client-side, and all the suspicious elements
would be around it, probably navigation/toolbar buttons. Another option
would be to have all the elements client-side and on click they would be
checking if there is a connection, before sending anything to the
server. After the user submits or cancels the form, we can show the
“true” offline view. Alternatively, it will be the only offline view in
the application, depending on the specific case.

For example, if you are using a navigator manager, the trick would be to
keep or find the `VNavigatorManager` and disable its widgets (left and
right widgets, the ones that are used to navigate):

[source,java]
....
getConnection().addHandler(OfflineEvent.TYPE, new OfflineEvent.OfflineHandler() {
  @Override
  public void onOffline(final OfflineEvent event) {
    setWidgetEnabled(getWidget().getNavigationBar().getWidget(0), false);
  }
});

void setWidgetEnabled(final Widget widget, final boolean enabled) {
  widget.setStyleName(ApplicationConnection.DISABLED_CLASSNAME, !enabled);

  if (widget instanceof HasEnabled)
    ((HasEnabled) widget).setEnabled(enabled);

  // this is just because for some reason VNavigatorButton does not implement HasEnabled, although it has such methods...
  if (widget instanceof VNavigationButton)
    ((VNavigationButton) widget).setEnabled(enabled);
}
....

Known issues: `HasEnabled` declaration should be fixed soon, but I should
warn you that for some reason a disabled `NavigationButton` still responds
to mouse click events, although correctly ignoring touch events.

Same works in the other direction as well, so when an offline form is
shown and the connection goes up, you just keep the offline form until
the user submits/cancels, then show the online view again.

This is how you can give the user experience the best experience.

[[phonegap-integration]]
PhoneGap integration
~~~~~~~~~~~~~~~~~~~~

As this is not directly related to the topic I will not explain the
basics here, just a couple of pitfalls that someone familiar with
PhoneGap might encounter.

http://dev.vaadin.com/ticket/13250[An issue with offline mode on
PhoneGap] was reported recently and because of that, a new solution was
found that puts the Vaadin application into an iframe. You can get the
files for PhoneGap from TouchKit maven archetype (_link no longer available_). However, this solution has its
drawbacks and you might want
to disable the iframe. If you do that, you need to copy some files (like
widgetset) to your PhoneGap project. There is still ongoing discussion
of how to improve this. No more details here, this was just to warn you.

Another pitfall is that when you specify the URL in archetype’s
index.html do put the final slash:

[source,java]
....
window.vaadinAppUrl = 'http://youraddress.com/path/'; // <--- slash is compulsory!
....

Without it the application will not load from cache when there’s no
connection.