Making an In-App WebView message
Introduced in Batch 1.17, In-App WebView messages enable you to display any web content in your app, while still leveraging Batch's campaign targeting, push and In-App trigger engine.
Any HTML content can be displayed. A handful of advanced fullscreen or modal scenarios are therefore possible:
- Ultra-customized look and feel and layout
- Multi-screens In-Apps
- Customer surveys, quiz, NPS, forms (data collection)
- Video players
Batch In-App WebView JavaScript SDK
Batch provides a JavaScript SDK that enables your message's JS code to communicate with the native SDKs.
It provides the following features:
- Message dismissal
- Opening deeplinks
- Triggering Batch Actions (Android / iOS)
- Getting installation data, such as the Installation ID
- Getting campaign data, such as the Custom Payload or Tracking ID
The JavaScript SDK makes heavy uses of Promises to asynchronously provide results.
All available methods are documented on GitHub. TypeScript definitions are published on the npm package.
Script Integration
Batch's In-App WebView SDK exposes its API in a global object named window.batchInAppSDK
.
Basic integration
Simply add the script to your page. As the script is very light, we encourage you to synchronously load it in so that it is always available:
<script src="https://cdn.jsdelivr.net/npm/@batch.com/in-app-webview-sdk@1.0/build/batch-webview-sdk.min.js"></script>
You are free to download and rehost this script.
Integration using NPM and a module bundler
The SDK can be added using a module bundler, such as webpack.
All you need to do is install the @batch.com/in-app-webview-sdk
npm package, and import it.
As the SDK sets up a global variable, its import should not be used directly:
import _ from '@batch.com/in-app-webview-sdk'
console.log(window.batchInAppSDK.getPlatform())
TypeScript typings will automatically be available.
Using Batch Actions
Batch Actions (Android / iOS) both built-in and custom can be triggered using JavaScript.
Triggering an action will dismiss the message.
batchInAppSDK.performAction('action_name', {})
// An [analytics ID](#analyics) can be optionally provided
batchInAppSDK.performAction('action_name', {}, 'my_button_id')
// Example: track an event
const eventParameters = {
e: 'survey_reply', // Event name
l: '2021_opinion', // Event label
a: {
// Event attributes
response: 'option_a',
},
}
batchInAppSDK.performAction('batch.user.event', eventParameters)
Deeplinks
The JS SDK can open deeplinks of any kind: both app-handled deeplinks and HTTP websites are supported.
Just like actions, opening a deeplink will dismiss the message.
You can control whether HTTP/HTTPS links should be opened in an in-app browser, or in the user's default browser app. If not specified, the campaign setting will be used.
As with any action related API, an analytics identifier can be provided.
// Opens batch.com
batchInAppSDK.openDeeplink('https://batch.com')
// Opens batch.com in an In-App browser, if possible
batchInAppSDK.openDeeplink('https://batch.com', true)
// Opens batch.com with a "my_button" analyticsId
// "undefined" as the second parameter tells Batch to use the default 'open in an in-app browser' campaign setting
batchInAppSDK.openDeeplink('https://batch.com', undefined, 'my_button')
// Opens an application deeplink
batchInAppSDK.openDeeplink('myapp://mycontent')
For more information about how deeplinking works, scroll down to Link Handling.
?batchAnalyticsID
isn't supported on oppenDeeplink
: use the analyticsId method parameter.
Note:
batchInAppSDK.openDeeplink()
is functionally equivalent tobatchInAppSDK.performAction("batch.deeplink, ...)
, or<a href="..." target="_blank">
.
Accessing installation data and identifiers
Installation ID
Batch's installation ID can be accessed from inside your message:
batchInAppSDK.getInstallationID().then(installationID => /* ... */)
Custom Language and Region
It is possible to read a language/region override that you have previously set on the native SDKs (Android / iOS).
You can access this in two ways:
- Client-side, via the JavaScript SDK:
batchInAppSDK.getCustomRegion().then(/* ... */)
batchInAppSDK.getCustomLanguage().then(/* ... */)
- Server-side, via HTTP headers:
X-Batch-Custom-Language: en
X-Batch-Custom-Region: US
Note: if the language/region has not been overriden, the header and JavaScript APIs will not reflect the default user language and region: the headers will be missing, and JS APIs will resolve with "undefined". Use standard HTTP headers and browser APIs to get the user's default language.
Advertising ID
If your application can use the Advertising ID, it can be retreived using JavaScript:
batchInAppSDK.getAdvertisingID()
.then((advertisingID) => {
/* Do something with it */
})
.catch(e => {
/* Failed to get the advertising ID */
console.error(e)
};
When getting the Advertising ID fails for any reason, the SDK will throw an exception. Make sure you add a .catch()
handler.
An unavailable Advertising ID can be because of any of the following:
- You asked Batch to disable AdvertisingID/IDFA support when configuring the native SDK.
- Your Android application does not have the required library to retrieve the Advertising ID
- Your iOS application doesn't link with
AdSupprot.framework
or doesn't implement App Tracking Transparency. - Tracking is restricted by user preferences
- The OS didn't provide any Advertising ID
Tracking ID
Tracking ID matches your campaign's Tracking ID
field.
This is usually meant to be used with Event Dispatchers, but can be accessed in your message and used with your analytics solution.
batchInAppSDK.getTrackingID().then((trackingID) => {
// trackingID = 2021_trip_discount
})
Custom payload
Custom payload can be read as a simple JS object. If the message is displayed in a Mobile Landing, you will get the complete custom payload attached to the push notification.
// Get a promocode from the custom payload
batchInAppSDK.getCustomPayload().then((payload) => {
const promocode = payload['promocode']
})
Types
The custom payload value types are preserved in the following contexts:
- iOS, both when opened via a Push Notification (Mobile Landing) and an In-App Campaign.
- Android, when opened via an In-App Campaign.
Reading the custom payload while in a mobile landing on Android differs quite a bit: Due to a Firebase limitation, all values are of string type. Complex objects are stringified in the JSON notation, meaning that subtypes are preserved.
For example, the source custom payload (as set on the dashboard)
{
"int_value": 1234,
"complex_value": {
"my_int": 4567
}
}
becomes, when read in a WebView opened from a Mobile Landing:
{
"int_value": "1234",
"complex_value": "{\"my_int\": 4567}"
}
Make sure your custom payload handling code can detect and parse non-string values, or consider using string values on all platforms and mediums.
Example:
batchInAppSDK.getCustomPayload((payload) => {
let complexValue = payload['complex_value']
if (typeof complexValue === 'string') {
// We're getting a serialized JSON as we're on an Android mobile landing
complexValue = JSON.parse(complexValue)
}
const myInt = complexValue['my_int'] // 4567
})
Analytics
Analytic identifiers (not to be confused with the Tracking ID) can be attached to deeplinks, actions and dismissal via the JS SDK or batchAnalyticsID
for links.
The analytic identifiers should:
- Have limited cardinality. There is a per campaign limit on how many different analytic identifiers will be.
A best practice is to use dedicated analytic identifiers that are not localized (therefore not using the button/link label). - Not be empty, or full of spaces.
- Not be longer than 30 characters.
Invalid tracking identifiers will be ignored.
More info about the Analytics ID can be found on the dashboard documentation.
Link handling
Standard HTML <a>
links are supported:
- Simple links will navigate inside of the format. Be careful if you're navigating to a website you do not control: Batch does not show any standard browser navigation control.
<a target="_blank">
will dismiss the message and open the link in an external browser, or an in application browser (SFSafariViewController or a Chrome Custom Tab) depending on your campaign settings. This is like callingbatchInAppSDK.openDeeplink(url)
.
Some URLs will force a certain behavior:
- Links that have a scheme that is not HTTP/HTTPS (such as itms://) will be handled as a deeplink, dismissing the message and being redirected to the host OS.
- Links that can be handled by an application might dismiss the format and redirect the user to the app. Whether this happens or not depends on user settings.
- App Store/iTunes Store links always dismiss the message and open the store.
Adding an analyticsId
on standard links is also supported if the link is attributed with target="_blank"
: add a batchAnalyticsID
query parameter to the URL.
Example: https://batch.com/?batchAnalyticsID=my_click_id
is the same as doing batchInAppSDK.openDeeplink('https://batch.com', undefined, 'my_click_id)
.
Special links and _blank
navigation are ignored when opened inside an iframe. Only the main frame will get link handling: iframes can only navigate to a webpage.
Interception
Any link that is not opened inside of the WebView (that is, a link that dismisses the message before being opened) is handled by Batch's deeplink handling.
This means that like push deeplinks, or native In-App formats deeplinks, links will go through BatchDeeplinkDelegate
on iOS and BatchDeeplinkInterceptor
on Android.
Dismissal
Batch will always show a native close button on top of your web content. Android's back button is also supported as expected by users.
If you want to trigger a dismissal, you can use JavaScript:
batchInAppSDK.dismiss()
. An Analytics ID can be set as the first parameter:batchInAppSDK.dismiss("ask_later")
.window.close()
Optimizing content presentation
Viewport
When displaying web content, iOS and Android might adopt a desktop viewport. Text and UI Elements might become unreadable.
To control this, use a <meta name="viewport">
in <head>
.
The most common viewport configuration to use for In-App WebViews should:
- Set the content to use the device's native size
- Disable user-controlled zoom
- Display content at 100% scale
This can be achieved using the following:
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0" />
You can get more documentation about viewport configuration on MDN.
Note: iOS implements the 'viewport-fit' content attribute. See iOS Safari Specificities for more info.
Responsive design
Be aware that your message might be displayed in virtually any display size and aspect ratio:
- iPhones (from an iPhone 5 to 12 Pro Max)
- Android phones
- Landscape phones
- Tablets both portrait and landscape
- Tablets in split screen mode (1/3 of the screen) or free floating windows
Use responsive web design techniques (CSS Media queries, Flexboxes, Grids, etc...) to handle this. Firefox, Chrome and Safari all come with tools that allow you to test your content on various form factors.
Light and Dark mode
WebViews support light and dark mode on both iOS and Android.
Use the prefers-color-scheme CSS media feature to change your style accordingly:
#content {
background-color: lightgray;
color: black;
}
@media (prefers-color-scheme: dark) {
#content {
background-color: black;
color: white;
}
}
Video
≥ 1.18.0≥ 1.19.5
WebViews can be used to display video messages. To do so, you will need to wrap your video in HTML: directly linking a video as your WebView URL is not supported.
Videos will only autoplay if they have the autoplay
and muted
HTML attributes:
Example:
<html>
<style>
/* Put a HTML5 reset and a background color here */
</style>
<body>
<video autoplay loop muted playsinline>
<source src="https://my-cdn/video.webm" type="video/webm" />
<source src="https://my-cdn/video.mp4" type="video/mp4" />
</video>
</body>
</html>
We encourage you to set multiple sources in different format and codecs. That way, you can serve smaller videos to supported browsers, while other can fall back on a more compatible format. See the <video>
tag documentation for more info.
Note that in Low Power mode, videos will not autoplay on iOS. There is no way to change this behaviour: you will need to add a cover photo.
Low Power Mode
You should test your webpage in Low Power Mode (also often known as Battery Saver) on both iOS and Android as browsers might change behaviour in this mode:
- Animations might skip frames or be skipped altogether
- Videos might not be able to be autoplayed
Browser Specificities
iOS
Batch uses the system WKWebView on iOS devices.
WKWebView uses Safari's engine, which is updated with iOS but not via the App Store.
This is quite different to what you may be used to on desktop, where major browsers like Firefox and Chrome are automatically updated.
Can I use... is a great ressource to see what a specific Safari version supports.
Safe Area
Modern iPhones and iPads have rounded screen corners, and sometimes a notch (that little black bar where the front cameras are). The screens end up having a non rectangular shape, and content might go under the rounding or the notch itself: users will not be able to see and interact with it.
Apple introduced a concept called "safe area": a rectangular shape where it is guaranteed that 100% of your content will be shown on screen. The default setting for web content is to limit it to the safe area. Borders will be shown around your content, that Batch will fill with your theme's static background color.
Whether your web content will expand into the "unsafe area" (which is the entire screen) is controlled by your <meta name="viewport">
tag.
By using viewport-fit=cover
to your viewport, you tell iOS that you're aware of the screen cutouts, and will handle it.
If you do, you will need to add padding on your content using the env()
CSS property. More info on the MDN. Failing to do so will make some content unreadable to the user.
This can be a tricky concept to understand, so here are some examples:
-
The first screenshot has the following viewport:
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0">
.
The "unsafe area" has been highlighted in purple for exaggeration. Note how web content is automatically put below it. -
The second screenshot has this viewport:
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, viewport-fit=cover">
, opting in to drawing in the unsafe area. It failed to take it into account using CSS: content ends up behind the system clock, indicators and notch. -
The third screenshot uses the same viewport as the second, but has the following css rule on its content div:
padding: env(safe-area-inset-top, 0px) env(safe-area-inset-right, 0px) env(safe-area-inset-bottom, 0px) env(safe-area-inset-left, 0px);
.
The web content expands in the unsafe area and draws its white background, hiding the theme's purple color.
Due to layouting bugs, avoid using 100vh
or any vh
related unit when using viewport-fit=cover
. Use height: 100%
instead.
Note: Do not hardcode margin/padding values mesured from env(). They are not the same for every device, especially when on an iPad.
Android
Batch uses the system WebView on Android devices.
The Android System WebView is based on Chromium and updated by Google via the Play Store: this means that even though your app may be running on a device running an old Android version, you can still use modern web technologies as the underlying Chrome version will be up to date.
As the rendering engine is Chrome, it behaves almost exactly like the Chrome application on an Android phone: if your message displays properly in it, it will most likely work as is in a Batch campaign.
Note: It is not possible to draw content behind the status bar and navigation bar using a HTML message.
Hosting
Requirements
Batch requires your In-App WebView messages to be served over HTTPS/HTTP like a standard webpage. There are no special requirements: if your hosting works in a browser, it will most likely work in an In-App message.
Be aware that Batch does not cache your message content: depending on your target audience, you might experience high peak loads.
A relatively cheap and reliable solution is to use a CDN (such as Cloudflare) or any service specialized in static content hosting (such as Netlify or Vercel). Free plans can sometimes be enough for your use cases, but make sure you check what the limits and the billing conditions are.
Response handling
Batch's WebViews will display any HTML page returned with a 2xx status code. Redirections are followed.
Custom 4xx/5xx error pages will not be displayed but will dismiss the format automatically.
Using plaintext HTTP
While we heavily discourage this practice, it is possible to use insecure HTTP URLs for your messages.
Android and iOS applications are by default configured to block plaintext networking. You will need to explicitly allow it:
- Android: Network security configuration. Set
cleartextTrafficPermitted
to true. - iOS: Configure App Transport Security. Use
NSAllowsArbitraryLoadsInWebContent
.
Note: Changing those settings might make your entire app more vulnerable. Please consider using HTTPS if possible.
Previewing your message on the dashboard will not be possible.
Development
During development, you can use any static HTTP server to serve your pages to your test devices:
php -S 0.0.0.8081
# or
python3 -m http.server 8081
# or
npx http-server -p 8081
Trying out your message
In order to try your message, you first have to host it.
Once that is done, follow the steps described in How do In-App WebViews work? to create a theme and fill out your In-App campaign on the dashboard.
Set your URL and any other field you'd like to configure, then either send yourself a test push notification or save the campaign with a limited audience to get it on your test device.
Note that as In-App WebViews are webpages first and foremost, it is a good idea to test them first on iOS Safari and Android Chrome. Testing them only on your desktop browser is not fully accurate.
Debugging
iOS
Development mode
When In-App WebView messages are displayed in development mode. This mode is only (and automatically) enabled when using the test push feature of the dashboard.
Development mode:
-
Explicitly shows why a message is dismissed because of an error (non 200 status code, SSL error, etc...)
Inspector
Web content opened on iOS can be debugged using Safari on macOS.
In order to show up in Safari's debug menu, your app needs to have been provisioned for development or ran in the iOS Simulator. Testflight or App Store apps can not be debugger.
First, you will need to enable Safari's develop menu.
Open Safari, and open its preferences. Go to Advanced
and check Show Develop menu in menu bar
.
Then, open an In-App WebView message in your application (either on a device or on a simulator).
Go back to safari, open the Develop
menu. You should be able to find your device/simulator here.
Click on the menu item to open a remote inspector.
Once the remote inspector is open, you can use it as you would on desktop Safari.
Android
Development mode
When In-App WebView messages are displayed in development mode. This mode is only (and automatically) enabled when using the test push feature of the dashboard.
Development mode:
-
Explicitly shows why a message is dismissed because of an error (non 200 status code, SSL error, etc...)
Inspector
Web content opened on Android can be debugged using Chrome on Windows, Linux and macOS.
In order to show up in Chrome's debug menu, your app needs to enable WebView debugging explicitly and your device needs USB debugging to be enabled.
To enable WebView debugging, add in your Application
subclass' onCreate()
:
WebView.setWebContentsDebuggingEnabled(true);
Note: This makes WebViews debuggable even if your application's
debuggable
manifest attribute is false. See Google's documentation for a way to only enlable this for debug builds, or remove this when compiling for production.
First, please follow Google Chrome's tutorial on how to enable remote debugging if you have not already.
Then, open an In-App WebView message in your application (either on a device or on a emulator).
Go back to Chrome and open chrome://inspect
using the adress bar. You should be able to find your device/emulator here.
Click on inspect
to open a remote inspector.
Once the remote inspector is open, you can use it as you would on desktop Safari.
Samples
Sample HTML messages can be found on our public github repository.
They implement the best practices (mobile viewport, dark mode support), and illustrate the use of the In-App WebView Javascript SDK.