Skip to content

App Banner

Ruofan Wei edited this page Sep 16, 2026 · 5 revisions

App Banner Integration

App Banner is a data-only API. The OmniSegment SDK selects eligible content and returns it to the host application. The host application owns parsing, rendering, user interaction, and removal of the native UI.

Requirements

  • OmniSegment Android SDK 1.1.0 or later.
  • Android minSdk 24 or later.
  • An OmniSegment API key and TID.
  • A pageKey and bannerKey configured with OmniSegment.
  • An agreed JSON content schema between OmniSegment and the host application.

The 1.1.0 dependency graph includes androidx.webkit:webkit:1.16.0. Kotlin 1.8 consumers may encounter incompatible Kotlin metadata from transitive dependencies; validate the consumer project's dependency resolution before release.

Follow the installation guide, then initialize the SDK once from Application.onCreate():

import android.app.Application
import com.bebittech.omnisegment.OmniSegment

class App : Application() {
    override fun onCreate() {
        super.onCreate()
        OmniSegment.initialize(this, "API_KEY", "TID")
    }
}

Request content

pageKey identifies the current app screen. bannerKey identifies one banner slot on that screen. Both values must be non-empty and must match the values configured in OmniSegment.

The callback is asynchronous and runs on the Android main thread. Keep the returned OSGBannerRequest while the request is pending, and cancel it when the screen no longer needs the result.

Kotlin

import com.bebittech.omnisegment.OSGBannerContentStatus
import com.bebittech.omnisegment.OSGBannerRequest
import com.bebittech.omnisegment.OmniSegment

private var bannerRequest: OSGBannerRequest? = null

fun loadBanner() {
    bannerRequest = OmniSegment.requestBannerContent("home", "top") { result ->
        bannerRequest = null

        when (result.status) {
            OSGBannerContentStatus.CONTENT -> {
                val delivery = result.delivery ?: return@requestBannerContent
                val contentJSON = delivery.contentJSON

                // Parse contentJSON using the schema agreed with OmniSegment,
                // then render the banner in the host application.
                renderBanner(contentJSON)

                // Call from the host application's actual UI lifecycle:
                // delivery.reportDisplayed() after the banner becomes visible.
                // delivery.reportClicked() from the banner click handler.
                // delivery.reportClosed() from the banner close handler.
            }

            OSGBannerContentStatus.NO_CONTENT -> removeBannerIfPresent()

            OSGBannerContentStatus.FAILED -> {
                handleBannerError(result.error?.code)
            }
        }
    }
}

fun stopLoadingBanner() {
    bannerRequest?.cancel()
    bannerRequest = null
}

Call stopLoadingBanner() from the screen's teardown lifecycle, such as onStop() or onDestroyView(), if a request is still pending. Do not cancel immediately after creating the request.

Java

import com.bebittech.omnisegment.OSGBannerContentDelivery;
import com.bebittech.omnisegment.OSGBannerContentStatus;
import com.bebittech.omnisegment.OSGBannerRequest;
import com.bebittech.omnisegment.OmniSegment;

private OSGBannerRequest bannerRequest;

private void loadBanner() {
    bannerRequest = OmniSegment.requestBannerContent("home", "top", result -> {
        bannerRequest = null;

        if (result.getStatus() == OSGBannerContentStatus.CONTENT) {
            OSGBannerContentDelivery delivery = result.getDelivery();
            if (delivery == null) {
                return;
            }

            String contentJSON = delivery.getContentJSON();
            renderBanner(contentJSON);

            // Call from the host application's actual UI lifecycle:
            // delivery.reportDisplayed() after the banner becomes visible.
            // delivery.reportClicked() from the banner click handler.
            // delivery.reportClosed() from the banner close handler.
        } else if (result.getStatus() == OSGBannerContentStatus.NO_CONTENT) {
            removeBannerIfPresent();
        } else {
            handleBannerError(result.getError() == null ? null : result.getError().getCode());
        }
    });
}

private void stopLoadingBanner() {
    if (bannerRequest != null) {
        bannerRequest.cancel();
        bannerRequest = null;
    }
}

renderBanner, removeBannerIfPresent, and handleBannerError are placeholders for host-application code; they are not SDK methods.

Result contract

Status delivery error Meaning
CONTENT Present null Eligible content was selected. Parse and render delivery.contentJSON.
NO_CONTENT null null No eligible content is available. This is not an error.
FAILED null Present The request failed. Inspect error.code.

Content returned to the application

App Banner obtains the selected banner from /ma_cms/get-web-popup/, but the SDK does not return the complete API response. delivery.contentJSON contains only this value:

response.PAYLOAD.data.options.content

For example, when /get-web-popup/ returns a WebBannerEmbed whose options.content is a card, contentJSON has the following shape:

{
  "card": {
    "image": {
      "enabled": false,
      "customRatio": {}
    },
    "title": {
      "text": "Banner title",
      "enabled": false
    },
    "device": "mobile",
    "ctaButton": {
      "text": "Learn more",
      "color": "#10A1B5",
      "style": "text",
      "enabled": true,
      "fontSize": 14,
      "textAlign": "left"
    },
    "image_src": null,
    "appearance": {
      "topMargin": 16,
      "bottomMargin": 16,
      "backgroundColor": "#f5f9fb",
      "borderRadiusTopLeft": 12,
      "borderRadiusTopRight": 12,
      "borderRadiusBottomLeft": 12,
      "borderRadiusBottomRight": 12
    },
    "closeButton": {
      "size": 20,
      "color": "#C8C8C8",
      "enabled": true
    },
    "description": {
      "text": "Banner description",
      "color": "#262626",
      "enabled": true,
      "fontSize": 14,
      "textAlign": "left"
    },
    "redirectUrl": "https://example.com/landing"
  },
  "type": "card"
}

redirectUrl is a plain string. It is not a Markdown link.

Current card fields

Path Meaning
type Content type. The current card content uses card.
card.image Media enablement and ratio settings. When enabled is false, do not render an image or video.
card.image_src Preferred media-source array when configured; otherwise it may be null. A source can represent an image or video.
card.visual_src Compatibility media-source array. The test app uses it as a fallback when image_src has no valid URL.
card.title Optional title content and text style. Check enabled before rendering.
card.description Optional description content and text style. Check enabled before rendering.
card.ctaButton Optional CTA content and style. Check enabled before rendering.
card.appearance Background, margins, border radii, and other card appearance values.
card.closeButton Close-button visibility and style. Check enabled before rendering.
card.redirectUrl External URL or application-defined internal destination handled by the host application.

Fields can be omitted when a block is disabled or not configured, and new fields may be added without an SDK release. Parse defensively: check type, tolerate missing and unknown fields, and do not require the example above to be byte-for-byte identical.

Images and videos

Do not assume that a configured media source is always an image. Each entry in image_src or visual_src can contain a remote url and a media type.

Image source:

{
  "image_src": [
    {
      "url": "https://cdn.example.com/banner.jpg",
      "type": "image"
    }
  ]
}

Video source:

{
  "image_src": [
    {
      "url": "https://cdn.example.com/banner.mp4",
      "type": "video"
    }
  ]
}

The current Android test app parser selects the first image_src entry with a non-blank URL, then falls back to visual_src. It treats type: "video" case-insensitively as video and renders other or missing types as images. Its banner screen demonstrates image loading, muted looping video playback, playback-error handling, and stopping the video when the screen is destroyed.

Applications may use a different UI implementation, but should follow these data-handling rules:

  • Hide the media block when card.image.enabled is false or no valid media URL is available.
  • Use a video-capable player for type: "video"; use an image loader for image or unspecified media types.
  • Handle image-load and video-playback failures without failing the rest of the banner.
  • Stop and release video playback when the banner or screen is removed.
  • Call reportDisplayed() only after the rendered banner is actually visible, not merely after starting an asynchronous media load.

The following /get-web-popup/ fields are used internally and are not part of contentJSON: name, pageId, frequency, need_login, behaviorRecords, clientPlatformVariable, routers, template_uid, and other values outside options.content.

import org.json.JSONObject

val content = JSONObject(delivery.contentJSON)
when (content.optString("type")) {
    "card" -> {
        content.optJSONObject("card")?.let { card ->
            // Render only the enabled blocks that the application supports.
        }
    }
    else -> {
        // Ignore or log unsupported future content types.
    }
}

Reporting UI lifecycle events

Method Call timing
reportDisplayed() After the rendered banner is actually visible to the user.
reportClicked() From the host application's banner click handler.
reportClosed() From the host application's banner close or dismiss handler.

These methods are idempotent for each delivery. reportClicked() and reportClosed() also report displayed first if it has not already been reported. Receiving content alone does not report an impression.

Errors

Error code Meaning
INVALID_ARGUMENT pageKey or bannerKey is missing or blank.
NOT_INITIALIZED OmniSegment.initialize(...) has not completed.
NETWORK The content request failed because of a network error.
TIMED_OUT The request did not complete within 30 seconds.
INVALID_CONTENT The selected content or tracking payload is malformed.
INTERNAL The SDK or JavaScript bridge encountered an unexpected failure.

Lifecycle rules

  • Each call creates an independent request. Concurrent requests are supported.
  • cancel() suppresses the callback only while the request is pending. It is safe to call more than once.
  • Keep the delivery associated with the UI rendered from that delivery.
  • Do not report displayed before rendering succeeds and the banner is visible.
  • Treat NO_CONTENT as a normal empty state rather than a retryable error.

Clone this wiki locally