diff options
author | Erik Lumme <erik@vaadin.com> | 2017-09-14 12:57:43 +0300 |
---|---|---|
committer | Erik Lumme <erik@vaadin.com> | 2017-09-14 12:57:43 +0300 |
commit | a9699a372495c92371b49a8ea224e8db2c01cd6a (patch) | |
tree | bace8460fcf9d8e26e5ce7fa948d2be31fd1201c | |
parent | 6390e2fb37e730fb0ae75660bff53bf933bdffa5 (diff) | |
download | vaadin-framework-a9699a372495c92371b49a8ea224e8db2c01cd6a.tar.gz vaadin-framework-a9699a372495c92371b49a8ea224e8db2c01cd6a.zip |
Migrate OfflineModeForTouchKit4MobileApps
-rw-r--r-- | documentation/articles/OfflineModeForTouchKit4MobileApps.asciidoc | 570 | ||||
-rw-r--r-- | documentation/articles/contents.asciidoc | 1 |
2 files changed, 571 insertions, 0 deletions
diff --git a/documentation/articles/OfflineModeForTouchKit4MobileApps.asciidoc b/documentation/articles/OfflineModeForTouchKit4MobileApps.asciidoc new file mode 100644 index 0000000000..1657464d7e --- /dev/null +++ b/documentation/articles/OfflineModeForTouchKit4MobileApps.asciidoc @@ -0,0 +1,570 @@ +[[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. + +[Error: Macro 'TableOfContents' doesn't exist] + +[[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. diff --git a/documentation/articles/contents.asciidoc b/documentation/articles/contents.asciidoc index 0fc3ebc5f1..0c5ce7f0af 100644 --- a/documentation/articles/contents.asciidoc +++ b/documentation/articles/contents.asciidoc @@ -4,3 +4,4 @@ == Articles - link:LazyQueryContainer.asciidoc[Lazy query container] - link:UsingJDBCwithLazyQueryContainerAndFilteringTable.asciidoc[Using JDBC with Lazy Query Container and FilteringTable] +- link:OfflineModeForTouchKit4MobileApps.asciidoc[Offline mode for TouchKit 4 mobile apps] |