Pushing the PWA Envelope

If you have a smartphone, you’re already familiar with push notifications. They’re the timely alerts with some useful info from your favorite apps. In 23.1, your APEX solutions can send them, too, using a page process or send_push_notification() API. You simply set the message details and APEX securely notifies the user on all their subscribed devices, if any.

In this article, see a sample app in action, how to try it out yourself, and where to download the slides from my recent APEX Alpe Adria conference talk with more in-depth info about push notifications in APEX.

Overview of the Sample App

The sample app uses APEX’s application access control with roles for Customer and Staff. App pages use an appropriate authorization scheme so customers can request reimbursements while staff members approve them. The app declaratively enables Progressive Web App (PWA) installation and push notifications, and the overview video below shows you how the app works on iPhone and Mac desktop PWAs for a customer user BO and a backoffice user PAT.

Regenerating the Signing Key Credentials

After downloading and importing the sample app, the first required setup step is regenerating the public/private key pair credentials. It’a is a one-time step that’s necessary the first time an APEX app using push notifications is imported into a new workspace. This is a new kind of credential used to cryptographically sign the push notifications your app sends.

In practice, it’s a one-click operation. Under Shared Components > Progressive Web App > Push Notifications, click the (Regenerate Credentials) button, and then in the confirmation dialog that appears click (Regenerate Credentials) again to confirm. Ok, you got me. It’s a two-click operation! You can regenerate the credentials any time in the future as well, but be aware that doing so invalidates and removes any existing user push notification subscriptions. Therefore, should you decide later to regenerate the credentials, your users will need to opt-in again to receive your app’s push notifications.

Creating the Two Sample Users

The sample app depends on a backoffice staff user named PAT who is configured as the approver on the Reimbursement Approval task definition and another user BO who is a customer. As shown in the demo video above, customer BO uses the app to request reimbursement of an expense, then backoffice user PAT approves or rejects the request, which results in sending the requesting user a push notification to alert them of the outcome.

If you have recently tried the Sample Approvals app from the gallery, you might already have workspace users PAT and BO, but if not then you’ll need to create them. Login to your APEX workspace as a workspace admin user and click the Administration (i.e. “person with wrench”) icon in the toolbar and choose the Manage Users and Groups option. Add the missing account(s) on this page using the Create User button.

Assigning Sample Users an Application Role

Once users PAT and BO exist, next you need to assign them an appropriate application role so that the staff member user PAT sees the approval pages and the customer BO sees the reimbursement request pages. To do this, in the context of the sample application in the App Builder, click Shared Components > Application Access Control. Use the (Add User Role Assignment) button twice on this page to add two user role assignments:

  • PAT -> Staff
  • BO -> Customer

HTTPS & Trusted Certificate Requirement

Keep in mind if you’re trying the sample on your own APEX instance that both on-device PWA installation as well as subscribing to push notifications depend on a secure, trusted connection. This means your desktop or mobile browser needs to access the APEX app over HTTPS and the client device must trust the server’s security certificate. If your APEX is running plain HTTP or it’s using a self-signed certificate that you haven’t configured your client device to trust, exploring the sample won’t work as expected. In that case, I recommend trying it on apex.oracle.com or your Oracle Cloud always free tier APEX instance instead.

Installing the Sample App as a PWA

The public APEX PWA Reference App’s Push Notifications page documents the compatibility matrix of supported operating systems and devices. If you are an iPhone user, notice that subscribing to push notifications on iOS and iPadOS require version 16.4 or later, as well as your installing the app as a PWA first. Other supported combinations allow push notifications either from the browser or when installed as a PWA. In the video above, I used Chrome on MacOS Ventura to install the PWA for user BO, Microsoft Edge to install the PWA for user PAT, and an iPhone 11 Plus running iOS 16.4 to install the mobile PWA for BO. You can use any of the supported combinations highlighted in the compatibility matrix.

I recorded the demo video on an APEX instance with instance-level settings Allow Persistent Auth set to Yes and Rejoin Sessions set to Enabled for All Sessions, so users can stay logged in for a month by default and where tapping on the push notification does not require the user to login again to see the related detail information in the APEX PWA application.

