# 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](https://pub.dev/packages/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](https://begenuin.com/developers/developer-resources/sdk/how-do-you-integrate-the-genuin-flutter-sdk-into-your-application): 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.

```kotlin title="android/app/src/main/kotlin/{{YOUR_PACKAGE}}/MainActivity.kt"
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.

```swift title="ios/Runner/AppDelegate.swift"
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](https://begenuin.com/developers/browse-by-products/onsite/how-do-brands-generate-embed-codes-from-brand-control-center) for how to create one and copy its ID.

### Show a carousel

```dart title="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.

```dart title="Feed and standard wall"
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.

```dart title="Home feed"
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

```dart title="GenuinSdk methods"
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.

```dart title="Pause and resume"
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](https://begenuin.com/developers/developer-resources/sdk/user-authentication-mechanisms-in-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.

```dart title="Sign in"
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](https://begenuin.com/developers/developer-resources/sdk/how-do-you-integrate-the-genuin-flutter-sdk-into-your-application); 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:

```kotlin title="MainActivity.kt"
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:

```swift title="AppDelegate.swift"
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](https://begenuin.com/developers/get-started/developers/genuin-sdk/creation-n-editing-tools/how-do-push-notifications-work-with-the-genuin-sdk).

1. Add Firebase to the app with the [FlutterFire setup](https://firebase.google.com/docs/flutter/setup), including `firebase_core` and `firebase_messaging`.
2. Register the token after the viewer grants notification permission:

```dart title="Register the token"
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);
  }
}
```

3. Handle messages that arrive while the app is in the foreground. On Android the plugin builds the notification itself and needs `body`, `data`, and `icon`:

```dart title="Foreground messages"
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
  }
});
```

4. Handle a tap on a notification, both when it opened the app and when the app was in the background:

```dart title="Notification tap"
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);
```

5. On iOS, make `AppDelegate` the notification center delegate so foreground notifications show as banners. Add this to `didFinishLaunchingWithOptions` before `GeneratedPluginRegistrant.register`, and implement the delegate method:

```swift title="AppDelegate.swift"
UNUserNotificationCenter.current().delegate = self
```

```swift title="AppDelegate.swift"
override 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*` and `resume*` throw `MissingPluginException` on iOS. The iOS views manage their own playback.
- **`HomeFeed`** **is Android-only**: On iOS, use `loadSdk()`, 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 `key` to change them.
- **One** **`HomeFeed`** **at a time**: The plugin keeps a reference to the last `HomeFeed` created; `pauseHomeFeed` and `resumeHomeFeed` act on that one.
- **Portrait phones**: The native screens lock to portrait and aren't laid out for tablets.

## See also

- [Integrate the Genuin SDK with Flutter apps](https://begenuin.com/developers/developer-resources/sdk/how-do-you-integrate-the-genuin-flutter-sdk-into-your-application)
- [Integrate the Genuin Android SDK](https://begenuin.com/developers/developer-resources/sdk/how-to-integrate-and-power-your-brand-community-in-android-apps)
- [Integrate the Genuin iOS SDK](https://begenuin.com/developers/developer-resources/sdk/developer/genuin-sdk/native-compatibility/ios/how-to-integrate-and-power-your-brand-community-in-ios-apps)
- [Generate embed codes from Brand Control Center](https://begenuin.com/developers/browse-by-products/onsite/how-do-brands-generate-embed-codes-from-brand-control-center)
- [Choose how members sign in to the Genuin SDK](https://begenuin.com/developers/developer-resources/sdk/user-authentication-mechanisms-in-genuin-sdk)
- [Set up push notifications in the Genuin SDK](https://begenuin.com/developers/get-started/developers/genuin-sdk/creation-n-editing-tools/how-do-push-notifications-work-with-the-genuin-sdk)
