Building an offline experience with a Rails-powered PWA

Summarized using AI

Building an offline experience with a Rails-powered PWA

Alicia Rojas • May 30, 2023 • online • Talk

The session led by Alicia Rojas at the WNB.rb Meetup focused on building offline experiences with Progressive Web Applications (PWAs) using Ruby on Rails. Rojas began by defining PWAs and discussing their advantages, such as enhanced reliability, installability, and the capacity to work offline, making them rich in user experience compared to traditional web apps.

Key points discussed include:
- PWAs Overview: Progressive Web Applications are designed to combine the best features of web and native applications, offering enhanced capabilities like offline support and push notifications.
- Case Study: Rojas presented a case study where a PWA was developed for the Italian government to assist farmers and technicians in assessing farm sustainability. The need for offline functionality was critical due to unreliable internet access in rural areas.
- Implementation: The speaker detailed the two main assets required to convert a Rails application into a PWA:
- Service Workers: These act as intermediaries between the web browser and server, intercepting requests to enhance performance and provide offline capabilities.
- App Manifest: This JSON file allows the browser to manage how the PWA is displayed on users' devices, helping to replicate a native application experience.
- Storage Solutions: Rojas explored browser storage options, particularly focusing on IndexedDB for storing complex objects and the Background Sync API, which helps synchronize data in the background when internet connectivity is restored.
- User Control Over Synchronization: The importance of giving users control over when synchronization occurs was emphasized, leading to a discussion on implementing a manual sync button using Stimulus, a JavaScript framework.
- Data Display: Strategies for displaying data collected in IndexedDB were also addressed, using the Mustache library to render objects on the user interface.
- Best Practices: Rojas highlighted the need for front-end and back-end validation to prevent errors during synchronization and the necessity for assessing browser compatibility based on user demographics.

Conclusions and Takeaways:
- PWAs can significantly enhance web applications, making them practical for diverse audiences, including those in rural locations.
- Utilizing frameworks like Stimulus can simplify the implementation of offline features, guiding developers to create applications that behave similarly to single-page apps with minimal JavaScript. Rojas concluded the presentation with encouragement for developers to explore the capabilities of PWAs to create impactful web solutions.

Building an offline experience with a Rails-powered PWA
Alicia Rojas • May 30, 2023 • online • Talk

Progressive web applications (PWAs) allow us to provide rich offline experiences as native apps would, while still being easy to use and share, as web apps are. Come to learn how to turn your regular Rails application into a PWA, taking advantage of the new front-end tools that come with Rails by default since version 7.
https://www.wnb-rb.dev/meetups/2023/05/30

WNB.rb Meetup May 2023

