Arena Android SDK


Integrate live blog, chat, analytics, and real-time data services into your Android applications. The Arena SDK handles real-time connections, rendering, and event tracking so you can focus on your app's integration.


Table of Contents


Requirements

ItemRequirement
Minimum SDKAPI 21
Compile SDKAPI 34 or higher
Target SDKAPI 34 or higher
Kotlin1.7.10 or higher
Java compatibilityJava 8
Google Play ServicesRequired on device for Firebase (LiveBlog and RealTimeData)

Known Compatibility Issues

Transitive dependencies no longer on Maven Central

The SDK has an internal dependency chain that goes through JCenter, which was shut down in 2021:

im.arena:liveblog
  └── im.arena:player:1.1.0          (real version, on dead JCenter)
        └── com.novoda:no-player:4.5.4   (dead JCenter)
              └── com.google.android.exoplayer2:*  (dead JCenter)
  └── im.arena:basiccard
        └── org.sufficientlysecure:html-textview:3.9  (dead JCenter)

The build will fail with errors like:

Could not find org.sufficientlysecure:html-textview:3.9
Could not find com.novoda:no-player:4.5.4

Fix: create a local stub-repo inside your project.

The approach is to intercept these unresolvable artifacts with local stubs that satisfy the compiler without pulling any further dead dependencies.

Step 1 — Create the folder structure

Create a stub-repo/ folder at the root of your project (same level as the app/ folder) with this layout:

stub-repo/
  im/arena/player/1.1.0/
    player-1.1.0.aar
    player-1.1.0.pom
  org/sufficientlysecure/html-textview/3.9/
    html-textview-3.9.aar
    html-textview-3.9.pom
  com/novoda/no-player/4.5.4/
    no-player-4.5.4.aar
    no-player-4.5.4.pom

Step 2 — Create the POMs

All three POMs follow the same minimal template with no <dependencies> block — this is what breaks the dead dependency chain:

<?xml version="1.0" encoding="UTF-8"?>
<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>GROUP_ID</groupId>
  <artifactId>ARTIFACT_ID</artifactId>
  <version>VERSION</version>
  <packaging>aar</packaging>
</project>

Replace GROUP_ID, ARTIFACT_ID, and VERSION for each artifact:

FilegroupIdartifactIdversion
player-1.1.0.pomim.arenaplayer1.1.0
html-textview-3.9.pomorg.sufficientlysecurehtml-textview3.9
no-player-4.5.4.pomcom.novodano-player4.5.4

Step 3 — Create the stub AARs

Each AAR is a ZIP file with the structure:

AndroidManifest.xml
classes.jar
R.txt          (empty file)

AndroidManifest.xml (same for all three, change the package):

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="PACKAGE_NAME" />

classes.jar is a JAR compiled from stub Java sources. The sources for each stub:


The im.arena:player artifact — your video extension point

im.arena.player.Player is the class the SDK instantiates to render video cards. The SDK reaches it in exactly one way (verified against the compiled BasicCard):

checkcast     im/arena/player/Player
invokevirtual im/arena/player/Player.configure(LifecycleOwner, String videoUrl, String thumbnailUrl)

So any class living at im.arena.player.Player that extends a View the SDK can inflate and exposes that configure(...) method is accepted by the SDK as its own player — transparently. This is the seam you use to plug in a video player without modifying the SDK. You provide this class entirely from your own project; nothing in the SDK changes.

Implement Player on top of AndroidX Media3 (ExoPlayer), which is published on Google's Maven repository (the google() repository, already in your repositories block) and replaces the dead com.novoda:no-playerexoplayer2 chain. The SDK never knows the difference — it still just calls configure(...).

// im/arena/player/Player.java
package im.arena.player;

import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.OptIn;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleEventObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.ui.PlayerView;

// Several Media3 APIs (ExoPlayer, PlayerView) are still marked @UnstableApi, which is
// a @RequiresOptIn marker — opting in here is required or the build fails.
@OptIn(markerClass = UnstableApi.class)
public class Player extends ConstraintLayout implements LifecycleEventObserver {

    private final PlayerView playerView;
    private ExoPlayer exoPlayer;
    private String currentUrl;

