Integrate the Genuin Android SDK

Add Genuin embeds, placements, single sign-on, deep links, and push notifications to an Android app with SDK 2.1.5.

The Genuin Android SDK renders your brand's communities, groups, and video feeds inside your own app. You add one or more of four modules, initialize the SDK with your API key, and drop a view or fragment where the content goes. Everything the view shows, from which community to the carousel or grid layout, is configured in Brand Control Center and identified by an embed ID or placement ID.

ModuleArtifactSizeAddsRequires
Corecom.begenuin:core:2.1.53.4 MBEmbeds, placements, sign-in, deep links, push notification handling, the video player—
UIcom.begenuin:ui:2.1.54.5 MBComments, search, notifications screen, community and group detail screens, deep-link destinationsCore
Cameracom.begenuin:camera:2.1.548 MBRecording and uploading video postsCore, UI
AIcom.begenuin:ai:2.1.56.5 MBAI-assisted creation of communities and groupsCore, UI, Camera

Sizes are the published AAR files; the Camera module is large because it bundles the video-processing toolchain. Add only the modules you use, and keep them on the same version.

Requirements

Minimum
AndroidAPI 24 (Android 7.0). Every module declares minSdkVersion 24.
Android Gradle Plugin8.6.0
LanguageKotlin or Java. The SDK ships with Kotlin 1.9.22 and exposes Java-callable methods through GenuinSDK.INSTANCE.
OrientationPortrait
Form factorPhones; the SDK isn't laid out for tablets or TV
UI toolkitViews, or Jetpack Compose through AndroidView

You also need an API key for your brand and at least one embed ID or placement ID, both from Brand Control Center. The key is the same one the Web and iOS SDKs use.

Add the SDK

Add the repositories

The SDK is published to Maven Central. Core also pulls in the Pangle ad SDK, served from ByteDance's repository, and two libraries hosted on JitPack, so declare all three in your project-level settings:

settings.gradle.kts
kotlin
dependencyResolutionManagement {
  repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
  repositories {
    google()
    mavenCentral()
    maven { setUrl("https://jitpack.io") }
    maven { setUrl("https://artifact.bytedance.com/repository/pangle") }
  }
}
build.gradle (project)
groovy
allprojects {
  repositories {
    google()
    mavenCentral()
    maven { url "https://jitpack.io" }
    maven { url "https://artifact.bytedance.com/repository/pangle" }
  }
}

Without the Pangle repository, Gradle fails to resolve com.pangle.global:pag-sdk even if you never show an ad.

Add the dependencies

Declare one version and apply it to every module you include. Modules depend on each other in a fixed chain (ai → camera → ui → core), so an app that uses the AI module declares all four:

build.gradle.kts (app)
kotlin
val genuinVersion = "2.1.5"
implementation("com.begenuin:core:$genuinVersion")
implementation("com.begenuin:ui:$genuinVersion")      // comments, search, notifications, detail screens
implementation("com.begenuin:camera:$genuinVersion")  // recording and upload
implementation("com.begenuin:ai:$genuinVersion")      // AI-assisted community and group creation
build.gradle (app)
groovy
def genuinVersion = "2.1.5"
implementation "com.begenuin:core:$genuinVersion"
implementation "com.begenuin:ui:$genuinVersion"
implementation "com.begenuin:camera:$genuinVersion"
implementation "com.begenuin:ai:$genuinVersion"

Mixing versions across modules fails at runtime, not at build time, so pin them to one variable.

The SDK brings its own video stack (androidx.media3 1.7.1 with ExoPlayer, HLS, and IMA), Glide, Retrofit, Lottie, and the RudderStack analytics client. If your app already uses any of these, Gradle resolves to the higher version.

Declare permissions

Core declares INTERNET, ACCESS_NETWORK_STATE, VIBRATE, and WAKE_LOCK in its own manifest, and Android merges them into yours. Add the rest in your AndroidManifest.xml only for the features you use:

PermissionAdd it forModule
android.permission.ACCESS_WIFI_STATENetwork-aware playbackCore
android.permission.READ_CONTACTSLetting users add contacts as community and group membersUI
android.permission.POST_NOTIFICATIONSPush notifications on Android 13 and laterUI
android.permission.CAMERA, android.permission.RECORD_AUDIO, plus <uses-feature android:name="android.hardware.camera" android:required="false" />Recording video postsCamera
android.permission.READ_MEDIA_VIDEO (API 33+) and android.permission.READ_EXTERNAL_STORAGE with android:maxSdkVersion="32"Uploading videos from the galleryCamera
android.permission.ACCESS_COARSE_LOCATION, android.permission.ACCESS_FINE_LOCATIONAI-created communities tailored to the user's locationAI
com.google.android.gms.permission.AD_IDMonetization. See Enable monetization.Core

