We’ve rolled out our first wave of (hybrid) Progressive Web Apps (PWAs) to our customers during the last couple of weeks. Using a PWA based approach speeds up the development process for sure, however building hybrid PWAs which run a native web container had some challenges.
Before we started to implement the hybrid PWA we had an internal discussion on how to tackle the project. To that end, we looked at two options.
Option A was to modify our existing qonnect App framework. We considered stripping it down to an app skeleton that just contains the navigation elements like the menu and webview containers. The PWA part itself would be divided into different components (e.g. news feed, interest filters, user, etc.) which then are knitted together by passing parameters (e.g., user id, etc.) from one webview to the other via native code. Every component would take care of specific functionality.
Option B was using a single webview container and to build an integrated web app as embedded “standalone”-PWA including the navigation and all the rest.
We chose option B since we wanted to optimize our implementation efforts by building a single PWA app. From a software engineering point of view this might sound like a contradiction: why not build several small PWA based components for the container app? This is certainly the most flexible solution.
However, in our case, there are strong dependencies between those components. For example, the news component depends on the interest filter settings component which depends on the user component. They always come together. This is corroborated by our project/customer pipeline: in all future projects we need those three components.
So we decided to build a lightweight container app with a simple onboarding screen and a single webview container.
Start me up
We started by building the PWA based on our qonnect framework and things worked quite well during the initial development and testing phase (on the web). We were able – as we suspected – to quickly (in a matter of days) to desgin and implement the first version of the app as PWA. We tested the main features like news stream, interest settings, embedding external content from a webpage and handling of a member card. All worked well in the PWA in mobile web browsers. Since push notifications for PWAs are only supported on Android, we didn’t bother with them in the first phase of the implementation.
Embedding the PWA
In the second phase we started to work on embedding the existing PWA into the native app containers. This was quite simple: it boiled down to a couple of lines of code that loads the PWA into the web container. That’s it. However, after using the embedded PWA we noticed a few differences and bugs in our container app due to apparent differences of the web containers on iOS and Android.
Bugs? Yes please
First of all, the menu selection didn’t work as expected. Menu items remained in the selected state in the iOS app. This didn’t happen with the Android app. Secondly, the pull to refresh feature didn’t work on iOS properly and lead to a flickering behavior on iOS (again, not on Android). The keyboard overlapped the input fields on Android when the user wanted to enter information.
Of course, this didn’t happen on the web. Fixing a bug for iOS introduced unexpected behavior on Android and vice versa. Thus we ended up re-implementing the pull to refresh feature and added some iOS specific work arounds for the menu item selection. The keyboard handling was also implemented with an Android specific workaround.
Cache as cache can
To make the app as native as possible we used caching extensively. We even considered preloading the app with local content from the app container. But the intrinsic complexities of such an approach (e.g., asset handling, API calls, update propagation, etc.) made it soon clear for us not to follow this path. Instead we chose to use built-in caching mechanisms. Using them is straightforward. It’s just a matter of configuring the server to tell the client that the content should be cached by the client. While this works well, there are some things to consider. First of all, how long should the client cache the content? We started out with a month, but soon realized that can be the source of additional problems.
As soon as the client caches the content (in our case the whole app), rolling out bugfixes can become difficult, since the propagation of these updates can take long, since the content stays cached on the client (like it is supposed to). We ended up to with a app store update triggers the app cache clearing strategy. We added code in the native container apps that cleared the local cache as soon as the use installs an app update from the app store. This provides us with synchronization points for updates that affect both, the native and the PWA part of the app.
The handling of push notifications is straightforward. The native PWA container registers for push notfications. The necessary code is quite simple and our qonnect framework provides all means to manage push notifications on the backend.
Conclusion and Outlook
With the experience of several weeks of having PWAs in production, we can say that the move from native apps to hybrid PWAs apps has paid off. We think that PWA first approach is the way into the app future: start with the PWA and if necessary you can later embedd it in a container app.