Opting-In to Receive Push Notifications

As shown in the demonstration video above, each user of your app needs to opt-in to receive push notifications from your app. And they need to do this on each device where they want to receive the notifications. When creating a new app with the Push Notification feature enabled in the Create App wizard, APEX generates a user settings modal drawer page containing the link to the Push Notification Settings page. For an existing app, there is a button on the Push Notifications page of the PWA app settings to generate the user settings page with a single click. At runtime, if the settings page shows a “Not Supported” badge, check to make sure you’re using HTTPS and a trusted certificate. If tapping or clicking on the Enable push notifications checkbox produces an error, that’s a signal you probably forgot to regenerate the push notification key pair credentials after importing the app the first time.

Using an App Page as Push Notification Target URL

When sending a push notification, you can configure a target URL that the device uses when the user taps or clicks on the notification. It must be an absolute URL, so for example the Reimbursement Approval task definition in the sample app contains an action that executes in response to the Complete event. Its action PL/SQL code sends the push notification about the task approval or rejection using the following code:

apex_pwa.send_push_notification(           
    p_user_name  => :CREATED_BY,
    p_title      => 'Reimbursement '||initcap(:APEX$TASK_OUTCOME),
    p_body       => 'Your reimbursement of ' || :AMOUNT || 
                    ' from ' || :RECEIPT_FROM || 
                    ' was ' || lower(:APEX$TASK_OUTCOME)||'.',
    p_target_url => apex_util.host_url||
                    apex_page.get_url(
                       p_page   => 'reimbursement-notification',
                       p_items  => 'p7_id',
                       p_values => :APEX$TASK_PK)          
);

Notice how the value being passed to the p_target_url parameter prefixes the result of the apex_page.get_url() result by the apex_util.host_url expression. This ensures that the URL is a fully-qualified absolute URL. When using a declarative Send Push Notification page process, the APEX engine handles this for you, so this tip only pertains to the send_push_notification() API.

When the target URL is a page of your APEX app, the page must have the following properties configured:

  • Authentication = Public
  • Deep Linking = Enabled
  • Rejoin Sessions = Enabled for All Sessions
  • Page Protection = Arguments Must Have Checksum

This public target page will typically use a Before Header branch to redirect to an authenticated page in the app, passing along appropriate parameters to show the user the expected detail information for the notification. When combined with the use of APEX’s persistent authentication “Remember me” functionality, this combination gives the most seamless user experience. Page 7 in the sample app, whose alias reimbursement-notification appears in the send_push_notification call above, meets all of these requirements and performs the forwarding to the authenticated page 6, passing along the reimbursement request id to show the end-user the request details.

Granting Outbound Network ACLs for Push Services

The APEX engine sends push notifications by invoking secure notification REST services from Apple, Google, Microsoft, and Mozilla depending on the subscribing user’s device. If you try the sample app on apex.oracle.com, you won’t have to worry about granting outbound network access for these notification service domains, however it’s a task you must perform when you use push notifications on your own APEX instance. In case you need it, the following PL/SQL block is an example of how to grant the appropriate ACLs to the push notification REST API domains and optional a proxy server if you are behind a corporate firewall. If your APEX applications are already using REST services, you will likely already be familiar with these steps. It’s included here for reference:

-- Run as SYS or DBA user
declare
    l_principal varchar2(20) := 'APEX_230100';
    l_proxy varchar2(100)    := null; -- e.g. 'proxy.example.org'
    l_proxy_port number      := 80;
    l_hosts apex_t_varchar2  := apex_t_varchar2(
                                '*.push.apple.com',
                                '*.notify.windows.com',
                                'updates.push.services.mozilla.com',
                                'android.googleapis.com',
                                'fcm.googleapis.com');
    procedure add_priv(p_priv varchar2, p_host varchar2, p_port number) is
    begin
        dbms_network_acl_admin.append_host_ace (
            host       => p_host, 
            lower_port => p_port,
            upper_port => p_port,
            ace        => 
                xs$ace_type(privilege_list => xs$name_list(p_priv),
                            principal_name => l_principal,
                            principal_type => xs_acl.ptype_db));
    end;
    procedure add_priv_resolve(p_host varchar2) is
    begin
        dbms_network_acl_admin.append_host_ace (
            host       => p_host,
            ace        => 
                xs$ace_type(privilege_list => xs$name_list('resolve'),
                            principal_name => l_principal,
                            principal_type => xs_acl.ptype_db)); 
    end;