Initialize the SDK

Call initSDK once, in your Application subclass, before any Genuin view is created:

YourApplication.kt
kotlin
class YourApplication : Application() {
  override fun onCreate() {
    super.onCreate()
    GenuinSDK.initSDK(this, "<<YOUR_API_KEY>>")                          // production
    // GenuinSDK.initSDK(this, "<<YOUR_API_KEY>>", genuinEnv = GenuinEnv.QA)  // QA environment
  }
}

genuinEnv defaults to GenuinEnv.PROD. Use GenuinEnv.QA only with a key issued for the QA environment; keys aren't shared between environments. Calling initSDK from an Activity works too, but views created before the call fail to load.

Note

Don't ship a build that points at QA. The two environments have separate brands, users, and content.

Replace the loading animation

The SDK shows a Lottie animation while content loads. To use your own, add a Lottie file named exactly loader_mix.json to res/raw/; a resource with that name overrides the SDK's.

Show an embed

An embed is a feed of videos from a community, group, or your whole brand. It's configured in Brand Control Center and rendered in one of four layouts: carousel, grid, feed, or dynamic. The layout comes from the embed's configuration, so the same code shows whatever the embed is set to.

For a carousel or grid inside your own screen, use GenuinEmbedView. For a full-screen feed or a standard wall that takes over a screen, use FeedEmbedFragment.

Add a GenuinEmbedView

Give the view a fixed height. A container with wrap_content height renders nothing.

activity_embed.xml
xml
<LinearLayout
    android:id="@+id/llCarouselContainer"
    android:layout_width="match_parent"
    android:layout_height="300dp">
  <com.begenuin.core.ui.customview.embed.GenuinEmbedView
      android:id="@+id/genuinEmbedView"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      app:interTileSpacing="12dp"
      app:leftInset="12dp"
      app:topInset="12dp"
      app:rightInset="12dp"
      app:bottomInset="12dp"
      app:tileCornerRadius="12dp" />
</LinearLayout>

Then configure and load it. The setters return the view, so they chain:

EmbedActivity.kt
kotlin
val params = hashMapOf<String, Any>(              // optional: pre-fills sign-up for SSO users
  "name" to "John Doe",
  "email" to "john.doe@example.com",
  "nickname" to "john",
  "mobile" to "{{PHONE_NUMBER}}"
)
val contextualParams = hashMapOf<String, Any>(    // optional: tailors the feed to the screen and viewer
  "page_context" to "{{PAGE_CONTEXT}}",
  "geo" to JSONObject().put("lat", 40.7128).put("long", -74.0060).toString()
)

genuinEmbedView
  .setEmbedParams(
    embedId = "{{EMBED_ID}}",
    uniqueId = "{{UNIQUE_ID}}",               // optional
    interactionDeepLink = "{{DEEP_LINK}}",    // optional
    isDirectDeepLinkEnabled = false,
    isShowProfileEnabled = false
  )
  .setActivity(this)
  .setSSOToken("{{SSO_TOKEN}}")               // optional: signs the viewer in
  .setParams(params)
  .setContextualParams(contextualParams)
  .setInterTileSpacing(24)                    // pixels
  .setInsets(left = 24, top = 24, right = 24, bottom = 24)   // pixels
  .setTileCornerRadius(24)                    // pixels
  .load()

To create the view in code instead of XML, construct GenuinEmbedView(context), add it to a container with a fixed height, and configure it the same way.

#### Embed parameters

ParameterTypeDefaultDescription
embedIdStringRequiredThe embed to load, from Brand Control Center.
uniqueIdStringnoneSet a different value for each instance when you show the same embed on several screens, or more than once on one screen.
interactionDeepLinkStringnoneA URL to open instead of the SDK's own flow when the viewer interacts in the full-screen view. The URL is validated; an invalid URL makes the redirect fail silently.
isDirectDeepLinkEnabledBooleanfalseWhen true, interactions open the specific video in your white-labelled app and interactionDeepLink is ignored.
isShowProfileEnabledBooleanfalseWhen true, signed-in viewers see their profile picture in the full-screen view, with access to account settings and sign-out.