00:00:00.000 welcome to this session I'm going to
00:00:02.159 talk about how to build an offline
00:00:04.500 experience with the rails powered pwa
00:00:09.500 so a bit about me I'm a software
00:00:12.179 developer at Dallas labs this is my
00:00:14.940 part-time job I'm actually a music
00:00:16.859 composer the other half of my productive
00:00:20.279 time
00:00:22.100 I was a natural resources engineer
00:00:25.380 before this so that's where my passion
00:00:27.900 about technology and sustainability
00:00:29.820 comes from
00:00:31.199 and actually the case study that I'm
00:00:33.600 gonna talk about it's it comes from from
00:00:36.300 that background
00:00:38.219 so as a quick upper view of this
00:00:40.680 presentation
00:00:42.180 I'm gonna Define first what is a blue UA
00:00:45.780 and why I think they're awesome then I'm
00:00:48.059 gonna briefly cover the case study
00:00:51.059 and then I'm gonna explain the main two
00:00:54.420 assets that are required to turn your
00:00:56.879 rails application into a progressive web
00:00:59.760 app
00:01:00.780 and then the substance the main
00:01:03.600 substance of this presentation which is
00:01:05.640 how to perform offline uh create read
00:01:09.299 update and delete quote-unquote actions
00:01:13.920 and finally some key takeaways
00:01:17.100 so
00:01:18.240 uh appear away is an acronym that stands
00:01:21.600 for Progressive web applications so
00:01:24.720 these are just web apps that use
00:01:27.299 service workers and manifests which are
00:01:30.119 two assets that I'll explain later and
00:01:32.880 other web platforms features in
00:01:35.460 combination with Progressive enhancement
00:01:37.439 to give users a boosted experience on
00:01:41.159 pair with Native applications
00:01:43.320 so this enhanced capability can be such
00:01:47.520 as speech performance push notifications
00:01:50.720 or in the case of this case study the
00:01:55.560 offline support So this gives pillow UA
00:01:59.579 um
00:02:00.720 enhanced capabilities reliability and
00:02:03.659 installability
00:02:05.759 so I like to think about pwas as the mix
00:02:09.300 of best of the Native and web Rewards
00:02:13.500 so this is a common chart that I've seen
00:02:15.980 in the web
00:02:18.000 I've seen some versions of the chart
00:02:20.340 where pwas and Native applications are
00:02:23.160 kind of the same level but I think
00:02:26.040 um
00:02:26.700 that because native apps are so platform
00:02:29.819 specific
00:02:32.160 they can reach more capabilities than
00:02:35.640 people always but still I think people
00:02:37.500 always are a pretty good deal
00:02:40.020 in terms of uh the budget friendliness
00:02:43.640 uh you can install them but it's not
00:02:46.500 required so you can just use them from
00:02:48.660 the regular browser
00:02:50.540 you can have search engine visibility
00:02:53.580 which you don't enter in in Native
00:02:56.459 applications
00:02:58.080 and so on so I think this is a pretty
00:03:01.319 good deal for a web application
00:03:05.160 so
00:03:06.360 the case study
00:03:08.640 um this was an application that was
00:03:10.800 requested by the Italian government and
00:03:13.019 it was meant to be used by farmers and
00:03:15.420 farm technicians in the field in order
00:03:18.720 to diagnose their fields uh sorry their
00:03:21.840 yeah their farms in terms of
00:03:23.819 sustainability so it was
00:03:26.580 uh kind of a complex survey that they
00:03:29.519 will fill up and they will show the
00:03:31.440 results to the farmers in the field
00:03:33.780 so as you can imagine uh the offline
00:03:36.540 support it was very important because
00:03:39.780 uh because we're talking about farms and
00:03:42.420 rural areas where internet connection is
00:03:44.459 not very reliable
00:03:47.220 so uh we really needed to tackle this
00:03:49.920 this challenge uh fast and in a
00:03:52.739 maintainable way
00:03:54.360 so this is uh the way it looks it's it's
00:03:57.900 a complex survey it has it has file
00:04:00.420 uploads Maps
00:04:03.120 um lots of questions
00:04:05.879 so we needed to provide a way for this
00:04:08.940 farmer and farm technicians to fill this
00:04:11.340 form offline
00:04:13.019 so the solution we came up with was to
00:04:16.199 build
00:04:18.919 an application with our favorite
00:04:21.359 favorite framework which is Ruby on
00:04:23.940 Rails so we could just provide value as
00:04:27.660 fast as possible to our client and then
00:04:30.479 we would progressively enhance it with
00:04:33.240 the pwa features
00:04:36.300 um we considered to build a native
00:04:38.340 application but this would require both
00:04:40.620 a rails API and the native code so this
00:04:43.979 would disallow a web experience without
00:04:46.919 an additional web front end so we wanted
00:04:49.919 to have a single code base
00:04:52.139 and efficiency and maintainability were
00:04:55.080 really key because of the budget
00:04:57.180 constraints
00:04:59.940 so um I'm kind of a homesick now so this
00:05:03.120 is just all images from from Chile where
00:05:06.540 I come from
00:05:07.740 uh so how to turn your application into
00:05:10.740 a video uh the Main Ingredients so these
00:05:14.699 are two assets
00:05:16.620 the first is the service worker
00:05:20.160 so
00:05:21.479 um the official definition is that
00:05:24.479 service workers are a specialized
00:05:26.400 JavaScript assets that acts as proxies
00:05:29.280 between your web browser and the web
00:05:32.160 server
00:05:33.120 so there are like in the middle of them
00:05:35.720 intercepting requests and improving
00:05:38.660 reliability boosting page performance
00:05:41.520 like all sorts of enhanced capabilities
00:05:45.180 but the key part here is that service
00:05:47.940 workers are an enhancement so there
00:05:52.020 might be cases where your client does
00:05:54.360 not have browser support for service
00:05:56.520 worker but this means that no base
00:05:59.699 functionality should be broken because
00:06:01.680 they're again an enhancement
00:06:04.020 so I like to think about service workers
00:06:06.479 like a small application that's running
00:06:08.400 in parallel to your rails application
00:06:10.560 and it's kind of there in the middle
00:06:12.900 intercepting requests and doing
00:06:15.419 different kinds of things
00:06:17.460 so the scope is important uh it should
00:06:19.919 be available at the root scope because
00:06:22.500 this is
00:06:24.060 this is reflected in the scope that your
00:06:27.300 service worker will be capable to
00:06:29.759 control
00:06:30.960 so we wanted to be at the roots code so
00:06:33.240 it can control all of your all the pages
00:06:35.639 in within your your main
00:06:39.360 web application
00:06:41.759 so this is uh kind of an example uh this
00:06:45.000 is not like an actual service worker it
00:06:47.699 look like but I wanted to show
00:06:50.880 uh these callbacks so the service worker
00:06:54.180 Works based on a life cycle of events
00:06:57.840 that are triggered in the browser and
00:07:00.360 the service worker can listen to them so
00:07:02.460 that's why I added those event listeners
00:07:05.639 at the end and every time it hears or it
00:07:08.580 listens for one of these events it
00:07:11.340 triggers this callback
00:07:13.620 so you fortunately don't have to write
00:07:16.560 these callbacks uh by yourself I
00:07:19.740 recommend to use a library that's called
00:07:22.139 workbox it's the link it's at the end of
00:07:24.900 this presentation which allows a more
00:07:27.419 programmer friendly Syntax for writing
00:07:30.419 your service worker
00:07:33.060 so the second main ingredient here is
00:07:36.060 the app manifest
00:07:37.560 so this is a Json file that will tell
00:07:40.800 your browser how your pillow UI should
00:07:43.680 display within the operating system of
00:07:46.259 your user's device
00:07:47.819 so this is an important file to make
00:07:49.800 your app installable
00:07:51.660 and that's to make it look and feel like
00:07:54.240 a native application
00:07:56.039 and same thing here with the with the
00:07:58.620 scope it should be available at the root
00:08:01.199 scope
00:08:03.000 so this is uh more or less how a
00:08:06.000 manifest look like
00:08:07.620 in this case I used
00:08:10.160 json.erb because I want to do some of
00:08:12.599 the Rails helpers
00:08:14.400 so I could use for example display my
00:08:17.940 home screen icon which is the icon that
00:08:21.599 will be added to the home screen of your
00:08:24.539 user's device when they accept the
00:08:28.199 prompt to install the application I
00:08:30.360 don't know if any of you have ever enter
00:08:32.760 your website and you're prompted to add
00:08:35.520 the home screen icon so that's probably
00:08:38.459 because these applications have a web
00:08:40.320 manifest
00:08:42.419 and other things such as the
00:08:46.020 like the display and the color and here
00:08:50.040 the the kind of web browser display that
00:08:52.920 you want to choose I chose Standalone
00:08:55.019 because I wanted to resemble as much as
00:08:57.000 possible native experience
00:09:01.320 so I'm not gonna dig into how to add
00:09:03.600 these files in your application because
00:09:05.339 I wrote I wrote a very detailed blog
00:09:08.279 post on this topic and I'd have little
00:09:10.680 time so I just left the link there but I
00:09:14.760 want to dig into how to create records
00:09:17.880 in your application and sync them later
00:09:22.080 so there are two main apis I used here
00:09:25.920 the first one is index DB so in terms of
00:09:30.420 browser storage tools we have different
00:09:32.880 options we have cookies session storage
00:09:35.760 we have local storage
00:09:38.100 and we have index DB
00:09:40.560 I chose this one because it's very
00:09:42.720 powerful you can store pretty much
00:09:45.240 everything from like Json objects to
00:09:49.260 even files blobs images
00:09:53.100 um it has persistence across sessions
00:09:55.680 and across tab browser tabs so that's
00:09:58.800 very powerful
00:10:01.080 and
00:10:03.120 um it's well it's a JavaScript API uh it
00:10:06.660 relies heavily on premises so it's
00:10:08.760 recommended that you have uh an at least
00:10:12.180 basic understanding of promises uh when
00:10:15.300 you deal with this but even if you do
00:10:17.820 have a strong Foundation I recommend
00:10:19.980 using a promise sorry a wrapper I
00:10:23.040 promise it will be easier
00:10:25.140 so wrappers RGS libraries that allow a
00:10:27.720 more programmer friendly Syntax for
00:10:29.760 using indexeddb under common different
00:10:31.860 flavors different functionalities
00:10:35.160 so you don't have to deal with these
00:10:37.200 promise syntax
00:10:38.700 um like
00:10:40.320 playing
00:10:42.240 so the other tool is the background Sync
00:10:45.300 API and this is a very powerful tool
00:10:48.240 that allows web applications to defer
00:10:50.160 service synchronization work to their
00:10:52.200 service worker so it can be handled at a
00:10:55.260 later time so the browser we basically
00:10:58.019 detect when the connection is back
00:11:02.100 and it will trigger the background
00:11:04.019 synchronization
00:11:06.240 so the implementation works more or less
00:11:08.820 like this
00:11:10.079 we have our server on the left side
00:11:12.540 sorry on the right side and our client
00:11:15.660 in the left side
00:11:17.220 let's suppose we have a user that's uh
00:11:20.160 just completed a survey and they want to
00:11:22.079 make a post request to your server
00:11:25.079 but uh the user is not connected to the
00:11:27.660 internet so this post request will fail
00:11:31.019 so this is where indexeddy comes to play
00:11:34.200 we will catch this object and store it
00:11:38.100 in index DB
00:11:39.839 so the the entire survey that your
00:11:42.480 farmer or your user completed it's not
00:11:45.480 stored in index DB
00:11:47.519 and then the background thing will later
00:11:49.800 detect when your user is back online and
00:11:54.000 will attempt the request again taking
00:11:56.279 the object from index DB
00:11:58.680 and now we have a synced server
00:12:03.480 so in order to do this we need to
00:12:05.640 perform several steps we first need to
00:12:08.100 check the network status so for this uh
00:12:11.760 the simplest solution that we found or
00:12:15.000 better the most effective uh it was
00:12:18.899 through appalling so we had this one
00:12:21.420 pixel image stored in our server we
00:12:23.880 would exclude this image from the
00:12:26.160 caching Passover service worker so this
00:12:28.680 image would never be cached it will
00:12:30.600 always be fetched fetched from the
00:12:33.480 server
00:12:34.760 and we every every time we would do this
00:12:37.920 like every 10 15 seconds we would store
00:12:41.279 a Boolean value value in our local
00:12:43.680 storage that we would use as a source uh
00:12:46.920 source of Truth to know whether we are
00:12:49.079 online or not
00:12:51.660 um then we need to declare or find uh an
00:12:54.720 already created database with our
00:12:57.360 wrapper for indexeddb
00:13:00.779 so every time we would submit a form
00:13:03.660 we would check the network status and if
00:13:06.180 no network was available we will store
00:13:08.639 this object in indexeddy instead of
00:13:11.880 Performing the actual post request
00:13:14.639 so as a productive here uh very quickly
00:13:16.920 we found out that this uh options we we
00:13:21.899 were performing in different places
00:13:24.480 um so in order to not to repeat
00:13:26.399 ourselves we ended up using stimulus
00:13:29.160 mixing so we would have a mixing for
00:13:31.320 checking the network status we would
00:13:33.360 have another for declaring a database
00:13:38.100 so um the implementation of the actual
00:13:41.339 function function to perform the
00:13:43.740 background sync
00:13:45.600 looks more or less like this we would
00:13:48.600 first register the the event under a
00:13:52.500 certain tag
00:13:53.880 then we would match this tag with the ad
00:13:57.180 event listener sorry with the event
00:13:59.100 listener that we
00:14:00.980 registered for this sync event
00:14:04.980 so every time we would trigger this we
00:14:08.160 would listen this event we would trigger
00:14:09.959 this function which is sync service
00:14:12.779 so in this case I'm using my jaxi syntax
00:14:16.860 like C is the wrapper that I I chose
00:14:21.180 so
00:14:22.860 um we would first query the database to
00:14:26.579 know whether there are service or not
00:14:28.500 and if we find service we would store
00:14:32.279 them in an array
00:14:33.779 we would declare an another array to
00:14:37.320 that I will explain later which is
00:14:39.779 service ID stream of
00:14:41.699 as an empty array
00:14:44.399 so we would then
00:14:46.620 iterate through this array and perform
00:14:49.380 an Ajax request to your server
00:14:52.440 for each one of them
00:14:54.360 and if the response was successful so we
00:14:57.300 make a post request for a survey and we
00:14:59.820 get a successful response so we take
00:15:02.940 that ID and we push it to this ID to
00:15:06.360 remove array
00:15:08.399 and we will look to through all our
00:15:11.339 service and finally we would uh when all
00:15:16.019 uh surveys were already synced we would
00:15:19.380 Loop through the service IDs to remove
00:15:21.779 to remove those objects from indexedd
00:15:25.920 because once we synced a surfy with the
00:15:30.240 server we no longer need that object in
00:15:32.760 index DB
00:15:36.480 so
00:15:38.100 um thanks Laurie
00:15:40.639 what if we want to enable manual
00:15:44.100 synchronization so there are a number of
00:15:46.680 reasons why a user would want to have
00:15:49.680 more control over when this happens
00:15:52.440 because background sync as the name says
00:15:55.019 occurs in the background so the user has
00:15:57.660 no control over when this happens or
00:16:00.660 under which conditions
00:16:02.579 so
00:16:03.959 um we might have for example a failsync
00:16:06.600 or the fact that some browsers don't
00:16:09.660 support background sync because for
00:16:12.540 example some privacy centered
00:16:15.120 uh browsers such as Brave that does have
00:16:18.660 it but they have a disabled by default
00:16:21.180 so
00:16:22.440 um among other reasons we wanted to give
00:16:25.980 our users more control over when this
00:16:28.320 synchronization happens
00:16:30.980 so in order to do this we used stimulus
00:16:34.800 again
00:16:36.720 so the way we implemented this was
00:16:39.779 connecting a stimulus controller to a
00:16:43.740 sync button that we will use for example
00:16:46.019 in our index so we would have our our
00:16:49.199 index of service and we would have there
00:16:51.660 a button that would say like sync and
00:16:55.800 that button would be connected to a
00:16:58.320 stimulus controller that would perform
00:17:00.540 the same operation that I just showed
00:17:02.880 you
00:17:03.540 so the the previous function was in the
00:17:05.939 service worker but we could use the same
00:17:08.280 function in a stimulus controller to
00:17:10.260 trigger it manually
00:17:13.260 so this is about the create right but
00:17:16.319 what do we do if we want to read this uh
00:17:19.860 data so
00:17:21.900 we just created an object we stored it
00:17:24.959 in index TV and this is how it looks but
00:17:27.240 we don't want our users to go to the
00:17:29.160 developer tools window to see this
00:17:32.700 object right
00:17:33.780 so we went a way to display this
00:17:37.080 information to the user in a further way
00:17:41.820 so again here we use the stimulus to
00:17:44.340 read these indexeddb data
00:17:47.039 and then we use uh
00:17:51.179 a library called mustache to render this
00:17:55.140 data
00:17:56.220 in the HTML that the user can see so for
00:18:00.059 each
00:18:00.900 survey that we would find we would
00:18:02.820 iterate and then perform a JavaScript
00:18:06.480 function to add this item to the Dom
00:18:10.500 so for this case HTML templates are your
00:18:13.620 friends in case of yeah you don't know
00:18:16.140 this HTML templates are a special tag
00:18:18.860 that is rendered but not actually
00:18:21.960 displayed so when you're
00:18:25.140 user is online it will render the page
00:18:28.559 this template would be loaded and then
00:18:31.260 we can use stimulus and mustache to
00:18:33.720 bring this template populated and then
00:18:37.020 render it to the user with the
00:18:39.539 information taken from index DB
00:18:42.179 so this is how the the syntax looks like
00:18:45.360 this is a template tag and we're using
00:18:47.820 here Master syntax to pass then the
00:18:51.000 attributes from the object that we
00:18:52.799 stored in indexeddb
00:18:54.840 foreign
00:18:58.760 to populate the template and render it
00:19:03.360 this is how the function looks like and
00:19:06.539 I'm kind of running out of time so I'm
00:19:08.760 just gonna skim through this
00:19:10.919 uh this is how the
00:19:13.620 uh the index look like so this is the
00:19:16.320 button I just mentioned uh with this
00:19:19.080 button would be connected to stimulus so
00:19:20.820 we could trigger a manual
00:19:21.900 synchronization we have a trouble here
00:19:24.660 that would show us uh this the network
00:19:27.660 status this would use the local storage
00:19:31.320 value that I just mentioned as a source
00:19:34.080 of Truth
00:19:35.340 we would have this
00:19:37.740 um icons to tell the users whether these
00:19:42.120 items are synced or not
00:19:46.100 and yeah pretty much pretty much that
00:19:49.980 um same here with the with the update we
00:19:52.919 would use a template version of the
00:19:56.220 survey like the entire Forum we would
00:19:59.460 put it between template tags so when the
00:20:02.220 user clicks this button that template
00:20:04.500 version would come up rendered uh using
00:20:07.919 the values from indexeddb
00:20:12.140 uh so yeah pretty much what what I just
00:20:15.179 explained
00:20:17.820 um so some gotchas here
00:20:20.520 um one important thing is that
00:20:22.620 validations must match in the front end
00:20:25.380 and the back end because we don't want
00:20:27.840 uh invalid objects to be stored in
00:20:31.080 indexeddb because that would cause uh a
00:20:34.200 failure when making this synchronization
00:20:36.780 so we want every object stored in index
00:20:41.039 DB to be a valid object in active record
00:20:44.820 so this kind of kind of makes uh the
00:20:48.120 challenge of duplicating some of the
00:20:50.580 validations but it's really important
00:20:53.100 and the other thing is that it's really
00:20:55.140 important to understand
00:20:56.720 your uh audience to assess the
00:20:59.520 importance of this browser compatibility
00:21:01.500 for some of the features that I
00:21:03.900 mentioned in dxb platform background
00:21:06.000 sync because there are different amount
00:21:09.360 differences among like Android users and
00:21:12.660 iOS users
00:21:14.280 and depending of your audience's most
00:21:16.679 used browser you might have to assess
00:21:18.900 the compatibility
00:21:23.039 um so that was it I hope this was a
00:21:28.320 well I hope this this was a meaningful
00:21:31.500 presentation so PW is features can
00:21:34.260 supercharge your application you can
00:21:36.360 make suitable for anyone everywhere uh
00:21:39.900 you can start making an impact by
00:21:42.120 reaching unconventional audiences such
00:21:44.700 as people uh in rural areas
00:21:48.440 and finally stimulus is a super powerful
00:21:51.900 tool for enhancing our application with
00:21:53.940 offline features
00:21:55.440 um you you can pretty much emulate a
00:21:57.600 single page application Behavior with
00:22:00.240 like minimum uh Js
00:22:04.679 and these are these are some of the
00:22:06.659 resources I mentioned
00:22:08.640 and that was it thank you very much
Explore all talks recorded at WNB.rb Meetup
+21