Initialize the Genuin SDK and load a feed in Flutter
Initialize genuin_sdk on each platform, render carousel, feed, and standard wall embeds, and connect sign-in, deep links, and push notifications.
The genuin_sdk plugin, version 1.0.1, has three parts. A native initializer on each platform starts the Genuin SDK with your API key. Four Flutter widgets host the native embed views. The GenuinSdk class sends sign-in, notification, and playback calls over a method channel named genuin_sdk.
This page documents all three. It assumes your project is configured as described in Integrate the Genuin SDK with Flutter apps: JitPack, minSdk 24, the Material theme, and the Podfile settings.
Initialize the SDK
Initialization is native code that runs once at launch, before the first Genuin widget is built. There's no Dart initializer: the GenuinSdk class has no init method, so the API key never passes through Dart.
Android
Call GenuinSdkPlugin.initSDK in onCreate of your launcher activity. The activity must extend FlutterFragmentActivity, because the plugin hosts the native feeds in fragments.
import android.os.Bundle
import com.begenuin.genuin_sdk.GenuinSdkPlugin
import io.flutter.embedding.android.FlutterFragmentActivity
class MainActivity : FlutterFragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GenuinSdkPlugin.initSDK(this, "<<YOUR_API_KEY>>")
}
}| Parameter | Type | Required | Description |
|---|---|---|---|
| activity | Activity | Required | Your FlutterFragmentActivity. The plugin passes its Application to the native SDK. |
| apiKey | String? | Required | Your brand's API key from Brand Control Center. With null, the plugin skips initialization and every embed stays empty. |
iOS
Call GenuinSdkPlugin.initialize in AppDelegate before registering the plugins, then wrap the Flutter view controller in the navigation controller the SDK provides. Genuin's full-screen and comment views push onto that navigation controller; without it, taps inside an embed don't open anything.
import Flutter
import UIKit
import genuin_sdk
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GenuinSdkPlugin.initialize(apiKey: "<<YOUR_API_KEY>>", loaderName: "{{LOTTIE_NAME}}")
GeneratedPluginRegistrant.register(with: self)
if let flutterViewController = window?.rootViewController as? FlutterViewController {
let navigationController = GenuinSdkPlugin.getGenuinNavigationController(rootViewController: flutterViewController)
navigationController.setNavigationBarHidden(true, animated: false)
window?.rootViewController = navigationController
window?.makeKeyAndVisible()
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| apiKey | String | Required | — | Your brand's API key from Brand Control Center. |
| showNavigationBar | Bool | Optional | false | Passed to the native SDK's GenuinConfiguration.showNavigationBar. The example app leaves it false. |
| loaderName | String? | Optional | nil | Name of a Lottie animation in your main bundle to use as the loading indicator. With nil, the SDK shows its own. |
If your app uses a UISceneDelegate, also forward scene(_:willConnectTo:options:) to GenuinSdkPlugin.scene(_:willConnectTo:options:) so the native SDK sees the connection options.
Note: There's no environment parameter on either platform. The plugin talks to production only, so use a production API key.
Embed widgets
| Widget | Import | Shows | Platforms |
|---|---|---|---|
| Carousel | package:genuin_sdk/genuin_carousel_embed_view.dart | A carousel embed inside your own layout | Android, iOS |
| Feed | package:genuin_sdk/genuin_feed_embed_view.dart | A full-screen feed embed | Android, iOS |
| StandardWall | package:genuin_sdk/genuin_standard_wall_view.dart | A standard wall embed | Android, iOS |
| HomeFeed | package:genuin_sdk/genuin_feed_view.dart | Your brand's home feed, without an embed ID | Android only |
Each widget is a platform view: a UiKitView on iOS and a PlatformViewLink with an AndroidViewSurface on Android. Platform views have no intrinsic size, so give every widget a bounded height and width with a SizedBox, an Expanded, or a Scaffold body. A widget inside an unbounded Column throws during layout.
The embed's layout comes from its configuration in Brand Control Center. See Generate embed codes from Brand Control Center for how to create one and copy its ID.
Show a carousel
import 'package:flutter/material.dart';
import 'package:genuin_sdk/genuin_carousel_embed_view.dart';
class CarouselSection extends StatelessWidget {
const CarouselSection({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
height: 300,
width: MediaQuery.of(context).size.width,
child: const Carousel(
embedId: '{{EMBED_ID}}',
uniqueId: '{{UNIQUE_ID}}',
ssoToken: '{{SSO_TOKEN}}', // optional: signs the viewer in
),
);
}
}The plugin's example app uses a height of 300 logical pixels. Tiles scale to the height you give.
Show a full-screen feed or standard wall
Feed and StandardWall take the same parameters as Carousel. Use the one that matches the embed's layout in Brand Control Center.
import 'package:flutter/material.dart';
import 'package:genuin_sdk/genuin_feed_embed_view.dart';
class FeedScreen extends StatelessWidget {
const FeedScreen({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold(
body: SafeArea(
child: Feed(embedId: '{{EMBED_ID}}', uniqueId: '{{UNIQUE_ID}}'),
),
);
}
}For a standard wall, import genuin_standard_wall_view.dart and replace Feed with StandardWall.
Show the brand home feed
The home feed is your brand's own feed and needs no embed ID. On Android, the HomeFeed widget renders it inline. On iOS, there's no inline widget: GenuinSdk().loadSdk() presents the SDK's home screen as a full-screen modal. loadSdk() also works on Android, where it starts the SDK's feed activity on top of your app.
import 'dart:io' show Platform;
import 'package:flutter/material.dart';
import 'package:genuin_sdk/genuin_feed_view.dart';
import 'package:genuin_sdk/genuin_sdk.dart';
class HomeFeedScreen extends StatelessWidget {
const HomeFeedScreen({super.key});
@override
Widget build(BuildContext context) {
if (Platform.isAndroid) {
return const Scaffold(body: HomeFeed());
}
return Scaffold(
body: Center(
child: FilledButton(
onPressed: () => GenuinSdk().loadSdk(),
child: const Text('Open community'),
),
),
);
}
}Widget parameters
Carousel, Feed, and StandardWall share one parameter set. HomeFeed takes none.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| embedId | String | Required | — | The embed to load, from Brand Control Center. |
| uniqueId | String | Optional | "" | Tells instances of the same embed apart. The plugin keys each native view by embedId plus uniqueId, so give every instance on screen a different value, or pause and resume calls hit the wrong one. |
| ssoToken | String | Optional | "" | Signs the viewer in for this embed with a single sign-on token from your server. Alternatively, call ssoLogin once for the whole app. |
| interactionDeepLink | String | Optional | "" | A URL to open instead of the SDK's own flow when the viewer interacts in the full-screen view. The URL must be valid; an invalid URL makes the redirect fail silently. |
| isDirectDeepLinkEnabled | bool | Optional | false | When true, interactions open the specific video in your white-labelled app and interactionDeepLink is ignored. Needs a white-labelled domain and the deep-link handling below. |
| isDirectDeepLinkWithLoginEnabled | bool | Optional | false | Passed through to the native SDK's embed configuration. Genuin hasn't documented it; leave it false. |
| isShowProfileEnabled | bool | Optional | false | When true, a signed-in viewer sees their profile picture in the top-right corner of the full-screen view, with account settings and sign-out. |
Parameters are read once, when the native view is created. Changing embedId or ssoToken on a widget that's already built has no effect; give the widget a new key to recreate it.
The GenuinSdk class
import 'package:genuin_sdk/genuin_sdk.dart';
final genuin = GenuinSdk();Every method returns a Future. The notification and playback methods take a Map<String, dynamic>; the tables below list the keys.
| Method | Returns | Android | iOS | Description |
|---|---|---|---|---|
| loadSdk() | Future<void> | Yes | Yes | Opens the SDK's home screen: a new activity on Android, a full-screen modal on iOS. |
| ssoLogin(String ssoToken) | Future<void> | Yes | Yes | Signs the viewer in for every Genuin view in the app. |
| ssoLogout() | Future<void> | Yes | Yes | Signs the viewer out of the SDK. |
| registerFCMToken(String token) | Future<void> | Yes | Yes | Registers the device's Firebase Cloud Messaging token for Genuin notifications. |
| willHandleForegroundNotifications(Map) | Future<bool?> | Yes | Yes | true when a notification received in the foreground is Genuin's. |
| handleForegroundNotifications(Map) | Future<void> | Yes | No-op | Shows the Genuin notification. On iOS it returns at once; the system banner is shown by your AppDelegate. |
| willHandleBackgroundNotifications(Map) | Future<bool?> | Yes | Yes | true when a tapped notification is Genuin's. |
| handleBackgroundNotifications(Map) | Future<void> | Yes | Yes | Opens the screen a tapped notification points to. |
| pauseCarousel(Map), resumeCarousel(Map) | Future<void> | Yes | Throws | Pauses or resumes autoplay in a Carousel. |
| pauseFeed(Map), resumeFeed(Map) | Future<void> | Yes | Throws | Pauses or resumes playback in a Feed. |
| pauseStandWall(Map), resumeStandWall(Map) | Future<void> | Yes | Throws | Pauses or resumes playback in a StandardWall. Note the spelling: StandWall. |
| pauseHomeFeed(), resumeHomeFeed() | Future<void> | Yes | Throws | Pauses or resumes the HomeFeed widget. |
"Throws" means the iOS plugin has no implementation for the method, so the call fails with MissingPluginException. Guard those calls with Platform.isAndroid.
Control playback
Videos autoplay. On Android, pause them when your app goes to the background and resume them when it returns, or audio continues while the app is hidden. The map identifies the widget by the same embedId and uniqueId you gave it.
class _CarouselSectionState extends State<CarouselSection> with WidgetsBindingObserver {
final _genuin = GenuinSdk();
final _config = {'embedId': '{{EMBED_ID}}', 'uniqueId': '{{UNIQUE_ID}}'};
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (!Platform.isAndroid) return; // pause and resume are Android-only
if (state == AppLifecycleState.paused) _genuin.pauseCarousel(_config);
if (state == AppLifecycleState.resumed) _genuin.resumeCarousel(_config);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
}Use pauseFeed and resumeFeed for a Feed, pauseStandWall and resumeStandWall for a StandardWall, and pauseHomeFeed and resumeHomeFeed for the HomeFeed, which needs no map.
Sign users in
Viewers can browse 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. How tokens are issued is covered in Choose how members sign in to the Genuin SDK.
You have two ways to pass the token. Set ssoToken on each widget, or call ssoLogin once after your own login completes. You don't need both.
final genuin = GenuinSdk();
// After the user signs in to your app
await genuin.ssoLogin('{{SSO_TOKEN}}');
// When the user signs out of your app
await genuin.ssoLogout();Call ssoLogout whenever the user signs out of your app, or the Genuin session outlives yours.
Handle deep links
Links to Genuin content use your white-labelled domain. The intent filter and the Associated Domains entitlement are described on the integration page; this section covers the code that hands an incoming link to the SDK.
Android app links
With launchMode="singleTask", a link that arrives while the app is running reaches onNewIntent. Handle both the launch intent and new intents:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GenuinSdkPlugin.initSDK(this, "<<YOUR_API_KEY>>")
handleDeepLink(intent)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
handleDeepLink(intent)
}
private fun handleDeepLink(intent: Intent?) {
intent?.data?.let { GenuinSdkPlugin.handleDeepLink(this, intent) }
}GenuinSdkPlugin.handleDeepLink(activity, intent) forwards the intent to the native SDK, which opens the matching screen.
iOS universal links
Universal links arrive through application(_:continue:restorationHandler:). Forward the activity to the SDK, then ask the plugin to open the URL:
override func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([any UIUserActivityRestoring]?) -> Void
) -> Bool {
let handled = GenuinSdkPlugin.flutterapplication(application, continue: userActivity, restorationHandler: restorationHandler)
if GenuinSdkPlugin.handleDeepLink(dlURL: userActivity.webpageURL, controller: window?.rootViewController) {
return true
}
return handled
}handleDeepLink(dlURL:controller:) returns true when the SDK recognizes the URL. It then opens the destination on the given view controller after a 0.3-second delay, which gives the Flutter view time to appear on a cold start.
Handle push notifications
The SDK sends notifications for replies, mentions, and community activity through Firebase Cloud Messaging. Your app owns the Firebase integration; the plugin registers the token and handles the notifications it recognizes. For the overall flow, see Set up push notifications in the Genuin SDK.
- Add Firebase to the app with the FlutterFire setup, including
firebase_coreandfirebase_messaging. - Register the token after the viewer grants notification permission:
final messaging = FirebaseMessaging.instance;
final settings = await messaging.requestPermission(alert: true, badge: true, sound: true);
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
final token = await messaging.getToken();
if (token != null) {
await genuin.registerFCMToken(token);
}
}- Handle messages that arrive while the app is in the foreground. On Android the plugin builds the notification itself and needs
body,data, andicon:
FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
final notificationData = {
'title': message.notification?.title,
'body': message.notification?.body,
'data': message.data,
'icon': 'mipmap/ic_notification', // or 'drawable/ic_notification'
};
if (await genuin.willHandleForegroundNotifications(notificationData) == true) {
await genuin.handleForegroundNotifications(notificationData);
} else {
// Your own notification handling
}
});- Handle a tap on a notification, both when it opened the app and when the app was in the background:
Future<void> openFromNotification(RemoteMessage message) async {
final notificationData = {
'title': message.notification?.title,
'body': message.notification?.body,
'data': message.data,
};
if (await genuin.willHandleBackgroundNotifications(notificationData) == true) {
await genuin.handleBackgroundNotifications(notificationData);
}
}
final initial = await FirebaseMessaging.instance.getInitialMessage();
if (initial != null) await openFromNotification(initial);
FirebaseMessaging.onMessageOpenedApp.listen(openFromNotification);- On iOS, make
AppDelegatethe notification center delegate so foreground notifications show as banners. Add this todidFinishLaunchingWithOptionsbeforeGeneratedPluginRegistrant.register, and implement the delegate method:
UNUserNotificationCenter.current().delegate = selfoverride func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
) {
completionHandler([.banner, .list, .badge, .sound])
}Notification map keys
| Key | Type | Android | iOS | Description |
|---|---|---|---|---|
| data | Map<String, String> | Required | Required | message.data. The SDK decides from this payload whether the notification is its own. |
| body | String? | Required by the foreground methods | Ignored | message.notification?.body, shown as the notification text. |
| title | String? | Optional | Ignored | message.notification?.title. |
| icon | String | Required by handleForegroundNotifications | Ignored | The small notification icon as folder/name, for example mipmap/ic_notification. Put the image in android/app/src/main/res/ under that folder. If the resource isn't found, the plugin falls back to a system icon. |
On Android, a foreground map without data and body makes willHandleForegroundNotifications return false. A map without icon makes handleForegroundNotifications fail on the native side; the Dart call returns normally and no notification is shown.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| MissingPluginException(No implementation found for method pauseCarousel on channel genuin_sdk) on iOS | The pause and resume methods exist on Android only | Guard the calls with Platform.isAndroid. |
| HomeFeed renders nothing on iOS | The iOS plugin registers no view factory for genuin_feed_view | Call GenuinSdk().loadSdk() on iOS instead. |
| Tapping a tile on iOS opens nothing | The root view controller isn't the navigation controller from getGenuinNavigationController | Wrap the Flutter view controller as shown in Initialize the SDK. |
| RenderAndroidView object was given an infinite size during layout or the iOS equivalent | The widget sits in an unbounded parent, such as a Column without Expanded | Give the widget a bounded height with SizedBox or Expanded. |
| Embeds stay empty on Android with no error | initSDK ran with a null key, or the activity isn't a FlutterFragmentActivity | Pass the key as a non-null string and extend FlutterFragmentActivity. |
| A foreground notification never appears on Android | The map is missing body or icon | Pass data, body, and icon as in the notification map keys. |
| GenuinSdk calls made from the Firebase background handler have no effect | The background handler runs in a separate isolate where the plugin has no activity | Call the plugin from the main isolate, for example in onMessageOpenedApp or after getInitialMessage. |
| Pause or resume affects the wrong embed | Two widgets share the same embedId and uniqueId | Give each instance its own uniqueId, and pass the same pair in the map. |
Limitations
- No events or callbacks: The plugin exposes no streams. The analytics, player, and interaction callbacks of the native SDKs aren't available in Dart.
- Production only: Neither initializer takes an environment. There's no way to point the plugin at another environment.
- Playback control is Android-only:
pause*andresume*throwMissingPluginExceptionon iOS. The iOS views manage their own playback. HomeFeedis Android-only: On iOS, useloadSdk(), which presents a full-screen modal rather than an inline view.- One parameter set: There are no layout setters (tile spacing, insets, corner radius), no user or contextual parameters, and no placements. Those exist in the native SDKs 2.1.5 and aren't wrapped.
- Static parameters: Widget parameters are read once at creation. Recreate the widget with a new
keyto change them. - One
HomeFeedat a time: The plugin keeps a reference to the lastHomeFeedcreated;pauseHomeFeedandresumeHomeFeedact on that one. - Portrait phones: The native screens lock to portrait and aren't laid out for tablets.