#### User parameters (setParams)

Pass these only together with an SSO token. They pre-fill the viewer's Genuin profile when their account is created.

KeyTypeDescription
nameStringDisplay name
emailStringEmail address
mobileStringPhone number
nicknameStringUsed as the viewer's nickname if it's free; otherwise the SDK generates one
profile_imageStringURL of a profile image to show in the SDK

#### Contextual parameters (setContextualParams)

KeyTypeDescription
page_contextStringWhat the current screen is about. The feed is ranked toward it.
geoJSON string{"lat": <double>, "long": <double>} to rank by location

Placements accept a longer list. See Contextual parameters for placements.

#### Layout setters

SetterXML attributeDefault
setInterTileSpacing(px)app:interTileSpacing8 dp
setInsets(left, top, right, bottom)app:leftInset, app:topInset, app:rightInset, app:bottomInsetleft 16 dp, top 8 dp, right 16 dp, bottom 0 dp
setTileCornerRadius(px)app:tileCornerRadius8 dp

XML attributes take dp. The programmatic setters take pixels, so convert with your display density.

Tile corner radius, insets, and inter-tile spacing on a carousel embed.

Corner radius, insets, and tile spacing on a carousel embed.

Control playback with the lifecycle

Videos autoplay. Pause them when the screen leaves the foreground and release the player when it's destroyed, or playback continues in the background:

Lifecycle
kotlin
override fun onResume() { super.onResume(); genuinEmbedView.play() }
override fun onPause() { genuinEmbedView.pause(); super.onPause() }
override fun onDestroy() { genuinEmbedView.release(); super.onDestroy() }

Load asynchronously

load() shows the view immediately with a shimmer while data arrives. To keep the view hidden until the feed is ready, use loadAsync:

Load asynchronously
kotlin
genuinEmbedView.loadAsync { success ->
  genuinEmbedView.visibility = if (success) View.VISIBLE else View.GONE
}

success is false when the embed doesn't exist, the key is wrong, or there's no network.

Show a full-screen feed or standard wall

Both use FeedEmbedFragment; the embed's configuration decides which layout appears. Add a FrameLayout that fills the screen and insert the fragment:

FullEmbedActivity.kt
kotlin
import com.begenuin.core.ui.fragment.FeedEmbedFragment

val fragment = FeedEmbedFragment.newInstance(
  embedId = "{{EMBED_ID}}",
  uniqueId = "{{UNIQUE_ID}}",               // optional
  interactionDeepLink = "{{DEEP_LINK}}",    // optional
  isDirectDeepLinkEnabled = false,
  ssoToken = "{{SSO_TOKEN}}",               // optional
  params = params,                          // optional
  contextualParams = contextualParams,      // optional
  isShowProfileEnabled = false
)
supportFragmentManager.beginTransaction()
  .add(R.id.frameLayout, fragment)
  .addToBackStack("GenuinFeed")
  .commit()

The parameters mean the same as on GenuinEmbedView. The fragment manages its own playback lifecycle.

Use Jetpack Compose

Wrap the view in AndroidView. The view needs an Activity, so resolve it from LocalContext:

Compose wrapper
kotlin
@Composable
fun GenuinCarousel(embedId: String, modifier: Modifier = Modifier) {
  val activity = LocalContext.current as? Activity
    ?: throw IllegalStateException("GenuinEmbedView needs an Activity context")
  AndroidView(
    modifier = modifier,            // give it a height, e.g. Modifier.fillMaxWidth().height(400.dp)
    factory = { context ->
      GenuinEmbedView(context)
        .setEmbedParams(embedId)
        .setActivity(activity)
        .load()
    }
  )
}

For a full-screen feed in Compose, host FeedEmbedFragment in a FragmentContainerView inside AndroidView, and add the fragment only when findFragmentById returns null so recomposition doesn't add it twice.

Show a placement

A placement is an embed whose content is chosen at render time from the context you pass. That context is the page, the viewer's location and segment, and the communities or users to draw from. It needs a placement ID and a style ID, both from Brand Control Center. It renders through GenuinPlacementView, which has the same setters and lifecycle as GenuinEmbedView.

