Migrate on Android
Preliminary pre-release documentation, subject to change without notice. The full list of properties on each event isn't published yet.
Updated on September 16, 2026.
The unified schema ships as a major version on Android. Removed enum entries are deleted rather than deprecated, so an exhaustive when over the analytics enums stops compiling until you handle the new shape.
This page is written to be followed by a developer or a coding agent. Read Ask before you change these before you change code.
Read first: Migrate to the unified events schema holds the mapping tables this page assumes. This page adds only what's specific to Android. The old value to new value pairs are only on that page, so don't change code from this page alone.
API reference: Android
Before you start
- Wait for the SDK version. The unified schema ships in Android 2.0.x. Don't start the code changes until you're upgrading to that line.
- Accept both schema versions. Your pipeline needs to accept
api_scheme_version"2"and"2.1"while older SDK versions in the field still emit legacy events. - Nothing is aliased. Old names aren't mapped to new ones anywhere in the SDK, so anything you miss goes silent rather than failing.
- The full list of properties on each event isn't published yet. Treat a mapping that isn't listed as unknown, not as unchanged. Don't run this as an unattended automated pass.
- React Native and Flutter wrappers forward raw JSON, so the payload changes reach them unchanged. Their typed layers need the same mapping as native Kotlin.
Ask before you change these
Don't make these changes without a person. The mapping tables say what the payload does now, not what your app should do about it. Compile-break rows in Fix what stops compiling are in scope. Rows below are not, even when they fail the build. Don't map a removed event to the nearest surviving case to make when compile.
| Change | Why a person decides |
|---|---|
story_completed | It now means one Story finished, not a completed playlist. Code that treated it as playlist completion needs remaining_unread_count = 0, which is a logic change. |
| Removed events with no replacement | Forced play and pause, and cta_dismissed, are gone. Dropping the feature that depended on them is a product decision. search_suggestions_shown is gone. For suggestion click-through, count search_suggestions_clicked against search_opened. |
| Call to action metrics | cta_shown counts once per start rather than once per appearance, so thresholds and rates built on the old counting need rebasing. |
| Search attribution | Search identifiers ride every player event in the session, so counting logic has to move to unique search_session_id. |
| Cross-platform comparisons around share | The extra background pair on Android is by design, so reconciling it is an analysis decision. |
Step 1: Find the affected code
The result of this step is a list of files to change, not a change.
- Search your app for each string in the table below.
- Search each Legacy cell in Migrate to the unified events schema: value remap, exit triggers, start triggers, renamed events, removed events, removed properties, and renamed properties.
- Search your dashboards, saved segments, and warehouse queries for the same strings. Code and reporting break independently, and reporting breaks silently.
The compiler won't catch string reads. Comparing eventAction.value, a category value, or a property name against a string literal keeps compiling and silently sees a different value. Review those by hand.
| Search for | Why it matters |
|---|---|
onEventTriggered | Handlers that read the payload |
event_category, event_action | Code that branches on event identity |
.value on an analytics enum | String reads the compiler can't check |
EventActionName, EventCategoryType | Enums with deleted and lowercased entries |
AnalyticsPropsContainer, AnalyticsGeo, AnalyticsPage, AnalyticsWscInternal, PlaybackActionMethod | Removed types |
playback_play, playback_pause, ad_playback_play, ad_playback_pause | Renamed events |
container_data_load, search_click, video_orientation_changed | Renamed events |
forced_playback_play, forced_playback_pause | Removed, no replacement. Report the call site. See Ask before you change these. |
cta_visible, cta_dismissed | Removed. cta_dismissed has no replacement. See Ask before. |
moments_playlist_start, moments_playlist_exit, casting_started, casting_ended, pip_enabled, pip_disabled | Removed events |
search_suggestions_shown | Removed. For suggestion click-through, count search_suggestions_clicked against search_opened. See Ask before. |
Stories completed, Moments Container, Swipe Down | Values that don't recase 1:1. See the shared trigger tables. |
current_mode, next_mode | Replaced by a single viewing_mode string |
origin_widget_id, labels_expression, container_id | Moved referring and tab fields |
next_video_id with a trailing space | The legacy key is fixed, so remove any workaround |
| Title Case string literals compared against property values | Search the Legacy column of the shared value remap. Don't recase and assume a match. |
Step 2: Fix what stops compiling
There are no deprecation shims. An exhaustive when that stops compiling is the signal that an event you depended on is gone. If a compile error matches Ask before you change these, stop and report it.
New types you'll need to handle: AnalyticsPropsTab, AnalyticsCtaConfig, AnalyticsCustomActionButtonConfig, AnalyticsLoopSettings, AnalyticsFollowedEntity, BlazeBackToLiveTrigger, TabTransitionMethod, and AnalyticsOriginNode.
There's no widget or container identifier change on Android. Widget and container IDs were already required constructor inputs, so the iOS widgetIdentifier and containerIdentifier break has no Android counterpart. The values still need to be stable placement labels, because widget metrics are grouped by them. Don't invent a new label as part of this migration.
| Change | What to do |
|---|---|
Deleted EventActionName entries | Handle the removals listed in the shared reference. An exhaustive when won't compile until you do. |
EventCategoryType is lowercased and extended | Handle the new tab and global categories. |
AnalyticsPropsContainer is removed | Use AnalyticsPropsTab. |
AnalyticsGeo, AnalyticsPage, AnalyticsWscInternal are removed | Delete the code that read them. They were always empty. |
PlaybackActionMethod is removed | No replacement. Anything referencing the type, not just the property, stops compiling. |
AnalyticsPropsAd is reshaped | Read the unified content_id and content_title. playback_action_method is gone from ad properties too. |
video.viewing_mode is a single string | Replace uses of current_mode and next_mode. On viewing_mode_transition it's the destination mode, and everywhere else it's the current mode. |
moment_duration_viewed_percent and story_page_duration_viewed_percent are Double? | Update the types that read them. They were Int?. |
Step 3: Android data changes
These changes are specific to Android. The schema is the same on every platform, and what differs here is where Android reporting becomes more precise than it was. Nothing errors, so a report built on the previous behavior keeps running on the new numbers.
Values that report differently
| What changed | What it means for your numbers |
|---|---|
| Viewed percentage |
|
stream_status | It previously reported values such as LIVE. It now reports live, upcoming, and ended. |
loop_number across a hide and show | A navigation tab hide and show no longer increments the loop count. |
moments_session_id on a tab return | Returning to a Moments tab re-embeds the player on Android. The session ID is now carried across the re-embed, matching iOS, where the return previously started a new session. |
video_current_time on an auto-advanced start | It now reports the new video's position rather than the previous video's end position. |
| The loop and advance exit | The moment_exit describes the play that finished, carrying that loop's number, viewed_percent = 100, and navigation_type = automatic. It previously described the loop that had already started. |
| Tab switch event order | The order is tab_transition, then the outgoing moment_exit with focus_lost, then the incoming moment_start. Viewed percentage is captured at the switch instant. |
Android-only notes
| Behavior | What it means |
|---|---|
| The system share chooser takes your app out of the foreground |
|
| A session is process lifetime | There's no session rotation on Android, which matters for the once-per-session load events. |
| Device fields are captured synchronously | Nothing to migrate. The iOS note about tech fields briefly reporting unknown doesn't apply here. |
from_tab_title, to_tab_title, and tab_navigation_type | Nothing to migrate. These never existed on Android, and they're listed as removed because iOS sent them. |
Step 4: Verify
- Confirm
api_scheme_versionreads2.1in the payloads your app receives. - Confirm
event_categoryvalues arrive lowercase, and that your handler coverstabandglobal. - Confirm no handler still matches a legacy value. A handler that never fires is the failure mode here, so assert on a match rather than on the absence of errors.
- Confirm the properties you depend on are present on the events you read them from. A missing property means it doesn't apply to that event, not that it's null.
- On React Native and Flutter, confirm the typed layer maps the new names and values, not only that the raw JSON arrives.
Related
- Migrate to the unified events schema: the payload reference for every platform
- Migrate on iOS: the same job on iOS
- Migrate on Web: the same job on Web
- Reporting changes to expect: metrics that move without anything failing
- 2026 events schema - breaking changes ahead: timeline
Updated 14 days ago