begin
    if l_proxy is not null then
        add_priv('connect',l_proxy,l_proxy_port);
        add_priv_resolve(l_proxy);
        add_priv('http',l_proxy,l_proxy_port);
    end if;
    for j in (select column_value as hostname from table(l_hosts)) loop
        add_priv('connect',j.hostname,443);
        add_priv_resolve(j.hostname);
        add_priv('http',j.hostname,443);
    end loop;
    commit;
end;

Configuring Wallet to Validate Certificates

On your own APEX instance, in addition to the outbound REST service ACLs required for push notification, you may also need to add certificates into the wallet your instance is using for validating secure HTTP communications. If you are running APEX in the Oracle Cloud, this step should not be necessary. However, on your own instance you may find my colleague Daniel Hochleitner’s open-source Oracle CA Wallet Creator script useful for that purpose.

Downloading the Sample App

You can download the sample app from here.

Downloading My Slides

You can download the slides from my recent talk on this topic at the APEX Alpe Adria 2023 conference from here: APEX 23.1: Native Push Notifications & Easy Background Processing.

Enhancing Existing APEX App with PWA, Nav Bar Items & Smart Filters

I was excited to try three powerful new no-code-required APEX 21.2 features in my art tracker application, and I finally had an hour to spare after work this week to give it a go. This article documents my successful attempt to evolve the showcase page from my existing app to leverage the space-saving Smart Filters search region, leaving more room for the cards that visualize the main application content.

In the process I regained even more screen real estate by positioning items in the navigation bar using the new layout positions available in 21.2’s Universal Theme. And last but not least, I enabled users of my app to install it like a native desktop or mobile application for quick access from the home screen or dock and faster startup time. Read on if any (or all!) of these features sound like they might be a useful upgrade in your own APEX application.

The screenshot below shows the main page of my application before the enhancements. It employs a faceted search region and two select-list page items in a Left Column layout position to let users find the artworks they are looking for. They can also switch dynamically among several useful orderings and interactively change the display size of the images. These complement a Cards region showing the works of art they are tracking.

Main page of an APEX 21.1 art collection app using faceted search and cards region

Setting the Stage

While none of these three new features required writing any code, the simplest of all to enable was the installable Progressive Web App capability. I first needed to ensure my app was using Friendly URLs, as shown below in the application definition’s Properties tab. While I was here, I also changed the Compatibility Mode to 21.2, which is a prerequisite to refreshing its Universal Theme version to 21.2. As we’ll see shortly, this theme refresh allowed me to access the new, flexible layout positions in Page Designer.

Ensuring friendly URLs are used and setting compatibility mode to 21.2

Installable Native App in Two Clicks

With these two settings configured appropriately, I proceeded to enhance my application to allow installation on desktop or mobile devices. As shown below, on the Progressive Web App tab of the application definition, I just needed to switch on the Enable Progressive Web App toggle, as well as the Installable toggle. The former enables the device to cache key application resources for faster startup, while the latter lets users add the app to their home screen or dock to launch it like every other app on their computer, phone, or tablet.

Turning your app with friendly URLs into an installable native app in two clicks

Adding Existing Items to the Nav Bar

The new page item positions are made available by the 21.2 version of the Universal Theme. Since my existing application is using the theme’s 21.1 version, the new positions won’t show in Page Designer until I’ve refreshed the theme to version 21.2. Above, we set the application’s Compatibility Mode to 21.2. This is a required step to be able to refresh our theme to the latest 21.2 version that supports the new page item positions.

Refreshing the Theme