    public Player(Context ctx) { this(ctx, null); }
    public Player(Context ctx, AttributeSet attrs) { this(ctx, attrs, 0); }
    public Player(Context ctx, AttributeSet attrs, int style) {
        super(ctx, attrs, style);
        // Inflated from XML so the PlayerView is backed by a TextureView (see the layout below).
        playerView = (PlayerView) LayoutInflater.from(ctx)
                .inflate(R.layout.arena_player_view, this, false);
        addView(playerView, new ConstraintLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT));
    }

    /** Called by the Arena SDK for every video card. */
    public void configure(LifecycleOwner owner, String videoUrl, String thumbnailUrl) {
        owner.getLifecycle().addObserver(this);
        if (videoUrl == null || videoUrl.isEmpty()) return;

        // The live feed rebinds the card on every refresh, calling configure() again with the
        // same URL. Without this guard we would tear down and rebuild the player each time,
        // interrupting playback (and possibly leaving an orphan instance still playing audio).
        if (videoUrl.equals(currentUrl) && exoPlayer != null) return;
        currentUrl = videoUrl;

        release();
        exoPlayer = new ExoPlayer.Builder(getContext()).build();
        playerView.setPlayer(exoPlayer);

        // Arena wraps the stream URL in a proxy (…/videoproxy?url=….m3u8), so the format
        // extension is in the query string and ExoPlayer can't infer it from the path.
        // Set the MIME type explicitly so HLS/DASH route to the right MediaSource.
        MediaItem.Builder item = new MediaItem.Builder().setUri(videoUrl);
        if (videoUrl.contains(".m3u8")) {
            item.setMimeType(MimeTypes.APPLICATION_M3U8);
        } else if (videoUrl.contains(".mpd")) {
            item.setMimeType(MimeTypes.APPLICATION_MPD);
        }
        exoPlayer.setMediaItem(item.build());
        exoPlayer.setPlayWhenReady(false);   // tap to play; set true to autoplay
        exoPlayer.prepare();
    }

    public void stop() { release(); }

    // LifecycleEventObserver has a single abstract method, so it works regardless of which
    // androidx.lifecycle version the SDK resolves at runtime. DefaultLifecycleObserver's
    // empty default methods live in lifecycle-common-java8 and can throw AbstractMethodError.
    @Override
    public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
        if (event == Lifecycle.Event.ON_STOP) {
            if (exoPlayer != null) exoPlayer.pause();
        } else if (event == Lifecycle.Event.ON_DESTROY) {
            release();
        }
    }

    private void release() {
        if (exoPlayer != null) { exoPlayer.release(); exoPlayer = null; }
        playerView.setPlayer(null);
    }
}

The PlayerView is inflated from a layout so it can use a TextureView instead of the default SurfaceView. The live blog is a RecyclerView that detaches, reattaches and re-lays-out the card on every feed refresh; a SurfaceView loses its surface in that flow — the video freezes on the last frame while audio keeps playing — whereas a TextureView survives it. Add this layout to your project (e.g. res/layout/arena_player_view.xml):

<?xml version="1.0" encoding="utf-8"?>
<androidx.media3.ui.PlayerView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:surface_type="texture_view"
    app:resize_mode="fit" />

Video size. The SDK's video card fixes the player area to a short, full-width band (≈16:9). Vertical (portrait) videos are letterbox-fitted into it and look small — this height is set by the SDK card layout and cannot be changed from your Player class (confirmed: setMinimumHeight has no effect). Your only lever is app:resize_mode: fit (default, whole video visible) or zoom (fills the width but crops portrait videos top/bottom).

Because this Player references Media3, declare Media3 in your app-level build.gradle. It resolves from the google() repository, so the stub AAR's POM stays dependency-free (the dead chain remains broken):

dependencies {
    implementation 'androidx.media3:media3-exoplayer:1.4.1'      // or newer
    implementation 'androidx.media3:media3-ui:1.4.1'
    implementation 'androidx.media3:media3-exoplayer-hls:1.4.1'  // Arena videos are HLS (.m3u8)
    // implementation 'androidx.media3:media3-exoplayer-dash:1.4.1'  // add if your feed serves DASH (.mpd)
}

Without media3-exoplayer-hls the player fails with UnrecognizedInputFormatException / Source error and the video silently stops — ExoPlayer only handles progressive formats (MP4, etc.) out of the box.

