SDK Setup Guide
Install the SDK
- Android
- iOS
- Flutter
- React Native
Octopus is available on Maven Central.
Add the dependencies to your build.gradle file:
dependencies {
// Core SDK functionnalities
implementation("com.octopuscommunity:octopus-sdk:x.x.x")
// SDK UI Components (optional)
implementation("com.octopuscommunity:octopus-sdk-ui:x.x.x")
}
See the Octopus SDK GitHub Release section to get the latest published version.
Octopus can be installed:
In an Xcode project
Open your workspace (.xcworkspace) or your project (.xcodeproj), open the File menu and open Add Package Dependencies. Then, paste the url of the Octopus Community SDK:
https://github.com/Octopus-Community/octopus-sdk-swift.git
On the next window, add both Octopus and OctopusUI to your target.
OR
In a Swift Package
Add the dependency to your package:
dependencies: [
.package(url: "https://github.com/Octopus-Community/octopus-sdk-swift.git", from: "1.0.0"),
],
Add the SDK as a dependency to your target:
.target(
name: "YourTarget",
dependencies: [
.product(name: "Octopus", package: "octopus-sdk-swift"),
.product(name: "OctopusUI", package: "octopus-sdk-swift"),
]
),
Not recommended: Cocoapods
As CocoaPods is starting to be less and less used, some libraries are not available anymore. This is the case of SwiftGrpc on which the Octopus SDK has built its backend exchanges. Hence, if you use the SDK using Cocoapods, you will be using an old version of SwiftGrpc.
If you really have to use the SDK with Cocoapods, here is how to do it:
- Copy the content of our podfile example in your podfile. Pay attention to the fact that the post_install script is setting iOS 14 as minimum requirement and setting
ENABLE_USER_SCRIPT_SANDBOXINGtoNO. - Then you can run
pod install
See the Octopus SDK GitHub Release section to get the latest published version.
The Octopus Flutter SDK is available on pub.dev.
Add the dependency to your pubspec.yaml:
dependencies:
octopus_sdk_flutter: ^1.9.0
Then run:
flutter pub get
Android setup
In your android/app/build.gradle, make sure you have:
android {
compileSdk 35
defaultConfig {
minSdk 24
}
}
In your AndroidManifest.xml, add the INTERNET permission if not already present:
<uses-permission android:name="android.permission.INTERNET"/>
In your MainActivity.kt, make sure your activity extends FlutterFragmentActivity:
import io.flutter.embedding.android.FlutterFragmentActivity
class MainActivity : FlutterFragmentActivity()
iOS setup
In your ios/Podfile, set the minimum iOS version:
platform :ios, '16.0'
Then run:
cd ios && pod install
If you encounter a gRPC conflict, add the following to your Podfile:
pod 'gRPC-Swift', :modular_headers => true
See the Octopus Flutter SDK GitHub repository to get the latest published version.
Install the package using npm or yarn:
npm install @octopus-community/react-native
iOS setup
Add use_frameworks! :linkage => :static to your ios/Podfile, then run:
cd ios && pod install
Xcode 16+ is required.
Android setup
In your android/app/build.gradle, make sure you have:
android {
compileSdk 35
defaultConfig {
minSdk 24
}
}
Kotlin 2.x is required.
See the Octopus React Native SDK repository for the latest version and full setup instructions.
Use the SDK
As early as possible in your code, you should initialize the OctopusSDK object.
This object is expecting two things:
- the Octopus Community API key
- the connection mode
You need to know the app managed fields (also called as associated fields) that your community is configured for.
As a reminder, every associated profile fields (nickname, picture and/or bio) of your users will be used in the Octopus Community profile of this user.
The user will only be able to change it in your profile edition interface and the data will be synced to its community profile.
On the oposite, every dissociated profile fields will only be used as prefilled values during Octopus profile creation.
After that, if a user changes its nickname in your app, it won't be reflected in Octopus Community, and the user will be able to change its community nickname in the community part.
- Android
- iOS
- Flutter
- React Native
At least one app managed field
If your community is having at least one associated field, you will have to create the SSO connection mode with the list of the associated fields.
Call the OctopusSDK initialization function in your Application's onCreate() block:
class YourApplication : Application() {
override fun onCreate() {
super.onCreate()
OctopusSDK.initialize(
context = this, // Application Context
apiKey = "YOUR_API_KEY",
connectionMode = ConnectionMode.SSO(
// The list of associated fields
appManagedFields = setOf(ProfileField.NICKNAME, ProfileField.PICTURE)
)
)
}
}
OR
No app managed fields
When there is no app managed fields (i.e. all fields are dissociated), the API is simpler since you only have to configure it in SSO connection mode.
Call the OctopusSDK initialization function in your Application's onCreate() block:
class YourApplication : Application() {
override fun onCreate() {
super.onCreate()
OctopusSDK.initialize(
context = this, // Application Context
apiKey = "YOUR_API_KEY",
connectionMode = ConnectionMode.SSO()
)
}
}
At least one app managed field
If your community is having at least one associated field, you will have to create the SSO connection mode with:
- the list of the associated fields
- the block that will be called when OctopusSDK needs a logged in user. When this block is called, you should start to display your login process.
- the block that will be called when the user tries to modify some fields related to its profile. When this block is called, you should open the profile edition. This block has a ProfileField optional parameter. It indicates the field that the user tapped to edit if there is one.
import Octopus
/* (...) */
let octopus = try OctopusSDK(
apiKey: "YOUR_API_KEY",
connectionMode: .sso(
.init(
appManagedFields: [.nickname, .picture], // the list of associated fields
loginRequired: {
// Put the code here to open your login flow
},
modifyUser: { fieldToEdit in
// Put the code here to open your profile edition screen
// `fieldToEdit` is the field that has been asked to be edited by the user. Nil if the user tapped on "Edit my profile".
}
)
)
)
OR
No app managed fields
When there is no app managed fields (i.e. all fields are dissociated), the API is simpler since it only requires a callback to display the login flow. When this block is called, you should start to display your login process.
import Octopus
/* (...) */
let octopus = try OctopusSDK(
apiKey: "YOUR_API_KEY",
connectionMode: .sso(
.init(
loginRequired: {
// Put the code here to open your login flow
}
)
)
)
The init function of the OctopusSDK also lets you provide a custom configuration (default config is used if you don't pass it). Here is what you can configure:
appManagedAudioSession: Bool: If false, the SDK will set the AVAudioSession category to .playback or .ambient when a video is playing to ensure audio plays in silent mode. Default is false. You can set it to true if your app is already managing audio session, to avoid that Octopus changes your config.
Here is how to use the config during SDK initialization:
import Octopus
let octopus = try OctopusSDK(
apiKey: "YOUR_API_KEY",
connectionMode: ..., // see above to chose the connection mode
configuration: OctopusSDK.Configuration(appManagedAudioSession: true)
)
At least one app managed field
If your community is having at least one associated field, you will have to create the SSO connection mode with the list of the associated fields.
Initialize the SDK as early as possible in your app:
import 'package:octopus_sdk_flutter/octopus_sdk_flutter.dart';
final octopus = OctopusSDK();
await octopus.initialize(
apiKey: 'YOUR_API_KEY',
appManagedFields: [ProfileField.nickname, ProfileField.picture],
);
OR
No app managed fields
When there is no app managed fields (i.e. all fields are dissociated), the API is simpler since you only have to configure it in SSO connection mode.
Initialize the SDK as early as possible in your app:
import 'package:octopus_sdk_flutter/octopus_sdk_flutter.dart';
final octopus = OctopusSDK();
await octopus.initialize(
apiKey: 'YOUR_API_KEY',
);
At least one app managed field
If your community is having at least one associated field, you will have to create the SSO connection mode with the list of the associated fields.
Initialize the SDK as early as possible in your app (e.g. in App.tsx before any navigation):
import { initialize } from '@octopus-community/react-native';
await initialize({
apiKey: 'YOUR_API_KEY',
connectionMode: {
type: 'sso',
appManagedFields: ['username', 'profilePicture'],
},
});
OR
No app managed fields
When there is no app managed fields (i.e. all fields are dissociated), the API is simpler since you only have to configure it in SSO connection mode.
Initialize the SDK as early as possible in your app:
import { initialize } from '@octopus-community/react-native';
await initialize({
apiKey: 'YOUR_API_KEY',
connectionMode: {
type: 'sso',
appManagedFields: [],
},
});
Multi-Community Support
If your app manages several communities with different API keys, use switchCommunity() to cleanly transition between them at runtime.
The switchCommunity method switches the active community to the one identified by a new API key. It performs a full internal cleanup — flushing pending analytics events, logging out the current user, clearing cached user data and files, and resetting all internal databases — before reinitializing the SDK internals with the new API key. The SDK object reference is preserved; only its internal state is replaced.
- During the call, you must not call any other Octopus function until
switchCommunityfinishes. - After the call finishes, if you are in SSO mode and have a logged-in user, you must call
connectUseragain to connect the user to the new community. - After the call finishes, you must reconstruct any displayed
OctopusHomeScreen.
- Android
- iOS
- Flutter
Parameters:
context(Context) — Required. The application context.apiKey(String) — Required. The API key that identifies the new community.connectionMode(ConnectionMode) — Optional. The connection mode for the new community. Default:ConnectionMode.SSO().
// Switch to a new community
OctopusSDK.switchCommunity(
context = applicationContext,
apiKey = "NEW_COMMUNITY_API_KEY",
connectionMode = ConnectionMode.SSO()
)
// After switchCommunity returns, reconnect the user (SSO mode)
OctopusSDK.connectUser(
ClientUser(
userId = yourUser.id,
profile = ClientUser.Profile(
nickname = yourUser.name,
bio = yourUser.bio,
picture = yourUser.picture
)
)
) {
// Fetch asynchronously this user token
// by calling your /generateOctopusSsoToken route
getTokenFromServer()
}
switchCommunity() is safe to call even when the SDK is not initialized — it will simply initialize it.
Parameters:
apiKey: String— Required. The API key that identifies the new community.connectionMode: ConnectionMode— Optional. The connection mode for the new community. Default:.octopus(deepLink: nil).configuration: Configuration— Optional. The SDK configuration. Default:.init().
// Switch to a new community
do {
try await octopus.switchCommunity(
apiKey: "NEW_COMMUNITY_API_KEY",
connectionMode: .sso(
.init(
appManagedFields: [.nickname, .picture],
loginRequired: {
// Put the code here to open your login flow
},
modifyUser: { fieldToEdit in
// Put the code here to open your profile edition screen
}
)
)
)
// After switchCommunity returns, reconnect the user (SSO mode)
octopus.connectUser(
ClientUser(
userId: yourUser.id,
profile: ClientUser.Profile(
nickname: yourUser.name,
bio: yourUser.bio,
picture: yourUser.picture
)
),
tokenProvider: {
// Fetch asynchronously this user token
// by calling your /generateOctopusSsoToken route
}
)
// Force-reconstruct any displayed OctopusHomeScreen
// For example, update an @State id to trigger a view re-init:
// octopusHomeScreenId = UUID()
} catch {
// Handle error
}
Check the Scenario "Switch Community" for a complete use case.
Multi-community support is not yet available on Flutter.
Link your user to the SDK
Your application is managing the connection status of the user and inform the Octopus SDK when the user is connected/disconnected
The connectUser API lets you pass the userId. This userId is the string that will identify your user for Octopus.
It needs to be unique and always refer to the same user.
You can also pass profile information (nickname, bio, picture). These fields will be used differently depending of wether the field is associated (i.e. in the app managed fields that you provided during SDK initialization) or not:
-
if the field is associated, each time the connectUser function will be called, Octopus will update the field in the user's community profile with the data you provided. Hence, the field in the community profile of the user will always be the same value as in its app profile
-
if the field is not associated, the data you provided will only be used to fill the community profile until the user edits its community profile. Once its done, their community profile remains separate from yours, meaning any updates made to your profile will not be reflected in theirs, and vice versa.
This is why you should call the connectUser function as soon as the user profile changes.
Client User Token
For security reasons, to ensure that the connected user is legitimate, the API informing the SDK of a user’s connection
includes a callback that provides a signed token for authentication. In other words, the SDK will request a token from
you when needed to authenticate the user.
Therefore, you must add a route like /generateOctopusSsoToken to your backend to generate this token.
Follow the Generate a signed JWT for SSO guide for more information
- Android
- iOS
- Flutter
- React Native
Inform the SDK that your user is connected:
≥ 1.6.0OctopusSDK.connectUser(
user = ClientUser(
userId = yourUserId, // Unique identifier of your user
profile = ClientUser.Profile(
nickname = yourUserNickname, // nickname is String?
bio = yourUserBio, // bio is String?
picture = yourUserPicture // A Remote URL or a Local Uri
)
),
tokenProvider = {
// Fetch asynchronously this user token (suspended callback)
// by calling your /generateOctopusSsoToken route
}
)
Check the Community ViewModel Sample for a complete use case.
Deprecated < 1.6.0
OctopusSDK.connectUser(
user = ClientUser(
userId = yourUser.id,
profile = ClientUser.Profile(
nickname = yourUser.name,
bio = yourUser.bio,
picture = yourUser.picture,
// Age Information:
// - LegalAgeReached = Your user is more than 16 years old
// - Underaged = Your user is less than 16 years old
// - null = You don't know
ageInformation = AgeInformation.LegalAgeReached
)
),
tokenProvider = {
// Return asynchronously this user token (suspended callback)
// by calling your /generateOctopusSsoToken route
}
)
- Inform the SDK that your user is disconnected:
OctopusSDK.disconnectUser()
- Optional: Monitor whether the user is connected to the Octopus platform:
OctopusSDK.isUserConnected
Inform the SDK that your user is connected:
≥ 1.6.0octopus.connectUser(
ClientUser(
userId: yourUser.id,
profile: ClientUser.Profile(
nickname: yourUser.name, // nickname is String?
bio: yourUser.bio, // bio is String?
picture: yourUser.picture // picture is Data?, this Data will be transformed into an UIImage using `UIImage(data:)` so it must be compatible.
)
),
tokenProvider: {
// Fetch asynchronously this user token
// by calling your /generateOctopusSsoToken route
}
)
Deprecated < 1.6.0
octopus.connectUser(
ClientUser(
userId: yourUser.id,
profile: ClientUser.Profile(
nickname: yourUser.name,
bio: yourUser.bio,
picture: yourUser.picture,
ageInformation: .legalAgeReached // if your user is more than 16 years old. Pass .underaged if they are less than 16. Pass nil if you don't know
)
),
tokenProvider: {
// Fetch asynchronously this user token
// by calling your /generateOctopusSsoToken route
}
)
Inform the SDK that your user is disconnected:
octopus.disconnectUser()
Inform the SDK that your user is connected:
await octopus.connectUser(
userId: yourUserId, // Unique identifier of your user
token: yourUserToken, // The signed JWT token
nickname: yourUserNickname, // String? - optional
bio: yourUserBio, // String? - optional
picture: yourUserPicture, // String? - A Remote URL
);
Alternatively, you can use a token provider that will be called when the SDK needs to authenticate the user:
await octopus.connectUserWithTokenProvider(
userId: yourUserId,
nickname: yourUserNickname,
bio: yourUserBio,
picture: yourUserPicture,
tokenProvider: () async {
// Fetch asynchronously this user token
// by calling your /generateOctopusSsoToken route
return token;
},
);
Inform the SDK that your user is disconnected:
await octopus.disconnectUser();
First, set up a token provider. In React components, use the useUserTokenProvider hook:
import { useUserTokenProvider, connectUser, disconnectUser } from '@octopus-community/react-native';
function App() {
useUserTokenProvider(async () => {
// Fetch the user token from your backend
// by calling your /generateOctopusSsoToken route
const response = await fetch('https://your-backend.com/generateOctopusSsoToken');
const { token } = await response.json();
return token;
});
// ...
}
Outside of React components, use addUserTokenRequestListener instead:
import { addUserTokenRequestListener } from '@octopus-community/react-native';
const subscription = addUserTokenRequestListener(async () => {
const response = await fetch('https://your-backend.com/generateOctopusSsoToken');
const { token } = await response.json();
return token;
});
// Later, to unsubscribe:
subscription.remove();
Inform the SDK that your user is connected:
await connectUser({
userId: yourUserId, // Required — unique identifier of your user
profile: {
username: yourUserNickname, // string | undefined
biography: yourUserBio, // string | undefined
profilePicture: yourUserPicture, // string | undefined — a remote URL or local file path
},
});
Breaking change in v1.9: The legalAgeReached field has been removed from the profile object. If your code was passing legalAgeReached, remove it when upgrading to v1.9.
Inform the SDK that your user is disconnected:
await disconnectUser();
Display the Octopus Community UI
Now that you have the SDK properly configured, you can add a button in your app that opens the Octopus Community UI.
- Android
- iOS
- Flutter
- React Native
- Add the
OctopusHomeContentcomposable to your community screen.
OctopusTheme(...) { // Customize the Octopus Theme here
OctopusHomeContent(
modifier = Modifier.fillMaxSize(),
navController = navController, // Your main NavHostController
onNavigateToLogin = {
// This block will be called when OctopusSDK needs a logged-in user.
// You should launch your login process here.
// Example: navController.navigate(LoginRoute)
// Once the user is logged in, you need to call OctopusSDK.connectUser(...) to link
// them with Octopus
},
// Optional: If your community has at least one associated field:
onNavigateToProfileEdit = { profileField ->
// This block will be called when the user tries to modify some fields related
// to their profile.
// When this block is called, you should open your profile edition screen.
// Example: navController.navigate(ProfileScreen(focusNickname = fieldToEdit == ProfileField.NICKNAME))
// Once the user has edited their profile, you should call OctopusSDK.connectUser(...) to update the Octopus profile.
}
)
}
Check the Community Screen Sample for basic usage.
- Declare other Octopus sub-screens in your main
NavHost:
NavHost(...) { // Your main NavHost
octopusComposables(
navController = navController, // Your main NavHostController
onNavigateToLogin = { ... },
onNavigateToProfileEdit = { ... }
) { backStackEntry, content ->
// Customize the Octopus Theme possibly based on a specific Octopus Screen here
OctopusTheme(...) {
content()
}
}
}
This function registers multiple composable() destinations in your NavGraphBuilder for adding the Octopus SDK navigation flow to your app.
Depending on your use case integration mode check the various samples:
First, import Octopus UI:
import OctopusUI
Octopus handles its own navigation, so you must not embed it in a navigation stack.
@State private var openOctopus = false
var body: some View {
Button("Open Octopus Community") {
openOctopus = true
}.fullScreenCover(isPresented: $openOctopus) {
OctopusHomeScreen(octopus: octopus)
}
}
If you're using a UITabBarController to display the Octopus UI inside a UIHostingController, you might encounter a safe area bug. This is a known bug not related to the Octopus SDK UI. To fix it, you might want to add the UITabBarController children in the code instead of in the Storyboard. Otherwise, you can add a negative padding equal to tabBarController.tabBar.frame.size.height to the OctopusUI.
You can display the Octopus Community UI as an embedded widget.
Embedded widget:
OctopusHomeScreen(
onNavigateToLogin: () {
// This callback will be called when OctopusSDK needs a logged-in user.
// You should launch your login process here.
},
onModifyUser: (String? field) {
// This callback will be called when the user tries to modify some fields
// related to their profile.
// `field` indicates the field that the user tapped to edit, if any.
},
)
Fullscreen mode:
Call openUI() to display the Octopus Community in fullscreen. Use closeUI() to dismiss it programmatically:
import { openUI, closeUI } from '@octopus-community/react-native';
// Open the community UI
await openUI();
// Optionally, close it programmatically
await closeUI();
Embedded mode:
Use the OctopusUIView component to embed the community UI inline within a screen:
import { OctopusUIView } from '@octopus-community/react-native';
import { View, StyleSheet } from 'react-native';
function CommunityScreen() {
return (
<View style={{ flex: 1 }}>
<OctopusUIView style={StyleSheet.absoluteFill} />
</View>
);
}
Both modes require initialize() to be called first.
Modify bottom safe area
According to your app, you might want to add a bottom padding to the Octopus UI content.
- Android
- iOS
- Flutter
- React Native
This can be done by using the contentPadding parameter of the OctopusHomeContent composable.
OctopusHomeContent(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(bottom = 10.dp) // This will add a 10dp padding at the bottom of the Octopus UI
// ...
)
To see a full example of how you can achieve that, you can check the Floating Bottom Navigation Bar Sample.
This can be done by using the bottomSafeAreaInset parameter of the OctopusHomeScreen.
OctopusHomeScreen(
octopus: octopus,
bottomSafeAreaInset: 10 // this will add a 10px safe area at the bottom of the Octopus UI
)
To see a full example of how you can achieve that, you can follow how it is done in the Samples, in the Embedded Tab.
Bottom safe area modification is not yet available on Flutter.
Pass the ui option inside the initialize() call:
import { initialize } from '@octopus-community/react-native';
await initialize({
apiKey: 'YOUR_API_KEY',
connectionMode: { type: 'sso', appManagedFields: [] },
ui: {
bottomSafeAreaInset: 10, // In points (iOS) or dp (Android)
},
});
Modify the theme
The Octopus SDK lets you modify its theme so its UI looks more like yours.
You can modify:
-
the colors:
Please pass colors without any transparency.
- the primary colors (a main color, a low contrast and a high contrast variations of the main color). If you do not pass a custom value for it, black/white default values will be used.
- the color of the elements (mostly texts) displayed over the primary color. If you do not pass a custom value for it, white/black default value will be used.
-
the fonts. You can customize the styles (title1, body2, caption1...) used in the sdk. If you do not pass a custom value for it, default value will be used.
-
the logo. This is an image displayed on the Octopus home page and profile creation view. If you do not pass a custom image for it, Octopus logo value will be used.
-
the icons. You can override any icon (or group of icons) used across all SDK screens to match your app's design language. If you do not pass a custom value for it, the SDK's built-in icons will be used.
-
(⚠ Android only) the TopAppBar. You can customize the appearance, title, navigation icons, actions, and colors of the TopAppBar. If you do not pass a custom configuration for it, default TopAppBar will be used.
- Android
- iOS
- Flutter
- React Native
By default, Octopus will rely on your application's MaterialTheme.colorScheme, but you can customize the UI more precisely by surrounding composables with the OctopusTheme:
To do that, you can override the theme by passing it as an environment object:
octopusComposables(
navController = navController
) { backStackEntry, content ->
OctopusTheme(
colorScheme = if (isSystemInDarkTheme()) {
octopusDarkColorScheme(
primary = yourDarkPrimaryColor, // Default: MaterialTheme.colorScheme.primary
primaryLow = lowContrastVersionOfYourPrimaryColor, // Default: MaterialTheme.colorScheme.primaryContainer
primaryHigh = highContrastVersionOfYourPrimaryColor, // Default: MaterialTheme.colorScheme.inversePrimary
onPrimary = yourDarkOnPrimaryColor, // Default: MaterialTheme.colorScheme.onPrimary
background = yourDarkBackgroundColor // Default: MaterialTheme.colorScheme.background
// See the complete list in the sources documentation
)
} else {
octopusLightColorScheme(
primary = yourLightPrimaryColor, // Default: MaterialTheme.colorScheme.primary
primaryLow = lowContrastVersionOfYourPrimaryColor, // Default: MaterialTheme.colorScheme.primaryContainer
primaryHigh = highContrastVersionOfYourPrimaryColor, // Default: MaterialTheme.colorScheme.inversePrimary
onPrimary = yourLightOnPrimaryColor, // Default: MaterialTheme.colorScheme.onPrimary
background = yourLightBackgroundColor // Default: MaterialTheme.colorScheme.background
// See the complete list in the sources documentation
)
},
typography = OctopusTypographyDefaults.typography(
title1 = yourCustomTitle1Style, // Default: TextStyle(fontSize = 26.sp)
title2 = yourCustomTitle2Style // Default: TextStyle(fontSize = 22.sp)
// See the complete list in the sources documentation
),
images = OctopusImagesDefaults.images(
logo = painterResource(R.drawable.your_custom_logo), // Default: null
icons = OctopusIconsDefaults.icons(
// Override any icon or icon group here
)
)
// ... Check the complete parameters list in the sources documentation
) {
content()
}
}
Use the OctopusThemeGenerator to configure your Community theme with a live preview of the various Octopus screens.
All parameters of the theme have default values. Only override the ones that you want to customize. In the following example, you are creating an OctopusTheme with default colors, default fonts except for the title1, and a custom logo:
OctopusTheme(
typography = OctopusTypographyDefaults.typography(
title1 = TextStyle(fontFamily = FontFamily.SansSerif, fontSize = 24.sp)
),
images = OctopusImagesDefaults.images(
logo = painterResource(R.drawable.your_custom_logo)
)
) {
OctopusHomeContent(...)
}
To do that, you can override the theme by passing it as an environment object:
OctopusHomeScreen(octopus: octopus)
.environment(
\.octopusTheme,
OctopusTheme(
colors: .init(
primarySet: OctopusTheme.Colors.ColorSet(
main: yourPrimaryColor,
lowContrast: lowContrastVersionOfYourPrimaryColor,
highContrast: highContrastVersionOfYourPrimaryColor
),
onPrimary: contentOverPrimaryColor
),
fonts: .init(
title1: Font.custom("Courier New", size: 26),
title2: Font.custom("Courier New", size: 20),
body1: Font.custom("Courier New", size: 17),
body2: Font.custom("Courier New", size: 14),
caption1: Font.custom("Courier New", size: 12),
caption2: Font.custom("Courier New", size: 10),
navBarItem: Font.custom("Courier New", size: 17)
),
assets: .init(
logo: yourLogoAsUIImage,
icons: .init(
common: .init(close: UIImage(named: "close"))
)
)
)
)
All parameters of the theme have default values. Only override the ones that you want to customize. In the following example, you are creating an OctopusTheme with default colors, default fonts except for the title1, default icons and a custom logo:
let octopusTheme = OctopusTheme(
fonts: .init(
title1: Font.custom("Courier New", size: 26)
),
assets: .init(logo: yourLogoAsUIImage)
)
If your app supports only light or dark mode, add UIUserInterfaceStyle to your Info.plist with the value Light or Dark to force the SDK to use the mode you specified.
You can customize the Octopus theme by passing an OctopusTheme to the OctopusHomeScreen widget or showOctopusHomeScreen():
OctopusHomeScreen(
theme: OctopusTheme(
colors: OctopusColors(
primary: Color(0xFF6200EE),
primaryLow: Color(0xFFBB86FC),
primaryHigh: Color(0xFF3700B3),
onPrimary: Colors.white,
),
fontSizes: OctopusFontSizes(
title1: 26,
title2: 22,
body1: 17,
body2: 14,
caption1: 12,
caption2: 10,
),
logo: yourBase64EncodedLogo, // String? - base64 encoded image
themeMode: ThemeMode.system, // ThemeMode.light, ThemeMode.dark, or ThemeMode.system
),
)
All parameters of the theme have default values. Only override the ones that you want to customize. In the following example, you are creating an OctopusTheme with default colors, default font sizes except for the title1, and a custom logo:
OctopusTheme(
fontSizes: OctopusFontSizes(
title1: 24,
),
logo: yourBase64EncodedLogo,
)
Use the themeMode parameter to control light/dark mode. Set it to ThemeMode.light or ThemeMode.dark to force a specific mode, or ThemeMode.system (default) to follow the system setting.
Pass a theme object inside the initialize() call. The theme supports colors, fonts, and a logo.
You can pass a single color set (applied to both light and dark modes) or separate sets for each mode:
import { initialize } from '@octopus-community/react-native';
import { Image } from 'react-native';
await initialize({
apiKey: 'YOUR_API_KEY',
connectionMode: { type: 'sso', appManagedFields: [] },
theme: {
colors: {
light: {
primary: '#FF6B35',
primaryLowContrast: '#FFB899',
primaryHighContrast: '#CC4400',
onPrimary: '#FFFFFF',
},
dark: {
primary: '#FF8F5E',
primaryLowContrast: '#CC7244',
primaryHighContrast: '#FFB899',
onPrimary: '#1A1A1A',
},
},
fonts: {
textStyles: {
title1: { fontType: 'serif', fontSize: { size: 26 } },
title2: { fontType: 'default', fontSize: { size: 22 } },
body1: { fontSize: { size: 17 } },
body2: { fontSize: { size: 14 } },
caption1: { fontSize: { size: 12 } },
caption2: { fontSize: { size: 10 } },
},
},
logo: {
image: Image.resolveAssetSource(require('./assets/logo.png')),
},
},
});
The React Native SDK automatically follows the device's system appearance (light or dark mode). There is no SDK-level colorScheme override — to control appearance, provide separate light and dark color sets as shown above, and the SDK will select the appropriate set based on the system setting.
All parameters of the theme have default values. Only override the ones that you want to customize. In the following example, you are creating an OctopusTheme with default colors, default fonts except for the title1, and a custom logo:
await initialize({
apiKey: 'YOUR_API_KEY',
connectionMode: { type: 'sso', appManagedFields: [] },
theme: {
fonts: {
textStyles: {
title1: { fontType: 'serif', fontSize: { size: 24 } },
},
},
logo: {
image: Image.resolveAssetSource(require('./assets/logo.png')),
},
},
});
Here is a summary of the impacts of the theme you choose:

Here is a summary of the text styles used in the main screens of the SDK:

Customize Icons ≥ 1.10.0
All icons used inside the Octopus Community UI can be replaced with your own assets. Icons will be automatically tinted by the SDK — the colors of your assets will be ignored. Icons should ideally be 24×24 pixels with drawn content of 14.5×14.5 (i.e. 4.75 px transparent border on each side) and square. If your icon is not square, it will be displayed in fit mode and may appear smaller than expected.
If you do not override an icon, the SDK default will be used.
Icons are organized into groups matching different areas of the UI: groups, content (posts, comments, replies, video, polls), profile, gamification, settings, and common (radio buttons, checkboxes, toggles, close, more actions).
As the icons are linked to the UI and the UI can change quite often, this API might change often, some icons won't be used anymore and will be quickly deprecated.
- Android
- iOS
- Flutter
You can override any icon used in the SDK UI by providing a custom OctopusIcons instance through OctopusImagesDefaults.images(). All icon parameters are optional — only specify the ones you want to change. The SDK organizes icons into groups: groups, content (posts, comments, replies, video, polls), profile, notifications, gamification, and settings. There is also a top-level moreActions icon for the overflow menu button, customizable via OctopusIconsDefaults.icons(moreActions = ...).
Inside any @Composable function, you can access the current icons via OctopusTheme.icons.
Override a single shared icon
Some icons are shared across multiple screens. For example, passing a custom close icon to OctopusIconsDefaults.icons() will propagate it as the default deletePicture icon in all creation forms (post, comment, and reply):
import com.octopuscommunity.sdk.ui.OctopusIconsDefaults
import com.octopuscommunity.sdk.ui.OctopusImagesDefaults
import com.octopuscommunity.sdk.ui.OctopusTheme
OctopusTheme(
images = OctopusImagesDefaults.images(
icons = OctopusIconsDefaults.icons(
close = painterResource(R.drawable.your_custom_close_icon)
)
)
) {
// All creation forms (post, comment, reply) will now use your custom close icon
// for the "delete picture" action
OctopusHomeContent(...)
}
Similarly, the report icon propagates to both content and profile screens:
import com.octopuscommunity.sdk.ui.OctopusIconsDefaults
import com.octopuscommunity.sdk.ui.OctopusImagesDefaults
import com.octopuscommunity.sdk.ui.OctopusTheme
OctopusTheme(
images = OctopusImagesDefaults.images(
icons = OctopusIconsDefaults.icons(
report = painterResource(R.drawable.your_custom_report_icon)
)
)
) {
OctopusHomeContent(...)
}
Override icons within a specific group
You can override icons within a specific area of the SDK. For example, to customize some post creation icons (unspecified parameters keep SDK defaults):
import com.octopuscommunity.sdk.ui.OctopusIconsDefaults
import com.octopuscommunity.sdk.ui.OctopusImagesDefaults
import com.octopuscommunity.sdk.ui.OctopusTheme
OctopusTheme(
images = OctopusImagesDefaults.images(
icons = OctopusIconsDefaults.icons(
content = OctopusIconsDefaults.content(
post = OctopusIconsDefaults.post(
creation = OctopusIconsDefaults.postCreation(
create = painterResource(R.drawable.your_create_post_icon),
addPicture = painterResource(R.drawable.your_add_picture_icon),
addPoll = painterResource(R.drawable.your_add_poll_icon)
)
)
)
)
)
) {
OctopusHomeContent(...)
}
Customize radio, checkbox, and toggle icons
By default, the SDK uses native Material3 RadioButton, Checkbox, and Switch widgets. You can replace them with custom icon pairs by providing an OctopusIcons.OnOff instance:
import com.octopuscommunity.sdk.ui.OctopusIcons
import com.octopuscommunity.sdk.ui.OctopusIconsDefaults
import com.octopuscommunity.sdk.ui.OctopusImagesDefaults
import com.octopuscommunity.sdk.ui.OctopusTheme
OctopusTheme(
images = OctopusImagesDefaults.images(
icons = OctopusIconsDefaults.icons(
radio = OctopusIcons.OnOff(
on = painterResource(R.drawable.your_radio_selected),
off = painterResource(R.drawable.your_radio_unselected)
),
checkbox = OctopusIcons.OnOff(
on = painterResource(R.drawable.your_checkbox_checked),
off = painterResource(R.drawable.your_checkbox_unchecked)
),
toggle = OctopusIcons.OnOff(
on = painterResource(R.drawable.your_toggle_on),
off = painterResource(R.drawable.your_toggle_off)
)
)
)
) {
OctopusHomeContent(...)
}
When radio, checkbox, or toggle is null (the default), the native Material3 component is used. Pass an OctopusIcons.OnOff instance to replace it with your custom painters.
Full icon customization example
You can combine all the above into a single OctopusTheme setup:
import com.octopuscommunity.sdk.ui.OctopusIcons
import com.octopuscommunity.sdk.ui.OctopusIconsDefaults
import com.octopuscommunity.sdk.ui.OctopusImagesDefaults
import com.octopuscommunity.sdk.ui.OctopusTheme
OctopusTheme(
images = OctopusImagesDefaults.images(
logo = painterResource(R.drawable.your_custom_logo),
icons = OctopusIconsDefaults.icons(
close = painterResource(R.drawable.your_close_icon),
moreActions = painterResource(R.drawable.your_more_actions_icon),
report = painterResource(R.drawable.your_report_icon),
content = OctopusIconsDefaults.content(
addPicture = painterResource(R.drawable.your_add_picture_icon)
),
profile = OctopusIconsDefaults.profile(
defaultAvatar = painterResource(R.drawable.your_default_avatar)
),
radio = OctopusIcons.OnOff(
on = painterResource(R.drawable.your_radio_on),
off = painterResource(R.drawable.your_radio_off)
),
checkbox = OctopusIcons.OnOff(
on = painterResource(R.drawable.your_checkbox_on),
off = painterResource(R.drawable.your_checkbox_off)
),
toggle = OctopusIcons.OnOff(
on = painterResource(R.drawable.your_toggle_on),
off = painterResource(R.drawable.your_toggle_off)
)
)
)
) {
OctopusHomeContent(...)
}
Icons are organized in a hierarchy under OctopusTheme.Assets.Icons. The structure mirrors the UI areas: groups, content (posts, comments, replies, video, polls), profile, gamification, settings, and common (radio buttons, checkboxes, toggles, close, more actions).
All icon parameters are optional UIImage? — only specify the ones you want to change. Pass the custom theme to the SDK using the .environment modifier:
OctopusUIView(octopus: octopus)
.environment(\.octopusTheme, myCustomTheme)
Override icons within a specific group
You can customize only the icons you need. Unspecified parameters keep SDK defaults. For example, to customize some post creation icons:
import OctopusUI
let theme = OctopusTheme(
assets: .init(
icons: .init(
content: .init(
post: .init(
creation: .init(
open: UIImage(named: "myCreatePostIcon"),
addPoll: UIImage(named: "myAddPollIcon")
),
commentCount: UIImage(named: "myCommentCountIcon")
),
delete: UIImage(named: "myDeleteIcon")
)
)
)
)
Convenience parameters (shared icons)
Some icons share the same meaning across different parts of the UI (for example, the "report" icon appears in both content and profile contexts). To make it easy to replace all related icons at once, the Icons and Content initializers provide convenience parameters:
Icons.initprovidesdefaultReport: when set, it applies to bothcontent.reportandprofile.report— unless you override those individually.Content.initprovides:defaultNotAvailable→ applies topost.notAvailableandcomment.notAvailabledefaultLikeNotSelected→ applies tocomment.likeNotSelectedandreply.likeNotSelecteddefaultAddPicture→ applies topost.creation.addPicture,comment.creation.addPicture, andreply.creation.addPicturedefaultDeletePicture→ applies topost.creation.deletePicture,comment.creation.deletePicture, andreply.creation.deletePicturedefaultCreateResponse→ applies tocomment.creation.createandreply.creation.createdefaultOpenResponseCreation→ applies tocomment.creation.openandreply.creation.open
Individual icon overrides always take priority over convenience parameters.
Example — replace the report icon everywhere with a single line:
import OctopusUI
let theme = OctopusTheme(
assets: .init(
icons: .init(
defaultReport: UIImage(named: "myReportIcon")
)
)
)
Example — set a default "add picture" icon for all creation forms, but override it specifically for post creation:
import OctopusUI
let theme = OctopusTheme(
assets: .init(
icons: .init(
content: .init(
post: .init(
creation: .init(
addPicture: UIImage(named: "myPostAddPictureIcon")
)
),
// Applies to comment and reply creation (but not post, since it's overridden above)
defaultAddPicture: UIImage(named: "myGenericAddPictureIcon")
)
)
)
)
Customize radio, checkbox, and toggle icons
The common group contains OnOff components for radio buttons, checkboxes, and toggles. Unlike other icons, OnOff requires two non-optional UIImage parameters (on and off):
import OctopusUI
let theme = OctopusTheme(
assets: .init(
icons: .init(
common: .init(
radio: .init(
on: UIImage(named: "myRadioOnIcon")!,
off: UIImage(named: "myRadioOffIcon")!
),
checkbox: .init(
on: UIImage(named: "myCheckboxOnIcon")!,
off: UIImage(named: "myCheckboxOffIcon")!
),
toggle: .init(
on: UIImage(named: "myToggleOnIcon")!,
off: UIImage(named: "myToggleOffIcon")!
)
)
)
)
)
Full icon customization example
You can customize icons across all groups in a single OctopusTheme:
import OctopusUI
let icons = OctopusTheme.Assets.Icons(
groups: .init(
openList: UIImage(named: "myGroupListIcon"),
selected: UIImage(named: "myGroupSelectedIcon")
),
content: .init(
post: .init(
creation: .init(
open: UIImage(named: "myCreatePostIcon"),
topicSelection: UIImage(named: "myTopicArrowIcon"),
addPicture: UIImage(named: "myPostAddPictureIcon"),
deletePicture: UIImage(named: "myPostDeletePictureIcon"),
addPoll: UIImage(named: "myAddPollIcon"),
addPollOption: UIImage(named: "myAddPollOptionIcon"),
deletePoll: UIImage(named: "myDeletePollIcon"),
deletePollOption: UIImage(named: "myDeletePollOptionIcon")
),
emptyFeedInGroups: UIImage(named: "myEmptyGroupFeedIcon"),
emptyFeedInCurrentUserProfile: UIImage(named: "myEmptyCurrentUserFeedIcon"),
emptyFeedInOtherUserProfile: UIImage(named: "myEmptyOtherUserFeedIcon"),
notAvailable: UIImage(named: "myPostNotAvailableIcon"),
commentCount: UIImage(named: "myCommentCountIcon"),
viewCount: UIImage(named: "myViewCountIcon"),
moreReactions: UIImage(named: "myMoreReactionsIcon")
),
comment: .init(
creation: .init(
open: UIImage(named: "myOpenCommentCreationIcon"),
create: UIImage(named: "mySendCommentIcon"),
addPicture: UIImage(named: "myCommentAddPictureIcon"),
deletePicture: UIImage(named: "myCommentDeletePictureIcon")
),
emptyFeed: UIImage(named: "myNoCommentsIcon"),
notAvailable: UIImage(named: "myCommentNotAvailableIcon"),
seeReply: UIImage(named: "mySeeReplyIcon"),
likeNotSelected: UIImage(named: "myCommentLikeIcon")
),
reply: .init(
creation: .init(
open: UIImage(named: "myOpenReplyCreationIcon"),
create: UIImage(named: "mySendReplyIcon"),
addPicture: UIImage(named: "myReplyAddPictureIcon"),
deletePicture: UIImage(named: "myReplyDeletePictureIcon")
),
likeNotSelected: UIImage(named: "myReplyLikeIcon")
),
video: .init(
muted: UIImage(named: "myMutedIcon"),
notMuted: UIImage(named: "myNotMutedIcon"),
pause: UIImage(named: "myPauseIcon"),
play: UIImage(named: "myPlayIcon"),
replay: UIImage(named: "myReplayIcon")
),
poll: .init(
selectedOption: UIImage(named: "myCheckIcon")
),
delete: UIImage(named: "myDeleteIcon"),
report: UIImage(named: "myContentReportIcon")
),
profile: .init(
addPicture: UIImage(named: "myAddAvatarIcon"),
editPicture: UIImage(named: "myEditAvatarIcon"),
addBio: UIImage(named: "myAddBioIcon"),
emptyNotifications: UIImage(named: "myNoNotificationsIcon"),
report: UIImage(named: "myProfileReportIcon"),
notConnected: UIImage(named: "myNotConnectedIcon"),
blockUser: UIImage(named: "myBlockUserIcon")
),
gamification: .init(
badge: UIImage(named: "myBadgeIcon"),
info: UIImage(named: "myGamificationInfoIcon"),
rulesHeader: UIImage(named: "myRulesHeaderIcon")
),
settings: .init(
account: UIImage(named: "myAccountIcon"),
help: UIImage(named: "myHelpIcon"),
info: UIImage(named: "myInfoIcon"),
logout: UIImage(named: "myLogoutIcon"),
deleteAccountWarning: UIImage(named: "myDeleteAccountIcon")
),
common: .init(
radio: .init(
on: UIImage(named: "myRadioOnIcon")!,
off: UIImage(named: "myRadioOffIcon")!
),
checkbox: .init(
on: UIImage(named: "myCheckboxOnIcon")!,
off: UIImage(named: "myCheckboxOffIcon")!
),
toggle: .init(
on: UIImage(named: "myToggleOnIcon")!,
off: UIImage(named: "myToggleOffIcon")!
),
close: UIImage(named: "myCloseIcon"),
moreActions: UIImage(named: "myMoreActionsIcon"),
listCellNavIndicator: UIImage(named: "myListCellNavIndicatorIcon")
)
)
let theme = OctopusTheme(
assets: .init(icons: icons)
)
In this full example, report is set individually on content and profile. You could instead use defaultReport at the Icons level to apply the same report icon to both (see Convenience parameters above).
The account, logout, and deleteAccountWarning icons inside settings are only displayed when using Octopus authentication mode. They are not shown in SSO mode.
Icon customization is not yet available on Flutter.
Customize the TopAppBar
- Android
- iOS
- Flutter
- React Native
You can customize the title displayed in the navigation bar on the main feed screen directly from the OctopusHomeScreen composable. It accepts three parameters for this:
titleText: an optionalStringthat defines the text shown in the nav bar. Whennull(default), no text title is displayed. We highly recommend keeping the text under 18 characters for best results.titleCentered: iftrue, the title is centered in the nav bar. Iffalse(default), the title is left-aligned (leading).logo: an optional@Composable () -> Painterthat provides a custom logo to display in the nav bar. Whennull(default), the logo from yourOctopusThemeis used.
Here is how to display a leading text title:
OctopusHomeScreen(
navController = navController,
titleText = "App Name",
titleCentered = false
)
And here is how to display a centered logo:
OctopusHomeScreen(
navController = navController,
titleCentered = true,
logo = { painterResource(R.drawable.your_logo) }
)
For more advanced customization of the TopAppBar (colors, navigation icons, text styles), you can provide your own OctopusTopAppBar configuration at the theme level. See the Advanced Screen-Based Theming section below.
You can customize the title displayed in the navigation bar on the main feed screen using OctopusMainFeedTitle. This struct lets you control both the content and the placement of the title.
The OctopusHomeScreen constructor accepts two parameters for this:
mainFeedNavBarTitle: an optionalOctopusMainFeedTitlethat defines what is shown in the nav bar. Whennil(default), nothing is displayed. It has two properties:content— either.logoto display the logo from yourOctopusTheme(see Modify the theme), or.text(TextTitle)to display a text. We highly recommend keeping the text under 18 characters for best results. If set to.logoand no custom logo is set in yourOctopusTheme, the title slot is empty regardless of placement.placement— either.leading(left-aligned) or.center(centered).
mainFeedColoredNavBar: iftrue, the navigation bar background uses the primary theme color (see Modify the theme). Requires iOS 16+; on earlier versions this parameter is ignored. Default isfalse.
Here is how to display a leading text title with a colored nav bar:
OctopusHomeScreen(
octopus: octopus,
mainFeedNavBarTitle: .init(
content: .text(.init(text: "App Name")),
placement: .leading
),
mainFeedColoredNavBar: true
)
And here is how to display a centered logo:
OctopusHomeScreen(
octopus: octopus,
mainFeedNavBarTitle: .init(
content: .logo,
placement: .center
)
)
The navBarLeadingItem and navBarPrimaryColor parameters are deprecated. Use mainFeedNavBarTitle and mainFeedColoredNavBar instead. Existing code using the old parameters will continue to compile but will produce a deprecation warning.
To see a full example of how you can achieve that, you can follow how it is done in the Samples, in the Scenario "Custom theme".
You can customize the navigation bar of the Octopus UI by passing parameters to the OctopusHomeScreen widget:
OctopusHomeScreen(
navBarTitle: "My Community", // Short text title (less than 18 characters)
navBarPrimaryColor: true, // Use primary color as background
showBackButton: true, // Show the back button
onBack: () { ... },
)
TopAppBar customization is not available on React Native.
Advanced Screen-Based Theming (Android only)
- Android
- iOS
- Flutter
- React Native
For more complex theming scenarios, you can customize any aspect of the Octopus theme (colors, typography, images, TopAppBar) based on the current screen:
octopusComposables(
navController = navController
) { backStackEntry, content ->
OctopusTheme(
colorScheme = when {
// Post Details with branded theme
backStackEntry.destination.hasRoute<OctopusDestination.PostDetails>() -> {
octopusColorScheme().copy(
background = if (isSystemInDarkTheme()) Color.Black else Color.White
)
}
else -> octopusColorScheme()
},
topAppBar = when {
// Home screen with custom TopAppBar theme
backStackEntry.destination.hasRoute<OctopusDestination.Home>() -> {
OctopusTopAppBarDefaults.topAppBar(
title = { text ->
OctopusTopAppBarTitle(text = "My Community")
}
)
}
else -> OctopusTopAppBarDefaults.topAppBar()
}
) {
content()
}
}
Screen-based theming is not available on iOS.
Screen-based theming is not available on Flutter.
Screen-based theming is not available on React Native.
Push Notifications ≥ 1.4.0
To increase user engagement, you can provide informations to the Octopus SDK so your users can receive push notifications when other users interact with them inside the community.
Octopus SDK is not asking for push notification permissions, we let you handle that part where it makes more sense in your app.
- Android
- iOS
- Flutter
- React Native
If your app does not support Push Notifications yet, you can follow the official Firebase documentation.
Our servers need your service account's private key file to be authorized to send notifications to your app on your behalf.
If you don't have this JSON file yet, follow this tutorial
- In the Firebase console, open Settings > Service Accounts
- Click Generate New Private Key, then confirm by clicking Generate Key.
- Securely store the JSON file containing the key.
More information can be found in the official Firebase documentation
Please send the json file using the online form sent by the Octopus team.
This JSON file is different from the one you are using to configure Firebase in your app (google-services.json)
Once your project is correctly set up for push notifications, you should forward the Firebase Cloud Messaging Token (FCM Token) to the Octopus SDK:
class MessagingService : FirebaseMessagingService() {
override fun onNewToken(token: String) {
super.onNewToken(token)
// Register the new token with Octopus
OctopusSDK.registerNotificationsToken(token)
}
}
You are in charge of requesting the Notification permission and referencing your messaging service:
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
if (checkSelfPermission(POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
// Request launcher for notification permission
registerForActivityResult(
contract = ActivityResultContracts.RequestPermission(),
callback = {
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
if (task.isSuccessful) {
OctopusSDK.registerNotificationsToken(task.result)
}
}
}
).launch(POST_NOTIFICATIONS)
}
<service
android:name=".notifications.MessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
When you receive a notification message, first check if it is an Octopus notification by calling:
override fun onMessageReceived(remoteMessage: RemoteMessage) {
super.onMessageReceived(remoteMessage)
val isOctopusNotification = remoteMessage.data.isOctopusNotification
if (isOctopusNotification) {
// ... Handle the Octopus notification here (see below)
}
}
If it's the case, display the Octopus notification using the Notification Manager:
val octopusNotification = OctopusSDK.getOctopusNotification(data = remoteMessage.data)
if (octopusNotification != null) {
notificationManager.notify(
octopusNotification.id,
Notification.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_stat_notification)
.setColor(getColor(R.color.accent))
.setAutoCancel(true)
// Extension function to extract the title, text and deep link from the OctopusNotification
.setOctopusContent(
context = this,
activityClass = MainActivity::class,
octopusNotification = octopusNotification
).build()
)
}
Then you can display the notification's targeted Octopus UI from the octopusNotification.deepLink:
intent.data?.let { uri -> navController.navigate(deepLink = uri)}
- The
activityClassmust be the one containing the compose content with theoctopusNavigation()subgraph. - The
setOctopusContentfunction is based on a deep link mechanism that will automatically launch the activity and navigate to the corresponding screen within the Octopus navigation graph. - You can also handle the content manually by using the
OctopusNotification'stitle,body, andlinkPathfields and configuring your ownPendingIntent.
If your app does not support Push Notifications yet, you can follow the official Apple documentation.
Our servers need to have a key in the .p8 format in order to send push notifications to your app on your behalf.
If you don't have this key yet, follow this tutorial
- Go to the Apple Dev Website and create a new key.
- Name it and check
Apple Push Notifications service (APNs). - Click on configure for this line and select
Sandbox & Productionin theEnvironmentmenu if you intend to have the same key for sandbox and production builds. - Download the key and be sure to store it in a secure and retrievable place.
Once you have that file, you should send it using the secure form provided by the Octopus Team, along with:
- the key ID: can be found in the list of keys on the Apple Dev Website
- your Bundle ID: can be found in your Xcode project
- your Team ID: can be found here in the
Membership detailscard
Once your project is correctly set up for push notifications, you should forward the notification device token to the Octopus SDK:
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
octopus.set(notificationDeviceToken: token)
}
You are in charge of registering for remote notifications by calling:
UIApplication.shared.registerForRemoteNotifications()
and for getting the user's authorization by calling:
notifCenter.requestAuthorization(options: [.alert, .badge, .sound])
When you receive a notification response (i.e., when the user tapped a notification), first check if it is an Octopus notification by calling:
OctopusSDK.isAnOctopusNotification(notification: notificationResponse.notification)
If it is one, display the Octopus UI and pass it the notification response as a Binding:
OctopusHomeScreen(octopus: octopus, notificationResponse: $octopusNotification)
Push notifications are not yet available on Flutter. You can handle push notifications at the native level.
Push notification are not yet available on React Native
Not seen notifications ≥ 1.3.0
To increase user engagement, let your users know that they have new notifications from the Octopus notification center. Displaying a badge with the number of new notifications in your app can be a great way to suggest your users to look at what's new in the community again.
- Android
- iOS
- Flutter
- React Native
To do that, the Octopus SDK exposes a val notSeenNotificationsCount: Flow<Int>
that you can collect:
OctopusSDK.notSeenNotificationsCount.collect {}
If you want to update this value with the latest count, simply call:
OctopusSDK.updateNotSeenNotificationsCount()
To see a full example of how you can achieve that, you can follow how it is done in the Samples, in the Octopus Sample app.
To do that, the Octopus SDK exposes a @Published var notSeenNotificationsCount: Int.
If you want to update this value with the latest count, simply call :
try await octopus.updateNotSeenNotificationsCount()
To see a full example of how you can achieve that, you can follow how it is done in the Samples, in the Scenario "Notification Badge".
To do that, the Octopus SDK exposes a Stream<int> that emits the unseen notifications count whenever it changes:
OctopusSDK.notSeenNotificationsCount.listen((count) {
// Update your badge with the new count
});
If you want to update this value with the latest count, simply call:
await octopus.updateNotSeenNotificationsCount();
Subscribe to unseen notification count changes using addNotSeenNotificationsCountListener. The SDK emits the current count automatically after initialization and whenever the count changes:
import {
addNotSeenNotificationsCountListener,
updateNotSeenNotificationsCount,
} from '@octopus-community/react-native';
// Subscribe to count changes
const subscription = addNotSeenNotificationsCountListener((count) => {
// Update your badge with the new count
console.log('Unseen notifications:', count);
});
// Manually trigger a server refresh of the count
await updateNotSeenNotificationsCount();
// Later, to unsubscribe:
subscription.remove();
Analytics
Octopus Community provides analytics to help you better understand your users' behavior within the community. To improve the quality of these analytics, we offer features that allow you to provide additional information about your users.
Custom events ≥ 1.4.0
You can tell the SDK to register a custom event. This event can be merged into the reports we provide.
- Android
- iOS
- Flutter
- React Native
OctopusSDK.track(
event = TrackerEvent.Custom(
name = "Purchase",
properties = mapOf(
"price" to TrackerEvent.Custom.Property(
value = String.format(Locale.US, "%.2f", 1.99)
),
"currency" to TrackerEvent.Custom.Property(value = "EUR"),
"product_id" to TrackerEvent.Custom.Property(value = "product1")
)
)
)
try await viewModel.octopus?.track(customEvent: CustomEvent(
name: "Purchase",
properties: [
"price": .init(value: "\(String(format: "%.2f", 1.99))"),
"currency": .init(value: "EUR"),
"product_id": .init(value: "product1"),
]))
To see a full example of how you can achieve that, you can follow how it is done in the Samples, in the Scenario "Custom events".
await octopus.trackCustomEvent('Purchase', {
'price': '1.99',
'currency': 'EUR',
'product_id': 'product1',
});
import { trackCustomEvent } from '@octopus-community/react-native';
await trackCustomEvent('Purchase', {
price: '1.99',
currency: 'EUR',
product_id: 'product1',
});
All property values must be strings. Convert numbers and booleans to strings before calling trackCustomEvent.
Community Visibility ≥ 1.3.0
If you enable access to the community for only a subset of your users and want the analytics we provide to take this into account, you can inform the SDK accordingly.
This information is reset at each SDK launch so be sure to call the function everytime and as soon as possible after SDK init.
This section is only usefull when the host app (i.e. your app) manages its own A/B testing logic. Call this if your app is running its own A/B test and you want Octopus to log whether a given user is in the "community-enabled" or "control" group. This is useful for reporting and engagement analytics, but does not change the SDK’s runtime behavior.
- Android
- iOS
- Flutter
- React Native
OctopusSDK.trackAccessToCommunity(hasAccess = canAccessCommunity)
Deprecated < 1.6.0
OctopusSDK.setHasAccessToCommunity(canAccessCommunity)
octopus.track(hasAccessToCommunity: canAccessCommunity)
Deprecated < 1.6.0
octopus.set(hasAccessToCommunity: canAccessCommunity)
To see a full example of how you can achieve that, you can follow how it is done in the Samples, in the Scenario "Track A/B Tests".
await octopus.trackCommunityAccess(canAccessCommunity);
import { trackCommunityAccess } from '@octopus-community/react-native';
await trackCommunityAccess(canAccessCommunity);
Octopus Events ≥ 1.9.0
As what the user does inside the Octopus UI is pretty opaque for you, we provide a way to register to be informed about events done by the user occuring inside Octopus.
If you have your own tracking service, you can use this publisher to listen to events that occured inside the Octopus UI part and feed your tracking service with them.
Here is the list of all events and their params that are emitted from the Octopus SDK
Content
postCreated
Sent when a post has been created by the current user.
Params:
postId: String— The id of the post.content: PostContent— Content of the post.PostContentis a set oftext,imageandpollindicating the content of the post.topicId: String— The id of the topic to which the post has been linked.textLength: Int— The length of the text of this post.
commentCreated
Sent when a comment has been created by the current user.
Params:
commentId: String— The id of the comment.postId: String— The id of the post in which the comment has been posted.textLength: Int— The length of the text of this comment.
replyCreated
Sent when a reply has been created by the current user.
Params:
replyId: String— The id of the reply.commentId: String— The id of the comment in which this reply has been posted.textLength: Int— The length of the text of this reply.
contentDeleted
Sent when a post, a comment or a reply has been deleted by the current user.
Params:
contentId: String— The id of the content that has been deleted.kind: ContentKind— The kind of content. Can bepost,commentorreply.
reactionModified
Sent when a reaction is modified (added, deleted or changed) on a content by the current user.
Params:
previousReaction: ReactionKind?— The previous reaction. Can be null.newReaction: ReactionKind?— The new reaction. If null, it means that the reaction has been deleted.ReactionKindis eitherheart,joy,mouthOpen,clap,cryorrage.contentId: String— The id of the content.contentKind: ContentKind— The kind of content. Can bepost,commentorreply.
pollVoted
Sent when the current user votes for a poll.
Params:
contentId: String— The id of the content (i.e. the post).optionId: String— The id of the option voted by the user.
contentReported
Sent when a content has been reported by the current user.
Params:
contentId: String— The id of the content.reasons: [ReportReason]— The reasons of reporting this content.
Gamification
gamificationPointsGained
Sent when gamification points are gained. Please note that only the points triggered by an in-app action are reported live.
Params:
pointsGained: Int— The points that have been added.action: GamificationPointsGainedAction— The action that led to gaining points.
gamificationPointsRemoved
Sent when gamification points are removed. Please note that only the points triggered by an in-app action are reported live. For example, if a post of this user gets moderated, you won't receive the information about points removed.
Params:
pointsRemoved: Int— The points that have been removed.action: GamificationPointsRemovedAction— The action that led to losing points.
Navigation & UI
screenDisplayed
Sent when the user navigates to a given screen.
Params:
-
screen: Screen— The screen that has been displayed.Screencan be:-
postsFeed— The posts feed (i.e. list of posts)Params:
feedId: String— The id of the feed that is displayed.relatedTopicId: String?— The id to the topic that is related to this feed. Null if the feed is not representing a topic or is multi-topic (for example, the feed "For You").
-
postDetail— The post detail screen with the list of commentsParams:
postId: String— The id of the post that is displayed.
-
commentDetail— The comment detail screen with the list of repliesParams:
commentId: String— The id of the comment that is displayed.
-
createPost— The create post screen -
profile— The user profile screen -
otherUserProfile— The profile screen of another Octopus userParams:
profileId: String— The id of the profile that is displayed.
-
editProfile— The edit profile screen -
reportContent— The report content screen -
reportProfile— The report profile screen -
validateNickname— The validate nickname screen (displayed after a user with a non-modified nickname has created a post) -
settingsList— The settings screen -
settingsAccount— The account settings screen. Only visible if the SDK is configured in Octopus authentication (not SSO) -
settingsAbout— The about settings screen -
reportExplanation— The report explanation screen -
deleteAccount— The delete account screen. Only visible if the SDK is configured in Octopus authentication (not SSO)
-
notificationClicked
Sent when the user clicks on an internal notification (from the Octopus Notification Center).
Params:
notificationId: String— The id of the notification.contentId: String?— The target content id. Can be null if the notification does not target a content.
postClicked
Sent when the user clicks on a post.
Params:
postId: String— The id of the post.source: PostClickedSource— The source of the click. Can befeed(if the post was displayed in the posts feed) orprofile(if the post was displayed in a user profile posts list).
translationButtonClicked
Sent when the user clicks on a translation button.
Params:
contentId: String— The id of the content.viewTranslated: Bool— Whether the user wants to display the translated or the original content.contentKind: ContentKind— The kind of content. Can bepost,commentorreply.
commentButtonClicked
Sent when the user clicks the comment button of a post.
Params:
postId: String— The id of the post.
replyButtonClicked
Sent when the user clicks the reply button of a comment.
Params:
commentId: String— The id of the comment.
seeRepliesButtonClicked
Sent when the user clicks on the replies button of a comment.
Params:
commentId: String— The id of the comment.
Profile
profileModified
Sent when the profile is modified by the user.
Params:
-
nickname: ProfileFieldUpdate<NicknameUpdateContext>— The nickname update information. Either unchanged or changed. -
bio: ProfileFieldUpdate<BioUpdateContext>— The bio update information. Either unchanged or changed.BioUpdateContext params:
bioLength: Int— The length of the bio.
-
picture: ProfileFieldUpdate<PictureUpdateContext>— The picture update information. Either unchanged or changed.PictureUpdateContext params:
hasPicture: Int— Whether the user has added a picture or deleted the existing one.
Session
sessionStarted
Sent when an Octopus UI session is started.
Params:
sessionId: String— The id of the session.
sessionStopped
Sent when an Octopus UI session is stopped (call either when the Octopus UI is closed or when the app is put in background).
Params:
sessionId: String— The id of the session.
Types
ContentKind
Can be post, comment or reply.
ReactionKind
Can be heart, joy, mouthOpen, clap, cry or rage.
PostContent
A set of text, image and poll indicating the content of the post.
Here is how to listen to these events:
- Android
- iOS
- Flutter
- React Native
// Listen to Octopus Events and convert into events for your analytics tool,
// for example here with Firebase Analytics
OctopusSDK.events.collect { event ->
when(event) {
is PostCreated -> {
FirebaseAnalytics.getInstance().logEvent(
name = "post_created",
params = Bundle().apply {
putString("topic", event.topicId)
putInt("text_length", event.textLength)
putBoolean("has_poll", event.content.contains(POLL)
putBoolean("has_image", event.content.contains(IMAGE)
}
)
}
else -> { //... }
}
}
// Listen to Octopus Events and convert into events for your analytics tool,
// for example here with Firebase Analytics
octopus.eventPublisher.sink { event in
switch event {
case let .postCreated(context):
Analytics.logEvent("post_created", parameters: [
"topic": context.topicId,
"text_length": context.textLength,
"has_poll": context.content.contains(.poll),
"has_image": context.content.contains(.image)
])
case ...
}
}
To see a full example of how you can achieve that, you can follow how it is done in the Samples, in the Scenario "Events".
// Listen to Octopus Events and convert into events for your analytics tool
OctopusSDK.events.listen((event) {
switch (event) {
case PostCreatedEvent():
// Log to your analytics tool
analytics.logEvent(
name: 'post_created',
parameters: {
'topic': event.topicId,
'text_length': event.textLength,
},
);
// ... handle other events
default:
break;
}
});
In React Native, the gamification event fields are named points (not pointsGained / pointsRemoved as on other platforms). The content field on postCreated is a PostContentType[] array of string values ('text', 'image', 'poll'), so use .includes() to check for content types.
import { addSDKEventListener } from '@octopus-community/react-native';
import type { SDKEvent } from '@octopus-community/react-native';
// Listen to Octopus Events and convert into events for your analytics tool
const subscription = addSDKEventListener((event: SDKEvent) => {
switch (event.type) {
case 'postCreated':
analytics.logEvent('post_created', {
topic: event.topicId,
text_length: event.textLength,
has_poll: event.content.includes('poll'),
has_image: event.content.includes('image'),
});
break;
case 'gamificationPointsGained':
analytics.logEvent('points_gained', {
points: event.points, // 'points' in React Native (not 'pointsGained')
action: event.action,
});
break;
case 'gamificationPointsRemoved':
analytics.logEvent('points_removed', {
points: event.points, // 'points' in React Native (not 'pointsRemoved')
action: event.action,
});
break;
// ... handle other events — use a default case for forward compatibility
default:
break;
}
});
// Later, to unsubscribe:
subscription.remove();
Bridges ≥ 1.5.0
Octopus provides a way of linking a specific object in your app (e.g. a page, a product, any kind of content) to a dynamic post in the community, created automatically. We call this a bridge. To create a bridge, your content (an article, a product, etc.) must have a unique identifier. This ID will be used to link your content to the post. It will also allow users in the Octopus Community to directly view your content.
The bridge process involves four main steps:
Step 1: Prepare the post data Use the SDK to get (or create) the ID of the post related to your content. First get from the SDK the id of the post to display. The SDK will create the post if it is not already created. To do that, call the dedicated API and pass the required data:
- Content id (called clientObjectId) that the post will be about
- Text of the post. It must be between 10 and 5000 (3000 before the 1.8.0) characters.
- Catchphrase (optional). It will be displayed below the title, in bold. You can use something like "What do you think about this?". It must be less than 84 characters and we recommand between 6 and 38 characters.
- Image for the post (optional). You can either pass a remote image using an URL or a local image.
The image must respect these requirements:
- File size must be less than 50Mb
- File format must be jpg or png
- Image sides must be between 50px and 4000px, with a max ratio of 32:9 (bigger side / smaller side)
- If you pass a remote image, the resource should be public
- Text of the button that will invite your users to display your content (optional). You can use something like "Buy it" if your content is a purchasable product, or "Read the article" if it is a press article. When the button containing this text will be tapped, the SDK will ask you to display your content. If no text is provided, the button won't be displayed. It must be less than 28 characters, we recommand between 4 and 28 characters.
- Topic id (optional). This is the id of the topic that you want the post to be labelled with. If not provided, the topic will be automatically set according to your community settings. You can retrieve the list of the available topics with the dedicated API (see below).
- Android
- iOS
- Flutter
- React Native
// Get cached topics
OctopusSDK.topics.collect { topics ->
// List of available topics
}
// Fetch latest topics from server
OctopusSDK.fetchTopics()
// Get cached topics
octopus.$topics
.sink { topics in
// List of available topics
}
// Fetch latest topics from server
octopus.fetchTopics()
Bridges are not yet available on Flutter.
Bridges are not yet available on React Native.
- Signature (Deprecated < 1.10.0): replaced by enhanced security in the
fetchOrCreateClientObjectRelatedPostfunction using a callback and a unique token per post.
- Android
- iOS
- Flutter
- React Native
val clientPost = ClientPost(
objectId = "recipe-129302938", // A unique identifier for your content
text = "The perfect Canelés", // Between 10 and 5000 chars
attachment = Image.Remote(url = imageUrl), // You can also pass Image.Local(uri)
topicId = foodRecipeId, // The id of the Octopus topic. Null if default.
catchPhrase = "Tried the canelés? Tell us how good they were!", // Less than 84 characters
viewObjectButtonText = "Read the recipe" // Less than 28 characters
)
let postContent = ClientPost(
clientObjectId: "recipe-129302938", // a unique identifier for your content
topicId: foodRecipeId, // the id of the Octopus topic. Nil if default.
text: "The perfect Canelés", // between 10 and 5000 chars
catchPhrase: "Tried the canelés? Tell us how good they were!", // Less than 84 characters
attachment: .localImage(image.jpegData(compressionQuality: 1)!), // you can also pass .distantImage(url)
viewClientObjectButtonText: "Read the recipe" // Less than 28 characters, not displayed if you did not set any `displayClientObjectCallback`
)
Bridges are not yet available on Flutter.
Bridges are not yet available on React Native.
Step 2: Get the post ID
Calling the API with the previously created post data will return a post ID. With this ID, you can display the post using the Octopus UI. If the post is not created, this API will create it, otherwise it won't change its content.
To ensure security, you must provide a callback called tokenProvider. The SDK calls this callback only when a new post needs to be created, to get the post signature from you. For maximum security (recommended), you should compute the bridge fingerprint yourself on the backend. You can find how to do it in the Generate fingerprint for bridge documentation. Alternatively, with reduced security, you can use the bridge fingerprint provided by the callback.
In both cases, with the fingerprint, you should ask your server to sign it (see Generate token for bridge documentation for details on how to do this).
- Android
- iOS
- Flutter
- React Native
val result = OctopusSDK.fetchOrCreateClientObjectRelatedPost(
clientPost = clientPost,
tokenProvider = { bridgeFingerprint ->
// For more security, ignore the bridgeFingerprint and get the jwt directly from a fingerprint
// computed on your backend.
// Otherwise, call your backend /generateBridgeSignature route with the bridgeFingerprint.
// `server` represents your own backend client — replace with your actual implementation.
// Return null if your community does not require a signature.
server.getBridgeSignature(bridgeFingerprint)
}
)
when (result) {
is OctopusResult.Success -> {
val post = result.data
val postId = post.id
// Navigate to the post or handle success
}
is OctopusResult.Error -> {
// Handle error
}
}
Deprecated < 1.10.0
// fetchOrCreateClientObjectRelatedPost without tokenProvider
val result = OctopusSDK.fetchOrCreateClientObjectRelatedPost(clientPost)
let post = try await octopus.fetchOrCreateClientObjectRelatedPost(
content: postContent,
tokenProvider: { bridgeFingerprint in
// For more security, ignore the bridgeFingerprint and get the jwt directly from a fingerprint
// computed on your backend.
// Otherwise, call your backend /generateBridgeSignature route with the bridgeFingerprint.
// `server` represents your own backend client — replace with your actual implementation.
// Return nil if your community does not require a signature.
return try await server.getBridgeSignature(bridgeFingerprint: bridgeFingerprint)
}
)
let postId = post.id
Deprecated < 1.10.0
// fetchOrCreateClientObjectRelatedPost without tokenProvider
let post = try await octopus.fetchOrCreateClientObjectRelatedPost(content: postContent)
let postId = post.id
Deprecated < 1.7.0
let postId = try await octopus.getOrCreateClientObjectRelatedPostId(content: postContent)
Bridges are not yet available on Flutter.
Bridges are not yet available on React Native.
Step 3: Handle user interaction
Optionally, you can register a callback to be notified when a user wants to view your content from a bridge post. The SDK will provide the clientObjectId so you can open the appropriate content.
- Android
- iOS
- Flutter
- React Native
// In your NavHost setup
octopusComposables(
// ...
onNavigateToClientObject = { objectId ->
// Display the content that has the given objectId
}
)
octopus.set(displayClientObjectCallback: { objectId in
// display the content that has the given objectId
})
Note that if you don't set the displayClientObjectCallback, the button containing the viewClientObjectButtonText you passed in the ClientPost won't be displayed.
Bridges are not yet available on Flutter.
Bridges are not yet available on React Native.
Step 4: Display the post
Using the post id, you can display it directly after the user taps a button on your object page:
- Android
- iOS
- Flutter
- React Native
OctopusPostDetailsContent(
modifier = Modifier.fillMaxSize(),
postId = postId,
// ...
)
OctopusHomeScreen(octopus: octopus, postId: postId)
Bridges are not yet available on Flutter.
Bridges are not yet available on React Native.
Optional: accessing live data about the post > 1.7.0
Additionaly to the post id, we also provide more information about the post. You can access and display the reaction count that all Octopus users did on the post, the number of comments and the number of views.
You can retrieve an object that references the post and that will be updated with the current value and as soon as you call the fetchOrCreateClientObjectRelatedPost function.
- Android
- iOS
- Flutter
- React Native
OctopusSDK.getClientObjectRelatedPostFlow(clientObjectId = "recipe-129302938")
.filterNotNull()
.collect { post ->
val reactions = post.reactions // Reactions is List<OctopusReactionCount> and OctopusReactionCount is a data class with a reaction and a count
val commentCount = post.commentCount
val viewCount = post.viewCount
}
octopus.getClientObjectRelatedPostPublisher(clientObjectId: "recipe-129302938")
.sink { post in
let reactions = post.reactions // reactions is [OctopusReactionCount] and OctopusReactionCount is a struct with a reaction and a count
let commentCount = post.commentCount
let viewCount = post.viewCount
}
Bridges are not yet available on Flutter.
Bridges are not yet available on React Native.
Optional: reading the current user's reaction ≥ 1.9.1
You can read the current user's reaction on a bridge post from the post object. This is useful if you want to display the user's reaction in your own UI outside of Octopus.
- Android
- iOS
- Flutter
- React Native
OctopusSDK.getClientObjectRelatedPostFlow(clientObjectId = "recipe-129302938")
.filterNotNull()
.collect { post ->
val userReaction = post.userReactionKind // OctopusReactionKind? — null if the user has not reacted
}
octopus.getClientObjectRelatedPostPublisher(clientObjectId: "recipe-129302938")
.sink { post in
let userReaction = post.userReaction // OctopusReactionKind? — nil if the user has not reacted
}
Bridges are not yet available on Flutter.
Bridges are not yet available on React Native.
Optional: setting a reaction on a bridge post ≥ 1.9.1
If you display bridge post data in your own UI, you can let users react to the post without entering the Octopus Community screen. Pass a reaction kind to set a reaction, or pass null/nil to remove it.
Available reaction kinds: heart ❤️, joy 😂, mouthOpen 😮, clap 👏, cry 😢, rage 😡.
- Android
- iOS
- Flutter
- React Native
// Set a reaction
OctopusSDK.setReaction(
reaction = OctopusReactionKind.Heart,
clientObjectRelatedPostId = "recipe-129302938"
)
// Remove the reaction
OctopusSDK.setReaction(reaction = null, clientObjectRelatedPostId = "recipe-129302938")
// Set a reaction
try await octopus.set(reaction: .heart, clientObjectRelatedPostId: "recipe-129302938")
// Remove the reaction
try await octopus.set(reaction: nil, clientObjectRelatedPostId: "recipe-129302938")
Bridges are not yet available on Flutter.
Bridges are not yet available on React Native.
To see a full example of how you can achieve that, you can follow how it is done in the Samples:
- Android
- iOS
- Flutter
- React Native
in the Octopus Sample app.
The MainViewModel is in charge of preparing the client post and getting the post.
in the Scenario "Bridge to Client Object".
The RecipeViewModel is in charge of preparing the client post and getting the post id and the BridgeToClientObjectViewModel
is in charge of setting the callback.
Bridges are not yet available on Flutter.
Bridges are not yet available on React Native.
Octopus A/B Testing ≥ 1.6.0
The A/B test feature lets you measure the impact of the community on your app usage by splitting your audience into two cohorts:
- Test group: a portion of users (e.g., 30%) with full access to the community.
- Control group: the remaining users (e.g., 70%) without access. For them, when they tap the community button, Octopus displays a screen: “The community is not available yet for you, please come back later.” In Octopus Analytics, you’ll then see clear comparisons between these two cohorts in terms of app session volume and retention.
Even if this A/B Test is totally internal, we let you know whether the user can access the community or not (i.e., in which group the user is).
We also let you override the group assigned to the connected user. This can be useful during your tests to check what will see the users of each group or even to provide or disable access to some given users.
Use this method when you need to guarantee that the user’s community access is enforced by Octopus, regardless of internal A/B testing rules.
Here is how to override the user community access:
- Android
- iOS
- Flutter
- React Native
OctopusSDK.overrideCommunityAccess(hasAccess = canAccessCommunity)
Here is how to know whether the current user has access to the community. It is a published value that is updated as soon as the user group changes. ≥ 1.6.1
OctopusSDK.hasAccessToCommunity.collect { hasAccessToCommunity ->
// Use the hasAccessToCommunity new value
}
To see a full example of how you can achieve that, you can follow how it is done in the Samples, in the Octopus Sample app.
octopus.overrideCommunityAccess(canAccessCommunity)
Here is how to know whether the current user has access to the community. It is a published value that is updated as soon as the user group changes. ≥ 1.6.1
// one shot value
let hasAccess = octopus.hasAccessToCommunity
// published value
octopus.$hasAccessToCommunity.sink { hasAccessToCommunity in
// use the hasAccessToCommunity new value
}
To see a full example of how you can achieve that, you can follow how it is done in the Samples, in the Scenario "Force Octopus A/B Tests Cohort".
await octopus.overrideCommunityAccess(canAccessCommunity);
Here is how to know whether the current user has access to the community. It is a reactive stream that is updated as soon as the user group changes:
OctopusSDK.hasAccessToCommunity.listen((hasAccessToCommunity) {
// Use the hasAccessToCommunity new value
});
import {
overrideCommunityAccess,
addHasAccessToCommunityListener,
} from '@octopus-community/react-native';
// Override the community access for the current user
await overrideCommunityAccess(canAccessCommunity);
Here is how to know whether the current user has access to the community. The listener is called whenever the access state changes:
const subscription = addHasAccessToCommunityListener((hasAccess) => {
// Update your UI based on the new access value
});
// Later, to unsubscribe:
subscription.remove();
Intercept URL openings ≥ 1.9.0
Users can open links from the SDK. These URLs can either be opened from a link tapped on a post/comment/reply content, or from a Post with CTA button tap. These links will be opened by default in the web browser. Octopus SDK lets you the ability to catch these URL opening and decide what to do with the URL. Either using it yourself to do whatever you want or let Octopus handle them. You could, for example, catch any URL opening your website and instead opening the correct page in your own app.
- Android
- iOS
- Flutter
- React Native
// Set the callback that will be called when a user tries to open a link inside the community.
// This link can come from a Post/Comment/Reply or when tapping on a Post with CTA button.
octopusComposables(
// ...
onNavigateToUrl = { url ->
val uri = url.toUri()
if(uri.host == "www.yourdomain.com" && uri.path == "/contact") {
// Open the contact page inside the app
...
// Link has been handled by app, let the Octopus SDK know that it should do nothing more
return HandledByApp
}
// Let the SDK handle the other links by returning `HandledByOctopus`
return HandledByOctopus
}
)
To see a full example of how you can achieve that, you can follow how it is done in the Samples, in the UrlHandler.
// Set the callback that will be called when a user tries to open a link inside the community.
// This link can come from a Post/Comment/Reply or when tapping on a Post with CTA button.
octopus.set(onNavigateToURLCallback: { url in
if url.host == "www.yourdomain.com" && url.path == "/contact" {
// open the contact page inside the app
...
// link has been handled by app, let the Octopus SDK know that it should do nothing more
return .handledByApp
}
// Let the SDK handle the other links by returning `handledByOctopus`
return .handledByOctopus
}
To see a full example of how you can achieve that, you can follow how it is done in the Samples, in the URLManager.
OctopusHomeScreen(
onNavigateToUrl: (url) {
final uri = Uri.parse(url);
if (uri.host == "www.yourdomain.com" && uri.path == "/contact") {
// Open the contact page inside the app
// ...
// Link has been handled by app, let the Octopus SDK know that it should do nothing more
return UrlOpeningStrategy.handledByApp;
}
// Let the SDK handle the other links
return UrlOpeningStrategy.handledByOctopus;
},
// ...
)
Enable URL interception by passing interceptUrls: true when opening the UI:
import {
openUI,
addNavigateToUrlListener,
UrlOpeningStrategy,
} from '@octopus-community/react-native';
// Subscribe to URL navigation events
const subscription = addNavigateToUrlListener(async (url) => {
const parsed = new URL(url);
if (parsed.hostname === 'www.yourdomain.com' && parsed.pathname === '/contact') {
// Open the contact page inside the app
// ...
// Link has been handled by app
return UrlOpeningStrategy.handledByApp;
}
// Let the SDK handle the other links
return UrlOpeningStrategy.handledByOctopus;
});
// Open the UI with URL interception enabled
await openUI({ interceptUrls: true });
// Later, to unsubscribe:
subscription.remove();
You can also enable URL interception on the embedded OctopusUIView component. Make sure to set up the addNavigateToUrlListener before the view mounts, and call subscription.remove() when the component unmounts:
<OctopusUIView interceptUrls={true} style={StyleSheet.absoluteFill} />
Override the language ≥ 1.9.0
On both iOS and Android, the system lets the user choose a language for their device and also lets them customize this language per app. Some apps do not use this standard way of handling the language. If you have a custom setting inside your app that does not set the system app language, you can call a function of Octopus in order to customize the language used (so Octopus does not use the system language but yours instead).
That being said, we recommend using the default system way of handling the locale, so system alerts and system screens (like the picture selection screen) are displayed in the desired language.
OctopusSDK supports these languages: English, French, German, Italian, Polish, Portuguese, Spanish, Swedish, Turkish. Passing another locale will fallback to the default language (english).
No check can be done on the Locale you pass, so ensure it is a valid Locale. If the locale is not valid, it will fallback on the default language (english).
- Android
- iOS
- Flutter
- React Native
// Override the default (i.e. system or app based) locale
OctopusSDK.overrideDefaultLocale(Locale.FRENCH)
// Override the default (i.e. system or app based) locale
// The locale should respect the BCP-47 standard: it can have a language and an optional region.
// The language must be two letters [ISO 639-1 code](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes) and the region should be two letters [ISO 3166-1](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements).
// If the region is provided, it should be separated from the country by a `-`.
// only a language
octopus.overrideDefaultLocale(with: Locale(identifier: "fr"))
// a language and a region (will use Portuguese inside Octopus because Brasilian Portuguese is not supported)
octopus.overrideDefaultLocale(with: Locale(identifier: "pt-BR"))
To see a full example of how you can achieve that, you can follow how it is done in the Samples, in the Scenario "Override the language of the SDK".
// Override the default (i.e. system or app based) locale
await octopus.overrideDefaultLocale(const Locale('fr'));
// You can also specify a country code
await octopus.overrideDefaultLocale(const Locale('en', 'US'));
// Reset to system default
await octopus.overrideDefaultLocale(null);
import { overrideDefaultLocale } from '@octopus-community/react-native';
// Override the default (i.e. system or app based) locale
await overrideDefaultLocale({ languageCode: 'fr' });
// You can also specify a country code
await overrideDefaultLocale({ languageCode: 'en', countryCode: 'US' });
// Reset to system default
await overrideDefaultLocale(null);
Follow the Samples
Want to see a code examples on how to use the SDK, no worries, we have that for you!
- Android
- iOS
- Flutter
-
First, clone the OctopusSDK project
-
Add those lines to the root project
local.propertiesfile:OCTOPUS_API_KEY=YOUR_API_KEY
OCTOPUS_SSO_CLIENT_USER_TOKEN_SECRET=YOUR_USER_TOKEN_SECRETReplace
YOUR_API_KEYwith your own API key andYOUR_USER_TOKEN_SECRETwith your own token secret. -
According to your desired UI integration mode choose the corresponding sample:
-
First, clone or download the code (SDK+Sample are placed on the same git repository):
-
Rename the
secrets.placeholder.xcconfigassecrets.xcconfig -
According to your connection mode, this matches how your app should integrate the SDK:
- If your are in SSO without any app managed fields:
- In
secrets.xcconfig, replaceOCTOPUS_SSO_NO_MANAGED_FIELDS_API_KEYwith your own API key - Open the third tab
More, then tap on the SSO Connection cell and then the No App Managed Fields.
- In
- If your are in SSO with all app managed fields:
- In
secrets.xcconfig, replaceOCTOPUS_SSO_ALL_MANAGED_FIELDS_API_KEYwith your own API key - Open the third tab
More, then tap on the SSO Connection cell and then the With all App Managed Fields.
- In
- If your are in SSO with some app managed fields:
- In
secrets.xcconfig, replaceOCTOPUS_SSO_SOME_MANAGED_FIELDS_API_KEYwith your own API key - In
SSOWithSomeAppManagedFieldsViewModel, set the fields that are associated inappManagedFields. - Open the third tab
More, then tap on the SSO Connection cell and then the Some App Managed Fields.
- In
- If your are in SSO without any app managed fields:
-
First, clone the Flutter SDK example project:
-
Navigate to the example directory:
cd example -
Install dependencies:
flutter pub get -
Replace
YOUR_API_KEYin the example code with your own API key. -
Run the example:
flutter run