From my application’s Shared Components page, I clicked on Themes. My application only uses a single theme “Universal Theme” (number 42), so I clicked on its name to edit it. On the Edit Theme page, as shown below, clicking the (Refresh Theme) button gets the job done. If by chance I had forgotten to update the application compatibility mode to 21.2, I would have seen a helpful error message telling me to go do that first before attempting again to refresh the theme.

Refreshing Universal Theme to latest 21.2 version to see new component positions

Seeing the New Page Positions in Page Designer

After updating the Universal Theme, editing the page in Page Designer shows the new positions. The one I was specifically interested in is Before Navigation Bar. This will allow me to position my sort control, size control, and new smart filter search field into the previously unused space in that bar. If you don’t see the Before Navigation Bar position (or any of the other new ones) you might have hidden the empty positions. If so, then as shown below, right-click on the layout panel and ensure that Hide Empty Positions is unchecked.

Existing art collection page in Page Designer before starting the changes

Moving Existing Items/Regions into New Positions

I had previously grouped the two select lists that control the dynamic sorting and card image size into a static content region called Sort. I could have simply set the Layout Position property of this Sort region to Before Navigation Bar, but I wanted to see how individual items could be placed into the new positions without belonging to a region, so instead I did the following for both the P18_ORDER_BY and P18_ZOOM page items:

  • Set the Layout Parent Region property to No Parent.
  • Set the Layout Position property to Before Navigation Bar

Changing Faceted Search to Smart Filters

My existing page was using the Left Side Column template, and the existing faceted search region (named Filter) resided in the template’s Left Column position. So I started by selecting the Filter faceted search region and changing its Layout Position property to Before Navigation Bar.

After doing this, I no longer needed the Left Side Column page template, so I set the page template to Theme Default to free up the space currently taken up by the left column, and deleted the now-empty Sort static content region. After, right-clicking on the layout pane to select Hide Empty Positions, the in-progress page looked like what you see below.

In-progress work after moving items and Filter region to Before Navigation Bar position

Next, I needed to change the region type of the Filter region from Faceted Search to Smart Filters. However, before doing this, for each facet I made a note of the following three elements of its definition:

  • Facet name (e.g. P18_ARTWORK_TYPE)
  • List of Values name (e.g. TYPE_LOV)
  • Icon name (e.g. fa-shapes)

This step simplified my recreating the same filters under the Smart Filter region. At the moment APEX does not automatically convert the existing facets into filters when the region type changes from Faceted Search to Smart Filters. However, with the information noted down above regarding the facet names, LOV names, and icon names, setting up the new filters was just a few additional clicks. So, in practice this missing automatic conversion was not a big deal.

After changing the Filter region’s type from Faceted Search to Smart Filters, an initial filter named P18_SEARCH got created for me. This is the mandatory search filter that will search on row text by default. Next I proceeded to recreate the five additional filters using the same names, corresponding List of Values, and icon names that the facets were using previously. I kept the default Checkbox Group type for each filter I created, and finally (based on a tip from my colleague Vincent) I set the CSS class on the Filter smart filter region to w100p so that it would stretch to fill up the space in the navigation bar. Then, I ran the page to test out the upgraded functionality.

As shown below, by default the Smart Filters region shows a suggestion “chip” for each filter category below the search bar with the most popular value and a record count for each filter type. [Note: the record counts have been replaced in the screenshot by (..)]

By default, the Smart Filter search region shows suggestion chips for each filter with counts

These suggestion chips simultaneously educate the end-user about the available filters as well as provide useful information about the most popular value for each category. Clicking on the label of a particular filter chip, the end-user can select specific value(s) they want to search for in that category. In contrast, clicking on the suggestion chip value, applies that most-popular value to the user’s search.

For my particular application, I preferred to further streamline the navigation bar by disabling these suggestions. To suppress the suggestion chips, I set the Maximum Suggestion Chips to the value zero (0) on the Attributes tab of the Filter region as shown below.

Disabling the Smart Filter suggestion chips for a region if desired.

Installing the App on Windows and iPad