Show a placement
kotlin
genuinPlacementView
  .setPlacementParams(
    placementId = "{{PLACEMENT_ID}}",
    styleId = "{{STYLE_ID}}",
    uniqueId = "{{UNIQUE_ID}}",               // optional
    interactionDeepLink = "{{DEEP_LINK}}",    // optional
    isDirectDeepLinkEnabled = false
  )
  .setActivity(this)
  .setSSOToken("{{SSO_TOKEN}}")               // optional
  .setParams(params)                          // optional
  .setContextualParams(contextualParams)
  .load()

The XML element is com.begenuin.core.ui.customview.embed.GenuinPlacementView, with the same layout attributes as the embed view.

Contextual parameters for placements

Every key is optional. JSON values are passed as strings (JSONObject.toString()); lists as JSONArray.

KeyTypeDescription
page_contextStringWhat the current screen is about
previous_page_contextStringWhat the previous screen was about
user_contextStringAnything else you know about the viewer's intent
urlStringThe web URL this screen corresponds to
timeNumberA Unix timestamp to rank against scheduled content
geoJSONlat, long (doubles), and radius_limit
placeJSONcountry (code), state, city, zipcode
user_segmentsJSONage, min_age, max_age, segment, gender (M, F, or O), race
user_interestJSON array of stringsInterests to rank toward
brands_idsJSON arrayRestrict to these brands
community_idsJSON arrayRestrict to these communities
loop_idsJSON arrayRestrict to these groups
posted_by_user_idsJSON arrayRestrict to posts by these users

The _ids keys scope the feed; the others only change its order. Which signals a placement honours is set per placement under Grow > Placement > Implement in Brand Control Center.

Sign users in

Viewers can use embeds without an account. To let them post, comment, or follow as themselves, sign them in with single sign-on. Your server issues an SSO token for the user, and the SDK creates or matches their Genuin account from it. Genuin's own login screens never appear.

Sign in with an SSO token

Call ssoLogin once your app has authenticated the user. Pass the same user parameters you'd pass to an embed; they're used the first time the account is created.

Sign in
kotlin
GenuinSDK.ssoLogin(
  context = this,
  ssoToken = "{{SSO_TOKEN}}",
  params = params                       // optional
) { success ->
  // success is false when the token is invalid or expired
}
Sign in (Java)
java
// Java
GenuinSDK.INSTANCE.ssoLogin(context, "{{SSO_TOKEN}}", params, success -> {
  return Unit.INSTANCE;
});

You can also set the token on an individual view with setSSOToken. ssoLogin signs the user in for every view in the app.

Sign the user out of the SDK when they sign out of your app:

Sign out
kotlin
GenuinSDK.ssoLogout(context = this) { success -> }

Let the SDK ask your app to sign in

If the viewer isn't signed in and taps an action that needs an account, the SDK calls back into your app instead of showing its own login. Register the callback once, run your login flow in it, and finish with ssoLogin:

GenuinInterface
kotlin
GenuinSDK.registerInterface(object : GenuinInterface {
  override fun onLogin(activity: Activity) {
    // Start your app's sign-in flow. When it completes:
    // GenuinSDK.ssoLogin(activity, "{{SSO_TOKEN}}")
  }
})

Without a registered interface, the SDK uses its own sign-in screens.

For how tokens are issued and validated, see Choose how members sign in to the Genuin SDK and How Auto Login works.

Links to Genuin content, such as a shared video or an invitation, use your white-labelled community domain. To open them in your app instead of the browser:

  1. White-label your community so links use your domain.
  2. Set up Android App Links for that domain in your app.
  3. Send your Genuin contact your package name and the SHA-256 fingerprint of every certificate that signs your app, including the Google Play app signing key. Genuin serves assetlinks.json on your white-labelled domain from your brand's configuration, so you don't host it. Handle Genuin deep links in your app shows how to check it.
  4. When your app receives a link, hand it to the SDK. You can do this as soon as the intent arrives, or after your home screen has loaded if you want it to sit behind your own UI:
Handle a link
kotlin
GenuinSDK.handleDeepLink(context = this, deeplinkData = intent)

Android has no check method like iOS's willHandleDeepLink. Pass only links on your Genuin domain, because the SDK drops a link it doesn't recognize instead of opening the browser.

Videos can carry link-outs to product pages. When a link-out points at a URL your app handles, the SDK gives you the intent instead of opening the browser:

Link-out interceptor
kotlin
GenuinSDK.registerLinkOutInterceptor(object : GenuinLinkOutInterceptor {
  override fun onLinkOutIntercept(intent: Intent, activity: Activity) {
    // Route the intent inside your app
  }
})