Don't display video cards at all? Leave the body of configure() empty (and drop the Media3 imports/dependencies). The same class then just satisfies the build without playing anything.

Building this Player into the AAR with raw javac is impractical (it needs the Media3, ConstraintLayout, and Lifecycle AARs on the classpath). Use a throwaway Android library module that compiles to an AAR you then copy into stub-repo:

  1. Create a minimal library module (e.g. arena-player/) with the Player.java above under src/main/java/im/arena/player/, the arena_player_view.xml layout under src/main/res/layout/, minSdk 21, and the Media3 + ConstraintLayout + Lifecycle dependencies.
  2. Build it: ./gradlew :arena-player:assembleRelease.
  3. Copy arena-player/build/outputs/aar/arena-player-release.aar to stub-repo/im/arena/player/1.1.0/player-1.1.0.aar.

Prefer to keep the player as a live module instead of copying an AAR? Use Gradle dependency substitution:

configurations.all {
    resolutionStrategy.dependencySubstitution {
        substitute module('im.arena:player') using project(':arena-player')
    }
}

Same result, no AAR copy step — and still nothing changes in the SDK.


org.sufficientlysecure:html-textviewBasicCard calls HtmlFormatter.formatHtml(). The stub delegates to android.text.Html:

// org/sufficientlysecure/htmltextview/HtmlFormatterBuilder.java
package org.sufficientlysecure.htmltextview;
public class HtmlFormatterBuilder {
    private String html = "";
    public HtmlFormatterBuilder setHtml(String html) {
        this.html = html != null ? html : "";
        return this;
    }
    public String getHtml() { return html; }
}
// org/sufficientlysecure/htmltextview/HtmlFormatter.java
package org.sufficientlysecure.htmltextview;
import android.os.Build;
import android.text.Html;
import android.text.Spanned;
public class HtmlFormatter {
    public static Spanned formatHtml(HtmlFormatterBuilder builder) {
        String html = builder.getHtml();
        if (html == null || html.isEmpty())
            return new android.text.SpannableString("");
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
            return Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY);
        else
            return Html.fromHtml(html);
    }
}

Compile with android.jar only:

javac -cp android.jar -source 8 -target 8 \
    org/sufficientlysecure/htmltextview/*.java
jar cf classes.jar org/sufficientlysecure/htmltextview/*.class

com.novoda:no-player — neither Player option above references NoPlayer (Option 1 is a no-op; Option 2 uses Media3), and the player POM declares no dependencies, so this artifact is resolved but never actually loaded at runtime. An empty classes.jar is enough:

jar cf classes.jar  # empty JAR

Step 4 — Register the stub-repo in build.gradle

In your project-level build.gradle, add stub-repo first in the repositories list so it takes priority over Maven Central:

allprojects {
    repositories {
        maven { url "$rootDir/stub-repo" }   // must be first
        google()
        mavenCentral()
    }
}

After syncing, the build will resolve all three dead artifacts from the local repo and skip any further transitive resolution for them.

Android 14+ crash in LiveBlog and RealTimeData (gRPC TLS reflection)

The SDK uses Firebase Firestore 21.2.1 with gRPC 1.21.0. On Android 14 (API 34) and higher, this version of gRPC attempts to configure TLS via reflection on a restricted system API (getAlpnSelectedProtocol), which crashes the app with:

java.lang.AssertionError: Method getAlpnSelectedProtocol not supported
    at io.grpc.okhttp.OkHttpProtocolNegotiator$AndroidNegotiator.negotiate(...)

Fix: add the Conscrypt security provider before initializing any Arena SDK:

  1. Add the dependency:
dependencies {
    implementation 'org.conscrypt:conscrypt-android:2.5.2'
}
  1. Install Conscrypt as the first security provider in Application.onCreate(), before any Arena SDK call:
import org.conscrypt.Conscrypt
import java.security.Security

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        Security.insertProviderAt(Conscrypt.newProvider(), 1)
        // Arena SDK initialization follows...
    }
}

This replaces the system TLS stack with Conscrypt, which exposes ALPN via proper APIs that gRPC can use without reflection.


Project Setup

These steps must be completed once before adding any Arena SDK module to your project.

Step 1 — Add Maven Central to your repositories

Open your project-level build.gradle (the one at the root of your project, not inside app/) and make sure mavenCentral() is listed under allprojects.repositories:

allprojects {
    repositories {
        google()
        mavenCentral()
    }
}

If your project uses dependencyResolutionManagement in settings.gradle instead (newer project templates), add it there:

dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
    }
}

Step 2 — Add Google Services plugin

The Arena Analytics and RealTimeData SDKs depend on Firebase internally. Add the Google Services plugin to your project-level build.gradle:

buildscript {
    dependencies {
        classpath 'com.google.gms:google-services:4.4.1'
    }
}

Then apply it at the top of your app-level build.gradle (app/build.gradle):

apply plugin: 'com.google.gms.google-services'

Step 3 — Add google-services.json

  1. Go to the Firebase Console and open (or create) your project.
  2. Register your Android app with your app's package name.
  3. Download the google-services.json file and place it inside the app/ folder of your project.

Never done this before? See the step-by-step guide with screenshots: How to create a Firebase project and obtain google-services.json.

MyApp/
  app/
    google-services.json   ← here
    src/
    build.gradle
  build.gradle

Step 4 — Add Internet permission

Open app/src/main/AndroidManifest.xml and add the Internet permission before the <application> tag:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.INTERNET" />

    <application ...>
        ...
    </application>
</manifest>

Step 5 — Create an Application class

All Arena SDKs must be initialized inside Application.onCreate(). If your project does not have an Application subclass yet, create one:

// app/src/main/java/com/example/myapp/MyApplication.kt
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        // SDK initialization goes here (see each module's section below)
    }
}

Then register it in AndroidManifest.xml via the android:name attribute on <application>:

<application
    android:name=".MyApplication"
    android:label="@string/app_name"
    ...>

Credentials

ModuleCredentialWhere to obtain
LiveBlogpublisherSlug, eventSlugYour Arena account manager
ChatwriteKey (site slug), slug (chat room slug)Arena Dashboard → Settings. The chat room slug is the last path segment in the chat list URL.
AnalyticswriteKeyYour Arena account manager
RealTimeDatapublisherSlug, eventSlugYour Arena account manager

Initialization Order

Important: when using LiveBlog, RealTimeData.configure() must be called before LiveBlog.configure(). LiveBlog depends on the RealTimeData connection being established first.

Modules used independently (Analytics, Chat, or RealTimeData alone) do not require any specific ordering.

Note: LiveBlog internally calls Analytics.configure() on first data load, even if you do not declare the im.arena:analytics dependency explicitly in your build.gradle. You will see analytics-related log entries as a result — this is expected behavior.

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        // Required on Android 14+ — install before any Arena SDK call
        Security.insertProviderAt(Conscrypt.newProvider(), 1)

        // 1. RealTimeData FIRST (required when using LiveBlog)
        RealTimeData
            .logLevel(LogLevel.NONE)
            .configure(this, Environment.PRODUCTION)

        // 2. LiveBlog SECOND
        LiveBlog
            .environment(Environment.PRODUCTION)
            .logLevel(LogLevel.NONE)
            .configure(this, "your-publisher-slug", "your-event-slug")

        // Analytics and Chat can be initialized in any order
        Analytics
            .environment(Environment.PRODUCTION)
            .logLevel(LogLevel.NONE)
            .configure(this, "your-write-key")
    }
}

LiveBlog

Displays a real-time live blog feed. Supports the following card types: title, description, publisher, pinned, summary, player/person, article, social, video, golf, and live score.

Video cards: the SDK delegates playback to a im.arena.player.Player class that you provide. To actually play video (rather than just satisfy the build), implement it with AndroidX Media3 — see Option 2 in the player section.

Step 1 — Add the dependency

In your app-level build.gradle:

dependencies {
    implementation 'im.arena:liveblog:1.30.0'
}

Step 2 — Add ProGuard rules

If your app uses minifyEnabled true, add to your proguard-rules.pro:

-keep class im.arena.liveblog.** { *; }
-keep class im.arena.analytics.** { *; }
-keep class im.arena.commons.** { *; }
-keep class im.arena.realtimedata.** { *; }

Step 3 — Initialize in Application

In MyApplication.onCreate(), configure RealTimeData first, then LiveBlog (see Initialization Order):

RealTimeData
    .logLevel(LogLevel.NONE)
    .configure(this, Environment.PRODUCTION)

LiveBlog
    .environment(Environment.PRODUCTION)
    .logLevel(LogLevel.NONE)
    .configure(this, "your-publisher-slug", "your-event-slug")

Step 4 — Fix manifest theme conflict

The LiveBlog SDK declares its own android:theme in its internal manifest. This conflicts with your app's theme declaration during manifest merging, causing a build error. Add tools:replace="android:theme" to the <application> tag in AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application
        android:theme="@style/Theme.YourApp"
        tools:replace="android:theme"
        ...>

Without this, the build fails with:

Attribute application@theme value=(@style/Theme.YourApp) from AndroidManifest.xml
is also present in [im.arena:liveblog] value=(@style/...)

Step 5 — Add the view to your layout

In your Activity or Fragment layout XML file:

<im.arena.liveblog.LiveBlog
    android:id="@+id/live_blog"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

Step 6 — Start the live blog in your Activity

In Activity.onCreate(), after inflating the layout, call start() on the view reference:

class MyActivity : AppCompatActivity() {

    // Using ViewBinding (recommended)
    private lateinit var binding: ActivityMyBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMyBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.liveBlog.start()
    }
}

If you are not using ViewBinding, get the reference with findViewById:

val liveBlog = findViewById<im.arena.liveblog.LiveBlog>(R.id.live_blog)
liveBlog.start()

Optional — Handle item clicks

Implement LiveBlogCallback on your Activity or Fragment to receive click events. The full import path is im.arena.liveblog.commons.LiveBlogCallback:

import im.arena.liveblog.commons.LiveBlogCallback

class MyActivity : AppCompatActivity(), LiveBlogCallback {

    override fun onItemClick(view: View?, position: Int) {
        // A regular card was tapped
    }

    override fun onPredictionItemClick(view: View?, position: Int) {
        // A prediction card was tapped
    }

    override fun onLiveScoreItemClick(view: View?, position: Int) {
        // A live score card was tapped
    }

    override fun onItemClick(view: View?, positionParent: Int, positionChild: Int) {
        // A nested item inside a card was tapped
    }
}

Chat

A ready-to-use live group chat embedded as an Android Fragment. Supports anonymous users and SSO-authenticated users.

Note: The Chat SDK uses Jetpack Compose internally. If your project does not already include Compose, the transitive dependencies pulled by the SDK are sufficient — no extra Compose setup is needed in most cases. If you see build errors related to Compose, ensure your compileSdkVersion is at least 31 and that you have kotlin-gradle-plugin 1.7.10 or higher.

Step 1 — Add the dependency

dependencies {
    implementation 'im.arena:chat:1.4.0'
}

Step 2 — Add ProGuard rules

-keep class im.arena.chat.** { *; }
-keep class im.arena.analytics.** { *; }
-keep class im.arena.commons.** { *; }
-keep class im.arena.realtimedata.** { *; }

Step 3 — Observe SSO events (before embedding the fragment)

Set up the event observer in Activity.onCreate() before the fragment transaction so it is ready to fire immediately:

class MyActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my)

        // 1. Observe FIRST
        Chat.liveDataEvents().observe(this) { event ->
            when (event) {
                Events.SSO_REQUIRED -> {
                    // The user tapped login — launch your own login flow here.
                    // After login succeeds, call Chat.loggedUser(...) (see SSO section below).
                }
                else -> {}
            }
        }

        // 2. Then embed the fragment
        embedChatFragment()
    }
}

Step 4 — Configure and embed the fragment

private fun embedChatFragment() {
    val chatFragment = Chat
        .environment(Environment.PRODUCTION)
        .logLevel(LogLevel.NONE)
        .configure(application, "your-write-key", "your-chat-slug")
        .newInstance()

    supportFragmentManager
        .beginTransaction()
        .replace(R.id.container, chatFragment)
        .commitAllowingStateLoss()
}

Your layout must have a container view with the matching id:

<FrameLayout
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

Step 5 — SSO (optional)

To start the chat with a pre-authenticated user, call Chat.loggedUser() at any point before or after embedding the fragment:

Chat.loggedUser(
    ExternalUser(
        id        = "user-123",
        name      = "John Doe",
        email     = "[email protected]",
        image     = "https://example.com/avatar.jpg",
        firstName = "John",
        lastName  = "Doe"
    )
)

Analytics

Tracks user actions, screen views, and identity to power the Arena analytics pipeline.

Step 1 — Add the dependency

dependencies {
    implementation 'im.arena:analytics:1.21.1'
}

Step 2 — Add ProGuard rules

-keep class im.arena.analytics.** { *; }
-keep class im.arena.commons.** { *; }
-keep class com.google.firebase.iid.** { *; }

Step 3 — Initialize in Application

Analytics
    .environment(Environment.PRODUCTION)
    .logLevel(LogLevel.NONE)
    .configure(this, "your-write-key")

Track — record a user action

Analytics.instance().track(
    "Post Reacted",
    hashMapOf(
        "postId"   to "WOR06DvfpcRaylQJoZe",
        "reaction" to "thumbs_up"
    )
)

Page — record a screen view

Analytics.instance().page(
    "Home",
    hashMapOf("referrer" to "push_notification")
)

Identify — associate a user with their events

Call this after login, registration, or profile update:

Analytics.instance().identify(
    "user-43564",
    hashMapOf(
        "name"  to "John Doe",
        "email" to "[email protected]",
        "plan"  to "business"
    )
)

RealTimeData

Provides raw real-time data streams via Firebase Firestore, giving you full control over rendering. The SDK uses RxJava 3, which is included as a transitive dependency — you do not need to declare it separately.

Note: If you need to observe results on the Android main thread (e.g., to update a RecyclerView), you must add rxandroid explicitly:

implementation 'io.reactivex.rxjava3:rxandroid:3.0.2'

Then use .observeOn(AndroidSchedulers.mainThread()) before .subscribe().

Step 1 — Add the dependency

dependencies {
    implementation 'im.arena:realtimedata:1.40.0'
}

Step 2 — Add ProGuard rules

-keep class im.arena.realtimedata.** { *; }

Step 3 — Initialize in Application

Note: unlike other modules, the Environment is passed as a parameter to configure(), not via a builder method.

RealTimeData
    .logLevel(LogLevel.NONE)
    .configure(this, Environment.PRODUCTION)

Step 4 — Load cached metadata, then subscribe to the stream

The typical flow is:

  1. Call cachedRepository.live() to load the event metadata and get the eventId.
  2. Use the eventId to subscribe to the real-time play-by-play stream.
  3. Clean up subscriptions in onDestroy().
class MyActivity : AppCompatActivity() {

    private val compositeDisposable = CompositeDisposable()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        loadEvent()
    }

    private fun loadEvent() {
        RealTimeData
            .instance()
            .cachedRepository
            .live("your-publisher-slug", "your-event-slug")
            .subscribe(
                { eventData -> subscribeToStream(eventData.eventInfo?.key) },
                { error -> /* handle error */ }
            )
            .also { compositeDisposable.add(it) }
    }

    private fun subscribeToStream(eventId: String?) {
        RealTimeData
            .instance()
            .playByPlay
            .realtime(
                eventId  = eventId,
                orderBy  = OrderBy.NEWEST
            )
            .subscribe(
                { posts -> /* update your RecyclerView adapter with the List<Posts> */ },
                { error -> /* handle error */ }
            )
            .also { compositeDisposable.add(it) }
    }

    override fun onDestroy() {
        compositeDisposable.clear() // cancels all Firestore listeners automatically
        super.onDestroy()
    }
}

realtime() full signature

RealTimeData.instance().playByPlay.realtime(
    eventId  = "your-event-id",   // obtained from cachedRepository.live()
    orderBy  = OrderBy.NEWEST,    // OrderBy.NEWEST or OrderBy.OLDEST
    priority = Priority.DESCENDING, // optional, default: DESCENDING
    perPage  = 20                 // optional, default: 20
)

Returns Flowable<List<Posts>>.


Getting Help

Issues are tracked on GitHub Issues.

LabelMeaning
bugFeature that should work but doesn't
enhancementMinor tweak to existing behavior
featureNew behavior
questionUsage question — no SDK change needed
duplicateAlready tracked elsewhere
wontfixWon't be fixed due to compatibility or design
non-libraryDocumentation, samples, or build process

License

Arena Android SDK is proprietary software. All rights reserved. See the LICENSE file for details.

Copyright © 2022 Arena Im.