After saving this Page Designer change and re-running the page, I saw the result below without the suggestion chips. Notice the new (Install App) button in the navigation bar. When I enabled the Progressive Web App feature above, the APEX builder added a new item into the Navigation Bar list which advertises the ability for the user to install the application on their device.

Enabling PWA with the Installable option adds a new (Install App) navigation bar button

Clicking on the (Install App) button in Chrome or Edge desktop browser let me install the application in one click on my desktop. I tried the same thing on my iPad and there the app can also be installed using the native iOS gesture of clicking on the share icon and choosing “Add to Home Screen” as shown below.

Adding your installable APEX application to the iPad’s home screen

This allowed me to long-press on the home screen icon for the application to enter “wiggle mode” so I could drag it into to my iPad’s dock like this:

Native app icon for an APEX application in the iPad dock

Clicking this new iPad dock icon launched the app into a full screen experience indistinguishable from other native apps on my iPad as shown below. Also notice that the (Install App) navigation bar button no longer displays, so APEX has configured the new navigation bar list entry to be conditionally displayed only when it makes sense. A nice touch!

Full screen iPad app user experience looks like every native app

Trying the Smart Filters User Experience

Back on my Windows desktop machine, I repeated this same experience of launching the application from the dock and noticed it now displayed in its own window with no browser address bar as shown below. Clicking into the Smart Filters search field, the user sees which filter categories are available and can click one of them to choose specific values to search for in that category…

Filter categories suggest the smart ways the user can narrow their search

Alternatively, the user can just start typing some text to search for. As shown below, after typing the two characters gr into the search field, the Smart Filters region shows matches in any appropriate categories. It’s showing some artists (e.g. Gregory Mason, Rich Pellegrino), an artwork type (Photography), and some sources (i.e. stores) like Ross Art Group and others, all which contain a “gr” in their name. If desired, clicking on one of these filter category matches will apply that filter to the search for the respective category.

Filter search matches show in the drop down as the user types

The user can ignore the filter category suggestions and just keep typing, for example, to enter the search term grand and upon pressing [Enter] they’ll see the artworks that contain grand in their row text (title plus other card text) as shown below.

Resulting show all types of artwork with “grand” in their title

Clicking again into the search field, suppose the user clicked on the Type filter category and selected Painting. That would result in the situation shown below where the results have been narrowed to only show paintings with “grand” in their title.

Results filtered to show only paintings with “grand” in the title

Clicking back on the “Type: Painting” filter chip in the search bar, the user can select other types of artwork to also include as shown below. The set of choices available is automatically filtered to reflect the narrowed search results so other types of artwork like “Animation Cel” are not in the list since no animation cel in the collection contains the word “grand” in the title or descriptive text.

Adding additional types of artwork to the search

After further narrowing the results by Decade and Source, the result looks like what you see below. Clicking back into the search field only shows those filter categories that are not already involved in the filter.

After applying multiple filters, only unused filter types show in the dropdown

I was very satisfied with the functionality and additional screen space I gained in my application by uptaking these new features. In short order, without having to write any code, I evolved my application to give users more room to see the primary subject of the application (the artwork cards!) while gaining a compact searching interface every bit as powerful and smart as the faceted search I was using before.

What’s more, enabling the installable Progressive Web App (PWA) feature let my users have a more native desktop and mobile experience that looks like all the other apps on their machine when they’re using my app.

Of course, in many use cases users may appreciate a more “AirBnB”- or “Amazon”-style search experience with the faceted search items always visible, but the new Smart Filters region gives developers a new option to consider when users may appreciate the powerful search capability to be more compact so other page content can be the star of the “show”.

I encourage you all to give these three new features a try!

P.S. In the process of doing the enhancements above, I also simplified the P18_ORDER_BY and P18_ZOOM select list items to have no visible label and fiddled with CSS styling to get these two items to be positioned and sized exactly how I wanted them in the navigation bar. But as I’m not a CSS expert, I didn’t want to offer any advice on what I did to achieve the final result since I’m not certain yet that it’s the best way. Once I have a chance to check with colleagues with more CSS expertise, I’ll either write a new blog article about that or update this article with more specific advice.