Android Embedded Content
Embedded Content is an alternative Scene format that can be displayed on any app or web screen in a view defined by a developer. It can also be presented in Story format. Android SDK 18.1.4+
About Embedded Content
Present the content of a SceneA single or multi-screen in-app experience cached on users’ devices and displayed when users meet certain conditions in your app or website, such as viewing a particular screen or when a Custom Event occurs. They can be presented in fullscreen, modal, or embedded format using the default swipe/click mode or as a Story. Scenes can also contain survey questions. on any app screen. There are three primary components:
Component | Description |
---|---|
A "view" in your app where the content will display | An app developer creates an AirshipEmbeddedView that controls the dimensions of the content and its location in your app. They also determine what content can be displayed in the view by setting a value for the view's embeddedId that matches the ID of an Embedded Content view style. |
A view style in your project settings | A marketer creates an Embedded Content view style and assigns an ID for reference in the app view's embeddedId . |
A Scene using an Embedded Content view style | This is the source of the content that will be displayed in the view. |
Once the Scene is triggered for display and matches the specified audience conditions, its content is available to users when visiting a screen that contains the AirshipEmbeddedView
.
The view is populated with the content from all active Scenes with the matching ID, sorted based on priority. When an embedded view loads content, the highest priority Scene that is queued for display will be shown. The same content will be shown across app and web sessions until it is dismissed by the user or is no longer available. The view will then show the next Scene with the highest priority from the display queue.
Airship first prepares the content when the triggering event occurs and then refreshes it upon every app open or web session start. This ensures that users always experience the most up-to-date message. So, after updating active Embedded Content, users will see the latest version the next time they open the app or load the app or web page containing the view.
Embedded Content behavior is the same as In-App AutomationsMessages cached on users’ devices and displayed when users meet certain conditions within your app, such as viewing a particular screen or opening the app a certain number of times. and modal and fullscreen Scenes:
- The content displays only within the app.
- When the app is terminated, the content is not automatically dismissed. It continues to display in the next app session.
You can set up Embedded Content for Android using Jetpack Compose or XML Views. If you are not already on Android SDK 18.1.4+, see the Airship Android SDK 17.x to 18.0 migration guide .
Jetpack Compose setup
Embedded Content support for Jetpack Compose is provided by an extension library, which must be declared as a dependency of your project.
Gradle dependencies
dependencies {
val airshipVersion = "17.8.0"
// Other Airship dependencies...
implementation("com.urbanairship.android:urbanairship-automation-compose:$airshipVersion")
}
All Airship dependencies included in the build.gradle.kts
file should all specify the exact same version.
dependencies {
def airshipVersion = "17.8.0"
// Other Airship dependencies...
implementation "com.urbanairship.android:urbanairship-automation-compose:$airshipVersion"
}
All Airship dependencies included in the build.gradle
file should all specify the exact same version.
Adding an AirshipEmbeddedView
The AirshipEmbeddedView
is a Composable UI element that defines a place for Airship Embedded Content to be displayed. When defining an AirshipEmbeddedView
, specify the embeddedId
for the content it should display. The value of the embeddedId
must be the ID of an Embedded Content view style in your project.
import com.urbanairship.automation.compose.AirshipEmbeddedView
@Composable
fun HomeScreenBanner() {
// Show any "home_banner" Embedded Content
AirshipEmbeddedView(
embeddedId = "home_banner",
modifier = Modifier.fillMaxWidth().height(300.dp)
)
}
Placeholders
If no content is available to display, the embedded view can optionally show a placeholder. The placeholder can be configured by providing a composable lambda that defines the placeholder content. If no placeholder is set, the embedded view will use the default behavior:
- If content is available for the
embeddedId
, theAirshipEmbeddedView
will display it within your composition. - If no content is available for the
embeddedId
, theAirshipEmbeddedView
will not be visible. - Compose previews will show a default placeholder that displays the
embeddedId
.
AirshipEmbeddedView(
embeddedId = "home_banner",
modifier = Modifier.fillMaxWidth().wrapContentHeight()
) {
Text("Placeholder!", Modifier.align(Alignment.Center))
}
Placing in a Scrolling Container
When placed directly in a scrolling Composable, or in a nested Composable within the scrolling parent that is not bounded in
the scroll direction, you must provide the parent’s size for the corresponding dimension of the embedded view. This enables
percent-based sizing to work correctly. A simple way to accomplish this is to use the onSizeChanged
modifier to store
the size of the scrolling parent (or another ancestor) so that the size can be passed to the embedded view via the
parentWidthProvider
or parentHeightProvider
arguments.
val scrollState = rememberScrollState()
var parentHeight by remember { mutableIntStateOf(0) }
Column(
modifier = Modifier.fillMaxSize()
.onSizeChanged { parentHeight = it.height }
.verticalScroll(scrollState)
) {
AirshipEmbeddedView(
embeddedId = "home_banner",
parentHeightProvider = { parentHeight },
modifier = Modifier.fillMaxWidth()
)
// ...
}
Placing in a Lazy Container
An approach similar to the above method can be used for sizing embedded views inside of Lazy scrolling containers, such as LazyColumn
or LazyRow
. It’s important to remember to hoist the embedded view state above the Lazy container so that the embedded view can be recycled and re-created properly. You can do this by calling rememberAirshipEmbeddedViewState
and passing the embedded view ID as an argument, which returns an embedded view state-holder instance for the given embeddedId
.
In the example below, you’ll notice that the AirshipEmbeddedView
call doesn’t include an embeddedId
argument. This is because the embeddedId
is provided by the remembered AirshipEmbeddedViewState
instance.
val lazyListState = rememberLazyListState()
// Hoist the embedded state above the LazyColumn.
val embeddedViewState = rememberAirshipEmbeddedViewState(embeddedId = "home_banner")
var parentHeight by remember { mutableIntStateOf(0) }
LazyColumn(
state = lazyListState,
modifier = Modifier.fillMaxSize()
.onSizeChanged { parentHeight = it.height }
) {
item {
AirshipEmbeddedView(
// The embeddedId of "home_banner" from embeddedViewState
// will be used by the embedded view.
state = embeddedViewState,
parentHeightProvider = { parentHeight },
modifier = Modifier.fillMaxWidth()
)
}
// ...
}
XML Views setup
You can use XML Views instead of Jetpack Compose.
Adding an AirshipEmbeddedView
The AirshipEmbeddedView
is an Android View
that defines a place for Airship Embedded Content to be
displayed.
When defining an AirshipEmbeddedView
, specify the airshipEmbeddedId
for the content it should display. The value of the embeddedId
must be the ID of an Embedded Content view style in your project.
<com.urbanairship.embedded.AirshipEmbeddedView
android:id="@+id/home_banner_embedded_view"
android:layout_width="match_parent"
android:layout_height="300dp"
app:airshipEmbeddedId="home_banner" />
Placeholders
If no content is available to display, the embedded view can optionally show a placeholder. The placeholder can be configured by providing a reference to an XML layout that defines the placeholder content. If no placeholder is set, the embedded view will use the default behavior:
- If content is available for the
airshipEmbeddedId
, theAirshipEmbeddedView
will display it within your layout. - If no content is available for the
airshipEmbeddedId
, theAirshipEmbeddedView
will not be visible.
<com.urbanairship.embedded.AirshipEmbeddedView
android:id="@+id/home_banner_embedded_view"
android:layout_width="match_parent"
android:layout_height="300dp"
app:airshipEmbeddedId="home_banner"
app:airshipPlaceholder="@layout/include_embedded_placeholder_item" />
Placing in a ScrollView or RecyclerView
When placed directly in a ScrollView
or RecyclerView
, or as a nested child view within a scrolling view that is not bounded in
the scroll direction, you must provide the parent’s size for the corresponding dimension of the embedded view. This enables
percent-based sizing to work correctly. You’ll need to determine the container size of the
scrolling parent (or another ancestor) and pass the size to the embedded view via the
parentWidthProvider
or ParentHeightProvider
arguments.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val scrollView = findViewById<ScrollView>(R.id.scroll_view)
val embeddedView = findViewById<AirshipEmbeddedView>(R.id.home_banner_embedded_view)
scrollView.doOnPreDraw {
embeddedView.parentHeightProvider = { scrollView.height }
}
}
Use with RecyclerView
is similar to the above example, but you’ll need to set the parent size in the onBindViewHolder
method.
One way to accomplish this is to pass the parent size to the adapter so that it can be used when binding the view holder
that contains the embedded view.
Controlling content display order
By default, pending Embedded Content is displayed in First In, First Out (FIFO) order per embeddedId
.
If you want to control the order in which pending content is displayed, you can provide a custom Comparator
to sort the Embedded Content based on fields that you define in the content’s extras.
Sorting by extras
AirshipEmbeddedView(
embeddedId = "home_banner",
comparator = { a, b ->
// Compare based on the priority field set on the Embedded Content extras.
val priorityA = a.extras.opt("priority").getInt(0)
val priorityB = b.extras.opt("priority").getInt(0)
priorityA.compareTo(priorityB)
},
modifier = Modifier.fillMaxWidth()
)
A Comparator
can also be passed as an argument to rememberAirshipEmbeddedViewState
to control content display order when hoisting the embedded view state.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val embeddedView = findViewById<AirshipEmbeddedView>(R.id.home_banner_embedded_view)
embeddedView.comparator = Comparator { a, b ->
// Compare based on the priority field set on the Embedded Content extras.
val priorityA = a.extras.opt("priority").getInt(0)
val priorityB = b.extras.opt("priority").getInt(0)
priorityA.compareTo(priorityB)
}
}
Observing available Embedded Content
Embedded Content is not always available, and even after being triggered, it still needs to be prepared before it
can be displayed. An AirshipEmbeddedView
will automatically update when content is available and transition from
the placeholder to the content once content is available. If you need to query the availability of Embedded
Content, you can use an AirshipEmbeddedObserver
to watch for updates.
An AirshipEmbeddedObserver
exposes both a callback and a Flow
that can be used to receive updates about the
availability of Embedded Content. This allows for more dynamic handling of Embedded Content than just content
or a placeholder.
Observer example
val observer = AirshipEmbeddedObserver("playground")
val embeddedInfo = observer.embeddedViewInfoFlow.collectAsState(initial = emptyList())
if (embeddedInfo.value.isEmpty()) {
Text("No banner available")
} else {
Text("Banner available")
AirshipEmbeddedView(embeddedId = "home_banner")
}
AirshipEmbeddedObserver observer = new AirshipEmbeddedObserver("home_banner");
observer.setListener(new AirshipEmbeddedObserver.Listener() {
@Override
public void onEmbeddedViewInfoUpdate(@NonNull List<AirshipEmbeddedInfo> views) {
if (views.isEmpty()) {
textView.setText("No banner available");
embeddedView.setVisibility(View.GONE);
} else {
textView.setText("Banner available");
embeddedView.setVisibility(View.VISIBLE);
}
}
});
The AirshipEmbeddedObserver
can be created to watch for one or more embeddedId
values or use custom
filtering to watch all Embedded Content or a subset determined by inspecting the embeddedId
or
extras
associated with the Embedded Content. The embeddedInfos
returned by the callback or Flow
are in FIFO order, meaning that the first content in the list is the first content that will be displayed.
Categories