You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

OfflineModeForTouchKit4MobileApps.asciidoc 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  1. ---
  2. title: Offline Mode For TouchKit 4 Mobile Apps
  3. order: 4
  4. layout: page
  5. ---
  6. [[offline-mode-for-touchkit-4-mobile-apps]]
  7. = Offline mode for TouchKit 4 mobile apps
  8. [.underline]#*_Note:_* _Vaadin Touchkit has been discontinued. A community-supported version is
  9. available https://github.com/parttio/touchkit[on GitHub]._#
  10. [[background]]
  11. Background
  12. ~~~~~~~~~~
  13. Vaadin is primarily a server-side framework. What happens with the
  14. application when the server is not available? Although this is possible
  15. on desktop computers, more often it happens when using a mobile device.
  16. This is why Vaadin TouchKit allows
  17. you to define offline behavior. In this article I will tell you all the
  18. details you need to know about offline mode and how to use it. It is
  19. written based on Vaadin 7.3 and TouchKit 4.0.0.
  20. Touchkit is a Vaadin
  21. addon that helps in developing mobile applications. I assume that you
  22. have some knowledge in Vaadin and how to develop client-side Vaadin
  23. (GWT) code. I will mention the http://demo.vaadin.com/parking/[Parking
  24. demo] here a few times and you can find its sources
  25. https://github.com/vaadin/parking-demo[here]. I suggest that you read
  26. this article before you try to understand the Parking demo source code,
  27. it will help you grasp the concepts demonstrated in the demo.
  28. [[demystifying-offline-mode]]
  29. Demystifying offline mode
  30. ~~~~~~~~~~~~~~~~~~~~~~~~~
  31. As said before, Vaadin is a server-side framework and that implies that
  32. when an application is running, there is a lot of communication going on
  33. between the server and the client. Thus server-side views are not
  34. accessible when there is no connection. On the other hand, offline
  35. enabled applications run pure client-side Vaadin (GWT) code without
  36. connecting the server.
  37. There are a couple of approaches you might take to specify offline
  38. behavior on the client-side.
  39. 1. Write a fully client-side application for the user to interact with
  40. when the server is offline.
  41. 2. Write some views as client-side widgets and, in case the connection
  42. is lost, disable all the components that might need a server connection.
  43. Let’s take a look at the technical details you need to know.
  44. [[client-side-offline-mode-handling---method-1-checking-the-status]]
  45. Client-side offline mode handling - method 1: checking the status
  46. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  47. The simplest way to know if the application is online or offline is to
  48. use this code:
  49. [source,java]
  50. ....
  51. OfflineModeEntrypoint.get().getNetworkStatus().isAppOnline()
  52. ....
  53. You might use it before sending something to the server or calling an
  54. RPC, for example. However, the network status might change at any time.
  55. Method 2 helps you react to those changes.
  56. [[client-side-offline-mode-handling---method-2-handling-events]]
  57. Client-side offline mode handling - method 2: handling events
  58. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  59. In order to use this method you need an `ApplicationConnection` instance.
  60. We are going to use its event bus to handle online/offline events.
  61. Usually you get an `ApplicationConnection` instance from a component
  62. connector. Here is an example:
  63. [source,java]
  64. ....
  65. @Connect(MyComponent.class)
  66. public class MyConnector extends AbstractComponentConnector {
  67. @Override
  68. protected void init() {
  69. super.init();
  70. getConnection().addHandler(OnlineEvent.TYPE, new OnlineEvent.OnlineHandler() {
  71. @Override
  72. public void onOnline(final OnlineEvent event) {
  73. // do some stuff
  74. }
  75. });
  76. getConnection().addHandler(OfflineEvent.TYPE, new OfflineEvent.OfflineHandler() {
  77. @Override
  78. public void onOffline(final OfflineEvent event) {
  79. // do some stuff
  80. }
  81. });
  82. }
  83. }
  84. ....
  85. Note that this connector will only be created if an instance of
  86. `MyComponent` is created on the server side and attached to the UI. As an
  87. option, it might be a `UI` or `Component` extension connector. Otherwise
  88. your connector will never be instantiated and you will never receive
  89. these events, so you can rely on them only if you want to show some
  90. changes in the view or disable some functionality of a view when
  91. offline. In order to get true offline capabilities, use method 3.
  92. [[client-side-offline-mode-handling---method-3-implementing-offlinemode-interface]]
  93. Client-side offline mode handling - method 3: implementing OfflineMode interface
  94. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  95. Implementing client-side OfflineMode interface allows you to specify
  96. true offline-mode behavior: you will receive events also in case the
  97. page is loaded from cache without network connection at all.
  98. Fortunately, there is a default implementation and you don’t need to
  99. worry about the implementation details. `DefaultOfflineMode` provides an
  100. OfflineMode implementation for any TouchKit application. It shows a
  101. loading indicator and a sad face when the network is down. In most cases
  102. all you want to do is replace this sad face with something more useful
  103. (for example Minesweeper or Sudoku), here’s a sample:
  104. [source,java]
  105. ....
  106. public class MyOfflineMode extends DefaultOfflineMode {
  107. @Override
  108. protected void buildDefaultContent() {
  109. getPanel().clear();
  110. getPanel().add(createOfflineApplication()); // might be a full blown GWT UI
  111. }
  112. }
  113. ....
  114. Then you need to specify the implementation in your widgetset definition
  115. file (*.gwt.xml):
  116. [source,xml]
  117. ....
  118. <replace-with class="com.mybestapp.widgetset.client.MyOfflineMode">
  119. <when-type-is class="com.vaadin.addon.touchkit.gwt.client.offlinemode.OfflineMode" />
  120. </replace-with>
  121. ....
  122. This is enough for showing an offline UI, it will be shown and hidden
  123. automatically, `DefaultOfflineMode` will take care of this. If you need a
  124. more complex functionality, like doing something when going
  125. offline/online, you might want to override additional methods from
  126. `DefaultOfflineMode` or implement OfflineMode from scratch. I briefly
  127. sketch what you need to know about it.
  128. The OfflineMode interface has three methods:
  129. [source,java]
  130. ....
  131. void activate(ActivationReason);
  132. boolean deactivate();
  133. boolean isActive();
  134. ....
  135. Pretty clear, but there are some pitfalls.
  136. Counterintuitively, not all `ActivationReason`{empty}(s) actually require
  137. activating the offline application view. On
  138. `ActivationReason.APP_STARTING` you can just show a loading indicator and
  139. on `ActivationReason.ONLINE_APP_NOT_STARTED` you might want to display a
  140. reload button or actually hide the offline view. Take a look at the
  141. `DefaultOfflineMode` implementation and the `TicketViewWidget` in the
  142. Parking demo.
  143. Second thing to note: `deactivate()` will never be called if i`sActive()`
  144. returns `false`. So you must track whether the offline mode is active or
  145. just take a shortcut like this:
  146. [source,java]
  147. ....
  148. boolean isActive() {
  149. return true;
  150. }
  151. ....
  152. And the last one: regardless of what JavaDoc says, the return value of
  153. the `deactivate()` method is ignored. You might want to check if this
  154. changes in future versions.
  155. Note that this client-side
  156. 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]
  157. interface has nothing to do with server-side extension
  158. 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]
  159. class (unfortunate naming).
  160. [[setting-up-the-offline-mode]]
  161. Setting up the offline mode
  162. ~~~~~~~~~~~~~~~~~~~~~~~~~~~
  163. You can turn a Vaadin application into an offline-enabled TouchKit
  164. application by using an extension of `TouchKitServlet` as your servlet
  165. class. For example, the following might be your servlet declaration in
  166. your UI class:
  167. [source,java]
  168. ....
  169. @WebServlet(value = "/*")
  170. public static class Servlet extends TouchKitServlet /* instead of VaadinServlet */ {}
  171. ....
  172. Below are some details that you might need at some point (or have read
  173. about in other places and are wondering what they are). You may skip to
  174. the “Synchronizing data between server and client” section if you just
  175. want a quick start.
  176. You can check network status (method 1) in any TouchKit application
  177. (i.e. any application using `TouchKitServlet`), nothing special is
  178. required.
  179. In order to use the application connection event bus (method 2), offline
  180. mode must be enabled or no events will be sent. As of TouchKit 4, it is
  181. enabled by default whenever you use TouchKit. If for some reason you
  182. want offline mode disabled, annotate your UI class with
  183. `@OfflineModeEnabled(false)`. Although this is not recommended in TouchKit
  184. applications, because no message will be shown if the app goes offline,
  185. not even the standard Vaadin message.
  186. For method 3 (implementing the OfflineMode interface), besides enabling
  187. offline mode, the
  188. http://en.wikipedia.org/wiki/Cache_manifest_in_HTML5[HTML5 cache
  189. manifest] should be enabled. The cache manifest tells the browser to
  190. cache some files, so that they can be used without a network connection.
  191. As with the offline mode, it is enabled by default. If you want it
  192. disabled, annotate your UI class with  `@CacheManifestEnabled(false)`.
  193. That way your application might be fully functional once starting online
  194. and then going offline (if it does not need any additional files when
  195. offline), but will not be able to start when there is no connection.
  196. [[caching-additional-files-for-example-a-custom-theme]]
  197. Caching additional files, for example a custom theme
  198. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  199. If you need some additional files to be cached for offline loading (most
  200. likely your custom theme), you can add this property to your *.gwt.xml
  201. file:
  202. [source,xml]
  203. ....
  204. <set-configuration-property
  205. name='touchkit.manifestlinker.additionalCacheRoot'
  206. value='path/relative/to/project/root:path/on/the/server' />
  207. ....
  208. Only files having these extensions will be added to the cache manifest:
  209. .html, .js, .css, .png, .jpg, .gif, .ico, .woff);
  210. If this is a directory, it will be scanned recursively and all the files
  211. with these extensions will be added to the manifest.
  212. [[offlinemode-extension]]
  213. OfflineMode extension
  214. ^^^^^^^^^^^^^^^^^^^^^
  215. In addition, you can slightly tweak the offline mode through the
  216. OfflineMode UI extension.
  217. You can set offline mode timeout (if there’s no response from the server
  218. during this time, offline mode will be activated), or manually set
  219. application mode to offline/online (useful for development). There’s
  220. also a less useful parameter: enable/disable persistent session cookie
  221. (enabled by default if you use `@PreserveOnRefresh`, which you should do
  222. for offline mode anyways). That’s all there is in this extension. Usage:
  223. [source,java]
  224. ....
  225. // somewhere among UI initializaion
  226. OfflineMode offline = new OfflineMode();
  227. offline.extend(this);
  228. offlineModeSettings.setOfflineModeTimeout(5);
  229. ....
  230. Note: it is not compulsory to use this extension, but it helps the
  231. client side of the Touchkit add-on to find the application connection.
  232. Without it, it tries to get an application connection for 5 seconds. If
  233. you suspect that your connection is too slow or the server is very slow
  234. to respond, you might add a new `OfflineMode().extend(this);` to your UI
  235. just in case. That should be very rarely needed.
  236. This extension is usually used for synchronizing data between the server
  237. and the client (covered in the next section), but it can be done through
  238. any other extension/component -- there is no special support for it in
  239. OfflineMode extension.
  240. [[synchronizing-data-between-server-and-client]]
  241. Synchronizing data between server and client
  242. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  243. In a sense, the client is always in “offline mode” between requests from
  244. the server point of view. Therefore the regular Vaadin way of
  245. synchronizing data between the client-side widget and the server-side
  246. (https://vaadin.com/book/-/page/gwt.rpc.html[Vaadin RPC mechanism] and
  247. https://vaadin.com/book/-/page/gwt.shared-state.html[shared state]) is
  248. still valid, the difference being that the offline widget is probably
  249. more complex and the amount of data is greater than that of an average
  250. component.
  251. As mentioned, the server is not necessarily aware that the client went
  252. offline for some time, therefore the synchronization should be initiated
  253. from the client side. So using method 2 or 3, the client side gets an
  254. event that the connection is online and it sends an RPC call to the
  255. server. New data might be sent with the notification or asked
  256. separately, e.g. using
  257. http://demo.vaadin.com/javadoc/com.vaadin.addon/vaadin-touchkit-agpl/4.0.0/index.html?com/vaadin/addon/touchkit/extensions/LocalStorage.html[LocalStorage]
  258. (TouchKit provides easy access to
  259. http://www.w3schools.com/html/html5_webstorage.asp[HTML5 LocalStorage]
  260. from the server side). The server might send new data through shared
  261. state.
  262. If we reuse OfflineMode (mentioned in the end of the last section), the
  263. code might look like this:
  264. [source,java]
  265. ....
  266. public class MyOfflineModeExtension extends OfflineMode {
  267. public MyOfflineModeExtension() {
  268. registerRpc(serverRpc);
  269. }
  270. private final SyncDataServerRpc serverRpc = new SyncDataServerRpc() {
  271. @Override
  272. public void syncData(final Object newData) {
  273. doSmth(newData); // update data
  274. getState().someProperty = newServerData; // new data from the server to the client
  275. }
  276. };
  277. }
  278. @Connect(MyOfflineModeExtension.class)
  279. public class MyOfflineConnector extends OfflineModeConnector {
  280. private final SyncDataServerRpc rpc = RpcProxy.create(SyncDataServerRpc.class, this);
  281. @Override
  282. protected void init() {
  283. super.init();
  284. getConnection().addHandler(OnlineEvent.TYPE, new OnlineEvent.OnlineHandler() {
  285. @Override
  286. public void onOnline(final OnlineEvent event) {
  287. Object new Data = … // get updated data
  288. rpc.syncData(newData);
  289. }
  290. });
  291. }
  292. }
  293. ....
  294. As already said, this does not necessarily have to be done through the
  295. OfflineMode extension, it can be done using any component connector,
  296. there is nothing special about OfflineMode.
  297. Another option, a less wordy and more decoupled one, could be done by
  298. using JavaScript function call.
  299. On the server side:
  300. [source,java]
  301. ....
  302. JavaScript.getCurrent().addFunction("myapp.syncData",
  303. (args) -> { /*sync data, e.g. get it from LocalStorage */});
  304. ....
  305. On the client side:
  306. [source,java]
  307. ....
  308. // in any connector
  309. getConnection().addHandler(OnlineEvent.TYPE, new OnlineEvent.OnlineHandler() {
  310. @Override
  311. public native void onOnline(final OnlineEvent event) /*-{
  312. myapp.syncData();
  313. }-*/;
  314. });
  315. ....
  316. Or similar code in client-side OfflineMode implementation:
  317. [source,java]
  318. ....
  319. MyOfflineMode extends DefaultOfflineMode {
  320. @Override
  321. public native boolean deactivate() /*-{
  322. myapp.syncData();
  323. }-*/;
  324. }
  325. ....
  326. This option is less “the Vaadin way”, but in some cases might be useful.
  327. [[creating-efficient-offline-views]]
  328. Creating efficient offline views
  329. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  330. There are two main concerns with offline-enabled applications:
  331. 1. Maximizing code sharing between online and offline mode.
  332. 2. Seamlessly switching between offline and online mode.
  333. To share the code for a view that is used both in online and offline,
  334. you will probably need to create the view as a custom widget, including
  335. connector and a server-side component class. If you know how to do this
  336. and understand why it is needed, you can skip to the “Switching between
  337. online and offline” subsection .
  338. As Vaadin is a server-side framework, the views and the logic are
  339. usually implemented using server-side Java code. During application
  340. lifetime, a lot of traffic is sent between the server and the client
  341. even in a single view. Thus server-side implemented views are not usable
  342. when there is no connection between server and client.
  343. For very simple views (e.g. providing a list, no data input) it might be
  344. appropriate to have two separate implementations, one client-side and
  345. one server-side, as it is quick and easy to build these and you avoid
  346. the development and code overhead of using client-side views online,
  347. keeping the server-side advantages for the online version.
  348. For more complex functionality you will need to implement a fully
  349. client-side view for both online and offline operation and then
  350. synchronize the data as described in the previous section. Using it
  351. during a completely offline operation is straightforward: just show the
  352. view on the screen by an OfflineMode interface implementation in an
  353. overlay. For server-side usage you will probably need to create a
  354. https://vaadin.com/book/-/page/gwt.html[server-side component and a
  355. connector].
  356. [[switching-between-online-and-offline]]
  357. Switching between online and offline
  358. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  359. What we want to achieve is that the user doesn’t feel that the
  360. application went offline or online if he doesn’t need to know that. We
  361. might show an indicator so that the user is aware, but he should be able
  362. to do what he did before the switch happened, if this is possible. Also,
  363. no data should be lost during switching.
  364. [[a-navigatormanager-issue-and-workaround]]
  365. A NavigatorManager issue and workaround
  366. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  367. Before we go to some deeper details, note that there is an annoying
  368. `NavigatorManager` behavior related to offline mode: when you click a
  369. `NagivationButton` while the connection is down (but before offline mode
  370. was activated) and the target view is not in the DOM yet, the server
  371. does not respond the system switches to offline mode and then when
  372. coming back from offline mode, we’re stuck in an empty view.
  373. A workaround for this is to call `NavigatorManagerConnector` to redraw on
  374. an online event, so this might be put in some connector (you might use
  375. deferred binding to put this in `NavigatorManagerConnector` itself):
  376. [source,java]
  377. ....
  378. getConnection().addHandler(OnlineEvent.TYPE, new OnlineEvent.OnlineHandler() {
  379. @Override
  380. public void onOnline(final OnlineEvent event) {
  381. final JsArrayObject<ComponentConnector> jsArray =
  382. ConnectorMap.get(getConnection()).getComponentConnectorsAsJsArray();
  383. for (int i = 0; jsArray.size() > i; i++) {
  384. if (jsArray.get(i) instanceof NavigationManagerConnector) {
  385. final NavigationManagerConnector connector =
  386. (NavigationManagerConnector) jsArray.get(i);
  387. connector.forceStateChange();
  388. }
  389. }
  390. }
  391. });
  392. ....
  393. [[user-experience-considerations-related-to-switching]]
  394. User experience considerations related to switching
  395. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  396. Here’s an example of what we want to achieve: if the user is filling a
  397. form, which by design can be filled offline or online, and the network
  398. suddenly goes down, he should be able to continue filling the form
  399. without much interference. That means, if we’re using method 3 by
  400. implementing OfflineMode and showing an overlay on the screen (which is
  401. done in the Parking demo), the offline overlay will be hiding the real
  402. online form. At that point the data from the online form is copied to
  403. the offline form and the user barely notices that something happened.
  404. That means there are two instances of the form, online one and offline
  405. one. Another option would be that you have only one instance of the form
  406. and instead of copying the data, you attach the whole form to a
  407. different view (thanks to Tomi Virkku for the tip).
  408. In the Parking demo, the ticket view jumps, because the scroll position
  409. changes and an indicator is added. If the user was in the middle of
  410. something, he is suddenly interrupted, although no data is lost.
  411. If we want to improve user experience, we could implement it in a better
  412. way. In case the network goes offline when the user is filling a form,
  413. we disable all the elements that might fire a request to the server and
  414. let the user continue filling the form. Of course, the form should be
  415. implemented completely client-side, and all the suspicious elements
  416. would be around it, probably navigation/toolbar buttons. Another option
  417. would be to have all the elements client-side and on click they would be
  418. checking if there is a connection, before sending anything to the
  419. server. After the user submits or cancels the form, we can show the
  420. “true” offline view. Alternatively, it will be the only offline view in
  421. the application, depending on the specific case.
  422. For example, if you are using a navigator manager, the trick would be to
  423. keep or find the `VNavigatorManager` and disable its widgets (left and
  424. right widgets, the ones that are used to navigate):
  425. [source,java]
  426. ....
  427. getConnection().addHandler(OfflineEvent.TYPE, new OfflineEvent.OfflineHandler() {
  428. @Override
  429. public void onOffline(final OfflineEvent event) {
  430. setWidgetEnabled(getWidget().getNavigationBar().getWidget(0), false);
  431. }
  432. });
  433. void setWidgetEnabled(final Widget widget, final boolean enabled) {
  434. widget.setStyleName(ApplicationConnection.DISABLED_CLASSNAME, !enabled);
  435. if (widget instanceof HasEnabled)
  436. ((HasEnabled) widget).setEnabled(enabled);
  437. // this is just because for some reason VNavigatorButton does not implement HasEnabled, although it has such methods...
  438. if (widget instanceof VNavigationButton)
  439. ((VNavigationButton) widget).setEnabled(enabled);
  440. }
  441. ....
  442. Known issues: `HasEnabled` declaration should be fixed soon, but I should
  443. warn you that for some reason a disabled `NavigationButton` still responds
  444. to mouse click events, although correctly ignoring touch events.
  445. Same works in the other direction as well, so when an offline form is
  446. shown and the connection goes up, you just keep the offline form until
  447. the user submits/cancels, then show the online view again.
  448. This is how you can give the user experience the best experience.
  449. [[phonegap-integration]]
  450. PhoneGap integration
  451. ~~~~~~~~~~~~~~~~~~~~
  452. As this is not directly related to the topic I will not explain the
  453. basics here, just a couple of pitfalls that someone familiar with
  454. PhoneGap might encounter.
  455. http://dev.vaadin.com/ticket/13250[An issue with offline mode on
  456. PhoneGap] was reported recently and because of that, a new solution was
  457. found that puts the Vaadin application into an iframe. You can get the
  458. files for PhoneGap from TouchKit maven archetype (_link no longer available_). However, this solution has its
  459. drawbacks and you might want
  460. to disable the iframe. If you do that, you need to copy some files (like
  461. widgetset) to your PhoneGap project. There is still ongoing discussion
  462. of how to improve this. No more details here, this was just to warn you.
  463. Another pitfall is that when you specify the URL in archetype’s
  464. index.html do put the final slash:
  465. [source,java]
  466. ....
  467. window.vaadinAppUrl = 'http://youraddress.com/path/'; // <--- slash is compulsory!
  468. ....
  469. Without it the application will not load from cache when there’s no
  470. connection.