Integrate the Genuin SDK with React Native apps
Add Genuin carousels, standard walls, full-screen feeds, single sign-on, deep links, and push notifications to a React Native app with genuin-react-native 3.0.3.
The genuin-react-native package wraps the native Genuin SDKs. On Android it pins com.begenuin:core and com.begenuin:ui 2.1.5; on iOS it pins the GenuinCore and GenuinUI 2.1.5 pods. It exports four components (Carousel, StandardWall, Feed, and HomeFeed) and functions for single sign-on, link-out interception, and push notifications. Initialization, deep-link entitlements, and Firebase setup stay in your Android and iOS projects.
This page covers what the package adds and what you configure natively. For the behaviour underneath (embed layouts, user and contextual parameters, callbacks), see Integrate the Genuin Android SDK and Integrate the Genuin iOS SDK.
Requirements
| Minimum | |
|---|---|
| React Native | Any version is accepted as a peer dependency; the package is built and tested against 0.81.5. It ships a TurboModule spec for the New Architecture and a legacy module for apps with newArchEnabled=false. |
| Android | minSdk 24, compileSdk and targetSdk 36, Kotlin 2.1.20 (read from rootProject.ext.kotlinVersion), Java 17, and a Material Components theme |
| iOS | Deployment target 15.1 (React Native 0.81's minimum; the Genuin pods declare 13.0), CocoaPods with dynamic frameworks, and an Objective-C bridging header when AppDelegate is Swift |
| Push notifications | A Firebase project and @react-native-firebase/messaging |
| Credentials | An API key and one or more embed IDs from Brand Control Center |
The API key comes from Build > Publish Brand Community > SDK Integration in Brand Control Center. That screen has a React Native tab and a switch between the Live Environment and QA Environment keys. Embed IDs come from Generate embed codes from Brand Control Center.
Step 1: Install the package
npm install genuin-react-native # or: yarn add genuin-react-native
cd ios && pod installAutolinking registers GenuinSDKPluginPackage on Android and the genuin-react-native pod on iOS. The pod requires GenuinCore and GenuinUI at exactly 2.1.5, and the Android module requires com.begenuin:core and com.begenuin:ui 2.1.5, so you don't declare the native SDKs yourself.
Step 2: Configure the Android project
- Set the minimum SDK in
android/app/build.gradle:
android {
defaultConfig {
minSdk = 24
}
}- Check your repositories. The package adds ByteDance's Pangle repository (the Pangle SDK is a dependency of
com.begenuin:core) to every project at configuration time, andcorealso needs two libraries from JitPack. If yoursettings.gradleusesRepositoriesMode.FAIL_ON_PROJECT_REPOS, declare both there instead:
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url "https://jitpack.io" }
maven { url "https://artifact.bytedance.com/repository/pangle" }
}
}- Update
android/app/src/main/AndroidManifest.xml. The SDK's manifests declareandroid:allowBackup="false", so your app must match or the manifest merge fails. Add thetoolsnamespace, theINTERNETpermission, and the attributes below:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".MainApplication"
android:allowBackup="false"
android:theme="@style/AppTheme"
tools:remove="android:taskAffinity">
<activity
android:name=".MainActivity"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustPan"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>- Use a Material Components theme in
android/app/src/main/res/values/styles.xml. The SDK's own themes extendTheme.MaterialComponents.DayNight.NoActionBar.Bridge, and its views are Material Components that read theme attributes from your activity. A non-MaterialAppThemecrashes when the first Genuin view inflates.
<resources>
<style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar.Bridge">
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
</resources>- Add ProGuard rules for minified release builds. The
coreanduiAARs ship consumer rules that keep their own public classes and Retrofit, so a release build runs without any app-side rule. The package README asks for the block below as well; add it toandroid/app/proguard-rules.proif your shrunk build misbehaves:
-keep class com.bumptech.glide.** { *; }
-keep class com.begenuin.core.data.model.** { <fields>; }
-keepattributes Signature, InnerClasses, EnclosingMethod, *Annotation*
-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations
-keepclassmembers,allowshrinking,allowobfuscation interface * { @retrofit2.http.* <methods>; }
-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface <1>
-keep public class * implements java.lang.reflect.Type
-dontwarn javax.annotation.**
-dontwarn retrofit2.KotlinExtensions
-dontwarn com.google.android.play.core.**- To replace the loading animation (Optional), add a Lottie file named exactly
loader_mix.jsontoandroid/app/src/main/res/raw/. A resource with that name overrides the SDK's.
Step 3: Initialize the SDK on Android
Initialize in MainActivity.onCreate, before React renders the first Genuin component, and hand incoming deep links to the SDK:
import android.content.Intent
import android.os.Bundle
import com.facebook.react.ReactActivity
import com.genuinsdk.GenuinSDKPluginModule
class MainActivity : ReactActivity() {
override fun getMainComponentName(): String = "YourApp"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GenuinSDKPluginModule.initSDK(application, "<<YOUR_API_KEY>>") // isQAEnv = true for a QA key
if (intent?.data != null) GenuinSDKPluginModule.handleDeepLink(this, intent)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
if (intent.data != null) GenuinSDKPluginModule.handleDeepLink(this, intent)
}
}initSDK(application, apiKey, isQAEnv = false, fontFamily = "", gamAppId = "") calls the native GenuinSDK.initSDK with GenuinEnv.QA when isQAEnv is true. Keys aren't shared between environments. Keep the key out of source control by injecting it through a BuildConfig field or a secrets plugin.
For deep links to open your app, the manifest also needs an intent filter for your white-labelled domain, plus a Digital Asset Links file on that domain:
<activity android:name=".MainActivity" ...>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:host="{{YOUR_DOMAIN}}" android:scheme="https" />
</intent-filter>
</activity>Genuin serves assetlinks.json on your white-labelled domain once you send your package name and signing fingerprints; Handle Genuin deep links in your app covers the request. For push notifications, add the Google Services plugin to android/build.gradle and your google-services.json to android/app/:
buildscript {
dependencies {
classpath("com.google.gms:google-services:4.4.2")
}
}Step 4: Configure the iOS project
- In
ios/Podfile, build with dynamic frameworks, keepRNReanimatedstatic, and set the deployment target on every pod:
ENV['USE_FRAMEWORKS'] = 'dynamic'
platform :ios, min_ios_version_supported
use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym
target 'YourApp' do
config = use_native_modules!
use_react_native!(:path => config[:reactNativePath], :app_path => "#{Pod::Config.instance.installation_root}/..")
pre_install do |installer|
installer.pod_targets.each do |pod|
if pod.name.eql?('RNReanimated')
def pod.build_type; Pod::BuildType.static_library; end
end
end
end
post_install do |installer|
react_native_post_install(installer, config[:reactNativePath], :mac_catalyst_enabled => false)
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.1'
config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'
end
end
end
end- If your
AppDelegateis Swift, add an Objective-C wrapper, because the pod'sGenuinSDKPlugin.his Objective-C++. CreateGenuinModuleObjCWrapper.h:
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface GenuinModuleObjCWrapper : NSObject
+ (void)initSDK:(NSString *_Nullable)apiKey isQAEnv:(BOOL)isQAEnv loaderName:(NSString *_Nullable)loaderName showNavigationBar:(BOOL *_Nullable)showNavigationBar;
+ (BOOL)willHandleDeepLinkWithUrl:(NSURL *_Nonnull)url;
+ (BOOL)handleDeepLink:(NSURL *_Nullable)dlURL controller:(UIViewController *_Nullable)controller;
+ (void)handleForegroundNotification:(NSDictionary *)notificationData;
+ (UINavigationController *_Nullable)getGenuinNavigationController:(UIViewController *)viewController;
@end
NS_ASSUME_NONNULL_END- Create
GenuinModuleObjCWrapper.mm, which forwards each call toGenuinSDKPlugin:
#import "GenuinModuleObjCWrapper.h"
#import "GenuinSDKPlugin.h"
@implementation GenuinModuleObjCWrapper
+ (void)initSDK:(NSString *)apiKey isQAEnv:(BOOL)isQAEnv loaderName:(NSString *)loaderName showNavigationBar:(BOOL *)showNavigationBar {
[GenuinSDKPlugin initSDK:apiKey isQAEnv:isQAEnv loaderName:loaderName showNavigationBar:showNavigationBar];
}
+ (BOOL)willHandleDeepLinkWithUrl:(NSURL *)url { return [GenuinSDKPlugin willHandleDeepLinkWithUrl:url]; }
+ (BOOL)handleDeepLink:(NSURL *)dlURL controller:(UIViewController *)controller {
return [GenuinSDKPlugin handleDeepLink:dlURL controller:controller];
}
+ (void)handleForegroundNotification:(NSDictionary *)notificationData {
[GenuinSDKPlugin handleForegroundNotification:notificationData];
}
+ (UINavigationController *)getGenuinNavigationController:(UIViewController *)viewController {
return [GenuinSDKPlugin getGenuinNavigationController:viewController];
}
@end- Import the wrapper in your bridging header,
YourApp-Bridging-Header.h:
#import "GenuinModuleObjCWrapper.h"Step 5: Initialize the SDK on iOS
Initialize in didFinishLaunchingWithOptions, then wrap React Native's root view controller in the Genuin navigation controller. The SDK pushes its own screens (full-screen feed, comments, profile) onto that stack.
import UIKit
import React
import React_RCTAppDelegate
import Firebase
@main
class AppDelegate: RCTAppDelegate, UNUserNotificationCenterDelegate {
override func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
self.moduleName = "YourApp"
if FirebaseApp.app() == nil { FirebaseApp.configure() }
UNUserNotificationCenter.current().delegate = self
UIApplication.shared.registerForRemoteNotifications()
GenuinModuleObjCWrapper.initSDK("<<YOUR_API_KEY>>", isQAEnv: false,
loaderName: "{{LOTTIE_LOADER_NAME}}", showNavigationBar: nil)
addNavVC()
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
func addNavVC() {
DispatchQueue.main.async {
guard let rootVC = self.window.rootViewController else { return }
self.window.rootViewController = nil
let navVC = GenuinModuleObjCWrapper.getGenuinNavigationController(rootVC)
navVC?.setNavigationBarHidden(true, animated: true)
self.window.rootViewController = navVC
self.window.makeKeyAndVisible()
}
}
}loaderName is the name of a Lottie file in your main bundle that replaces the SDK's loading animation; pass nil to keep the default. isQAEnv: true points the SDK at the QA environment and needs a QA key.
Deep links on iOS arrive as universal links. Let the SDK claim the ones it recognizes:
override func application(_ application: UIApplication, continue userActivity: NSUserActivity,
restorationHandler: @escaping ([any UIUserActivityRestoring]?) -> Void) -> Bool {
if let url = userActivity.webpageURL,
GenuinModuleObjCWrapper.willHandleDeepLink(with: url),
GenuinModuleObjCWrapper.handleDeepLink(url, controller: window.rootViewController) {
return true
}
return false
}Add the Associated Domains entitlement with applinks:{{YOUR_DOMAIN}}. Genuin serves the apple-app-site-association file on that domain once you send your Team ID and bundle ID; Handle Genuin deep links in your app covers the request.
For push notifications, forward the APNs token to Firebase, register the FCM token with the SDK, and pass tapped notifications to it. The package doesn't export the push functions to JavaScript on iOS, so this happens in AppDelegate. Set Messaging.messaging().delegate = self and the notification center delegate in didFinishLaunchingWithOptions:
import FirebaseMessaging
import GenuinCore
override func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Messaging.messaging().apnsToken = deviceToken
}
extension AppDelegate: MessagingDelegate {
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
if let fcmToken { GenuinSDK.shared.registerFCMToken(token: fcmToken) }
}
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.list, .banner, .badge, .sound])
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
GenuinModuleObjCWrapper.handleForegroundNotification(["data": response.notification.request.content.userInfo])
completionHandler()
}
}handleForegroundNotification checks whether the payload is Genuin's and, if so, opens the matching screen on the top view controller. Other notifications are ignored, so your own handling is unaffected.
Step 6: Render an embed
Each component renders one embed configured in Brand Control Center. Give every component an explicit size through style; a component without a height renders nothing.
import React, { useRef } from 'react';
import { Carousel, type PlayPauseRef } from 'genuin-react-native';
export default function CarouselScreen() {
const carouselRef = useRef<PlayPauseRef>(null);
return (
<Carousel
ref={carouselRef}
style={{ height: 400, width: '100%' }}
embedId="{{EMBED_ID}}"
uniqueId="home-carousel"
ssoToken="{{SSO_TOKEN}}"
isShowProfileEnabled={false}
isDirectDeepLinkEnabled={false}
/>
);
}| Component | Renders | Platforms |
|---|---|---|
| Carousel | A horizontal carousel or grid, depending on the embed's layout | Android, iOS |
| StandardWall | A standard wall that takes over its container | Android, iOS |
| Feed | A full-screen vertical feed | Android, iOS |
| HomeFeed | The brand's home feed, with no embed ID | Android only |
StandardWall and Feed are normally given { height: '100%', width: '100%' }. Render HomeFeed only when Platform.OS === 'android'; on iOS the native view isn't registered and the component throws.
Props
All four components accept style. Carousel, StandardWall, and Feed accept the embed props below; HomeFeed takes none.
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| embedId | string | Yes | — | The embed to load, from Brand Control Center |
| uniqueId | string | No | — | Set a different value for each instance when the same embed appears more than once, on one screen or across screens |
| ssoToken | string | No | — | Signs the viewer in before the embed loads. See Step 7. |
| params | object | No | — | name, email, mobile, nickname, profile_image to pre-fill the viewer's profile. On Android it's applied only together with ssoToken. |
| contextualParams | object | No | — | page_context and geo to rank the feed toward the screen and the viewer. See Deliver contextual feeds. |
| interactionDeepLink | string | No | — | A URL to open instead of the SDK's own flow when the viewer interacts in the full-screen view. Android only. |
| isDirectDeepLinkEnabled | boolean | No | false | Opens the specific video in your white-labelled app on interaction; interactionDeepLink is then ignored. Needs the deep-link setup from Steps 3 and 5. |
| isDirectDeepLinkWithLoginEnabled | boolean | No | false | Like isDirectDeepLinkEnabled, after the viewer signs in. Android only. |
| isShowProfileEnabled | boolean | No | false | Shows the signed-in viewer's profile picture in the full-screen view, with account settings and sign-out |
| interTileSpacing | number (dp) | No | 8 | Gap between carousel tiles. Carousel only. |
| carouselInsetTop, carouselInsetLeft, carouselInsetBottom, carouselInsetRight | number (dp) | No | 8, 16, 0, 16 | Space between the carousel and its container. Carousel only. |
| tileCornerRadius | number (dp) | No | 8 | Rounding of each tile. Carousel only. |
| isVerticalScrollDirection | boolean | No | false | Declared in the typings but has no effect in 3.0.3 |
Numbers are in dp on Android (the package converts them to pixels) and in points on iOS.
Pause and resume playback
Videos autoplay. On Android, the ref gives you pause() and resume(), which forward to the native view; call them when your screen loses and regains focus:
import React from 'react';
import { useFocusEffect } from '@react-navigation/native';
useFocusEffect(
React.useCallback(() => {
carouselRef.current?.resume();
return () => carouselRef.current?.pause();
}, [])
);On iOS the components ignore the ref, and playback follows the native view's own lifecycle.
Step 7: Sign users in with single sign-on
Viewers can browse without an account. To let them post, comment, or follow, your server issues an SSO token for the user and you pass it to the SDK. Either set ssoToken on a component, or sign in once for every view:
import { ssoLogin, ssoLogout } from 'genuin-react-native';
// After your app authenticates the user
ssoLogin('{{SSO_TOKEN}}', { name: 'John Doe', email: 'john.doe@example.com' });
// When the user signs out of your app
ssoLogout();The second argument is optional and takes the same keys as params; the SDK uses them the first time the account is created. How tokens are issued and validated is explained in Choose how members sign in to the Genuin SDK.
Let the SDK ask your app to sign in
When a signed-out viewer taps an action that needs an account, the SDK can return to your app instead of showing its own login. Register once at startup, and listen for the two events the package emits:
import { DeviceEventEmitter, NativeEventEmitter, Platform } from 'react-native';
import { GenuinSDKPlugin, registerCallback, registerLinkOutCallback, ssoLogin } from 'genuin-react-native';
registerCallback(); // 'NavigateToScreen' with 'Login' when sign-in is needed
registerLinkOutCallback(['shop.example.com']); // 'LinkOutRedirection' with the URL for these domains
const emitter = Platform.OS === 'ios' ? new NativeEventEmitter(GenuinSDKPlugin) : DeviceEventEmitter;
emitter.addListener('NavigateToScreen', (screen: string) => {
if (screen === 'Login') {
// Run your sign-in flow, then: ssoLogin('{{SSO_TOKEN}}')
}
});
emitter.addListener('LinkOutRedirection', (url: string) => {
// Route the product URL inside your app
});On Android, the SDK first brings your launcher activity back to the front and then emits the event. Without registerCallback, the SDK shows its own sign-in screens. registerLinkOutCallback takes the domains your app handles; link-outs to any other domain open in the browser.
Step 8: Handle push notifications
Genuin sends notifications for replies, mentions, and community activity through Firebase Cloud Messaging (FCM). 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.
- Create the Firebase project, add
google-services.json(Android) andGoogleService-Info.plist(iOS), and install@react-native-firebase/appand@react-native-firebase/messaging. - On Android, mount this controller once, after your first screen renders. On iOS, the
AppDelegatecode in Step 5 handles tokens and taps:
import { useEffect } from 'react';
import { PermissionsAndroid, Platform } from 'react-native';
import messaging, { type FirebaseMessagingTypes } from '@react-native-firebase/messaging';
import { GenuinSDKPlugin } from 'genuin-react-native';
const open = (m: FirebaseMessagingTypes.RemoteMessage | null) => {
if (m && GenuinSDKPlugin.willHandleBackgroundNotifications(m)) GenuinSDKPlugin.handleBackgroundNotifications(m);
};
export default function NotificationController() {
useEffect(() => {
if (Platform.OS !== 'android') return; // iOS: AppDelegate, Step 5
PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS);
messaging().getToken().then((token) => GenuinSDKPlugin.registerFCMToken(token));
const offRefresh = messaging().onTokenRefresh((token) => GenuinSDKPlugin.registerFCMToken(token));
const offMessage = messaging().onMessage(async (m) => {
const payload = { data: m.data, body: m.notification?.body ?? '', title: m.notification?.title, icon: 'drawable/ic_notification' };
if (GenuinSDKPlugin.willHandleForegroundNotifications(payload)) GenuinSDKPlugin.handleForegroundNotifications(payload);
});
const offOpened = messaging().onNotificationOpenedApp(open);
messaging().getInitialNotification().then(open);
return () => { offRefresh(); offMessage(); offOpened(); };
}, []);
return null;
}The foreground payload must carry data and body. icon names the small notification icon in your Android resources as folder/name; the SDK falls back to a system icon when it's missing. registerFCMToken needs a running activity, which is why the controller mounts after the first screen. willHandleForegroundNotifications and willHandleBackgroundNotifications return false for notifications that aren't Genuin's, so your own notifications are unaffected.
Limitations
- Pinned native SDKs: the package fixes the Android and iOS SDKs at 2.1.5. To use another version you need a fork.
- Explicit size required: every component needs a height and width in
style;wrap_content-style sizing renders nothing. - iOS ignores some props and the ref:
interactionDeepLinkandisDirectDeepLinkWithLoginEnabledaren't forwarded,StandardWallignoresparamsandcontextualParams, andpause()andresume()have no effect. HomeFeedis Android only: rendering it on iOS throws, because no native view is registered.- Push helpers are Android only:
registerFCMTokenand the four notification functions aren't exported to JavaScript on iOS; iOS taps go throughAppDelegate. - No web target: the package resolves native views at import time, so it can't run in a web bundle.
- Material theme required: Android needs a
Theme.MaterialComponentsparent on the app theme. - Deep links need a white-labelled domain:
handleDeepLinkonly resolves links on a domain configured in Brand Control Center.