Handle push notifications

Genuin sends notifications for replies, mentions, and community activity through Firebase Cloud Messaging (FCM). Your app owns the FCM integration, and the SDK registers the token and builds the notifications it recognizes. Genuin also needs your Firebase project's credentials to send anything; Set up push notifications in the Genuin SDK covers that step, the payload, and every notification type.

  1. Create the app in the Firebase console, add google-services.json to your project, and add the FCM client SDK.
  2. Declare POST_NOTIFICATIONS and request it at runtime on Android 13 and later.
  3. Register the FCM token with the SDK, on first launch and whenever it changes:
Register the FCM token
kotlin
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
  if (task.isSuccessful) GenuinSDK.registerFCMToken(this, task.result)
}
  1. In your FirebaseMessagingService, let the SDK handle its own messages while the app is in the foreground, and re-register on token refresh:
MyFirebaseMessagingService.kt
kotlin
class MyFirebaseMessagingService : FirebaseMessagingService() {
  override fun onMessageReceived(remoteMessage: RemoteMessage) {
    val data = remoteMessage.data
    if (GenuinSDK.willHandleForegroundNotification(data)) {
      GenuinSDK.handleForegroundNotifications(
        context = this,
        data = data,
        message = remoteMessage.notification?.body ?: "",
        title = remoteMessage.notification?.title,
        smallNotificationIcon = R.drawable.ic_notification
      )
    } else {
      // Your app's other notifications
    }
  }

  override fun onNewToken(token: String) {
    GenuinSDK.registerFCMToken(this, token)
  }
}
  1. When the user taps a notification while the app is in the background or closed, Android delivers the payload to your launcher activity. Pass it on there:
Notification tap
kotlin
if (GenuinSDK.willHandleNotification(intent)) {
  GenuinSDK.handleBackgroundNotifications(context = this, intent = intent)
}

willHandleForegroundNotification and willHandleNotification return false for notifications that aren't the SDK's, so your own handling is unaffected.

Listen to SDK callbacks

Register these on GenuinSDK to react to what happens inside Genuin views:

CallbackInterfaceFires when
registerEventCallbackGenuinEventCallback.onEventCallback(name, properties)The SDK records an analytics event, for forwarding to your own analytics
registerPlayerCallbackGenuinPlayerCallback (onPlay, onPause, onError, onAudioStateChanged, onFullScreenOpen, onFullScreenClose, …)Playback or full-screen state changes, for pausing your own media or adjusting your UI
registerInteractionCallback / unregisterInteractionCallbackGenuinInteractionCallback.onInteraction(data, activity, continue)The viewer interacts with a post; call continue to let the SDK proceed
registerInterfaceGenuinInterface.onLogin(activity)The viewer needs to sign in. See Sign users in.
registerLinkOutInterceptorGenuinLinkOutInterceptor.onLinkOutIntercept(intent, activity)A link-out targets a URL your app handles

The SDK requests audio focus when a video plays with sound. Call GenuinSDK.abandonAudioFocus() if your app needs it back, and GenuinSDK.releasePlayerCache() to free the player's disk cache.

Enable monetization

If your brand runs ads in embeds, Google Play requires the app to declare its use of the advertising ID. Add the permission:

AndroidManifest.xml
xml
<uses-permission android:name="com.google.android.gms.permission.AD_ID" />

Then declare it in Google Play Console: open the app, go to Policy and programs > App content, and find Advertising ID. Click Manage, answer Yes to using the advertising ID, complete the questionnaire, and submit. Builds without the declaration are rejected at review.

Limitations

  • One version for all modules: core, ui, camera, and ai must be the same version. Mismatches fail at runtime.
  • Minimum API 24: Forcing a lower minSdk with tools:overrideLibrary lets the app build, but SDK features fail on devices below API 24.
  • Fixed-height containers: Embed and placement views render nothing inside a wrap_content height.
  • Activity context: Views need an Activity, set with setActivity; an application context doesn't work.
  • Portrait only: The SDK doesn't lay out for landscape, tablets, or TV.
  • Playback follows your lifecycle: Call play, pause, and release from the host screen, or audio continues in the background.
  • Deep links need the domain setup: handleDeepLink only resolves links on a white-labelled domain with a valid assetlinks.json.
  • Camera module size: Adding camera grows the app by about 48 MB before compression.

See also

TRUST AND COMPLIANCE

© 2026 Genuin Inc.

Genuin Footer