Skip to main content

Bridges

Link a page or an object of your app (an article, a product, a recipe…) to a community post that Octopus creates for it, so your users can discuss it and jump back to it from the community.

Android availableiOS availableFlutter ≥ 1.12.0React Native ≥ 1.13.0Unity ≥ 1.13.0

Before you begin​

  • Complete the Quickstart: the SDK is initialized and the community UI opens.
  • Give each object you want to link a stable, unique id. The SDK uses it to find the post again, and hands it back to you when a user taps the post's button.
  • If your community requires signed bridge posts, add a signing route to your backend: see Generate fingerprint for bridge and Generate token for bridge.
  • To label the post with a group, get the group id from the group list.

How it works​

A bridge is a post that belongs to one of your objects:

  1. Your app describes the object: its id, the text of the post, and optionally an image, a catchphrase, a button label and a group.
  2. The SDK fetches the post linked to this id. The first time, it creates the post from your description and asks your token provider for a signature. Later calls return the existing post unchanged: the description is only used at creation.
  3. The SDK returns the post, with its Octopus post id. Open the Octopus UI on this post id to show it.
  4. When a user taps the post's button in the community, the SDK calls your callback with the object id, and your app opens the matching content.
  5. Optionally, your app observes the post to show its reaction, comment and view counts in its own UI, and sets the user's reaction from there.

1. Prepare the post content​

Describe the object and the post to create for it. Only the object id and the text are required.

FieldRequiredConstraint
Object idYesStable and unique for your object.
TextYesBetween 10 and 5000 characters.
CatchphraseNoShown in bold below the text, for example "What do you think about this?". Fewer than 84 characters; 6 to 38 recommended.
Button textNoLabel of the button that opens your object, for example "Read the article" or "Buy it". Fewer than 28 characters; 4 to 28 recommended. Without it, or without the callback of step 3, no button is shown.
Group idNoThe group the post is labelled with. Without it, the group follows your community settings.
ImageNoA local image or a remote URL, within the image constraints below.

The image must meet these constraints:

ConstraintValue
File sizeLess than 50 MB
FormatJPG or PNG
Side lengthBetween 50 px and 4000 px
Aspect ratioAt most 32:9 (longer side divided by shorter side)
Remote imageA public URL that points at the image file itself
  • ClientPost(objectId, text, attachment, catchPhrase, viewObjectButtonText, groupId) — only objectId and text are required.
  • attachment: Resource? — Resource.Remote(url) for a remote image, Resource.Local(uri) for a local one.
val clientPost = ClientPost(
objectId = "recipe-129302938", // A unique identifier for your content
text = "The perfect Canelés", // Between 10 and 5000 chars
attachment = Resource.Remote(url = imageUrl), // or Resource.Local(uri)
groupId = foodRecipeGroupId, // The id of the Octopus group. 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
)

2. Get or create the post​

Pass the content to the SDK. It returns the post linked to the object id, and creates it the first time. An existing post is returned as is: changing the content afterwards does not update it.

When the SDK creates a post, it calls your token provider with a bridge fingerprint and waits for a signature. For the best security, ignore this fingerprint: compute it on your backend from your own copy of the content (see Generate fingerprint for bridge). Otherwise, send the fingerprint to your backend. In both cases, your backend signs it (see Generate token for bridge). If your community does not require a signature, return no token.

warning

Avoid repeatedly deleting and recreating a bridge post for the same content id. Recreating a post starts a brand-new bridge: its comments, reactions and engagement counters all reset to zero. On top of that, each content id only supports a limited number of delete-and-recreate cycles; past that limit, creating a new post for that content will fail. Treat deletion as occasional, not as part of a regular publishing loop.

  • clientPost: ClientPost — the content from step 1.
  • tokenProvider: (suspend (String) -> String?)? — receives the bridge fingerprint and returns the signature, or null when your community does not require one.
  • Returns OctopusResult<OctopusPost, ClientPostError>; the function is suspend.
val result = OctopusSDK.fetchOrCreateClientObjectRelatedPost(
clientPost = clientPost,
tokenProvider = { 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 postId = result.data.id // Octopus post id, used in steps 4 and 6
}
is OctopusResult.Failure -> {
// Invalid content (ClientPostError), no network, user not authenticated…
}
}

3. Handle taps on the content button​

Register a callback to open your object when a user taps the post's button in the community. The SDK passes the object id you gave in step 1. Register it before opening the Octopus UI: without a callback, the button is not shown.

  • onNavigateToClientObject: (String) -> Unit — a parameter of octopusComposables, called with the object id.
// In your NavHost setup
octopusComposables(
// ...
onNavigateToClientObject = { objectId ->
// Display the content that has the given objectId
}
)

4. Display the post​

Open the Octopus UI on the post id returned in step 2, for example when the user taps a "Discuss" button on your object page. See Open a specific screen for the other entry points of each platform.

  • OctopusPostDetailsContent(navController, postId, …) — the post detail screen as a composable.
OctopusPostDetailsContent(
navController = navController,
postId = postId,
modifier = Modifier.fillMaxSize(),
// ...
)

5. Show live post data​

Observe the post linked to an object to show its reaction, comment and view counts in your own UI, along with the current user's reaction. The observation emits the latest state of the post, and nothing (or an empty value) until the post exists.

The post carries:

FieldMeaning
IdThe Octopus post id.
ReactionsOne entry per reaction kind, with its count.
Comment countNumber of comments.
View countNumber of views.
User reactionThe current user's reaction, or empty when the user has not reacted.
  • OctopusSDK.getClientObjectRelatedPostFlow(clientObjectId): Flow<OctopusPost?> — emits null until the post exists.
  • OctopusPost: id, reactions: List<OctopusReactionCount> (each with reactionKind and count), commentCount, viewCount, userReactionKind.
OctopusSDK.getClientObjectRelatedPostFlow(clientObjectId = "recipe-129302938")
.filterNotNull()
.collect { post ->
val reactions = post.reactions
val commentCount = post.commentCount
val viewCount = post.viewCount
val userReaction = post.userReactionKind // null if the user has not reacted
}

6. Set a reaction from your UI​

Android ≥ 1.12.0iOS ≥ 1.12.0Flutter ≥ 1.12.0React Native ≥ 1.13.0Unity ≥ 1.13.0

Let users react to the post without opening the community. Pass a reaction kind to set the user's reaction, or an empty value to remove it. Setting the same reaction twice has no further effect.

The call takes the Octopus post id returned in step 2 or read from the observed post, not your object id. It works on any post, bridge or not.

The reaction kinds are heart ❤️, joy 😂, mouthOpen 😮, clap 👏, cry 😢 and rage 😡.

  • reaction: OctopusReactionKind? — OctopusReactionKind.Heart, Joy, MouthOpen, Clap, Cry or Rage; null removes the reaction.
  • postId: String — the Octopus post id.
  • Returns OctopusResult<Unit, SetReactionError>; the function is suspend.
// Set a reaction; pass reaction = null to remove it
when (val result = OctopusSDK.setReaction(reaction = OctopusReactionKind.Heart, postId = postId)) {
is OctopusResult.Success -> {
// Reaction updated
}
is OctopusResult.Failure -> {
// SetReactionError (UnknownReaction, PostNotFound, ReactionError), no network…
}
}
info

Removing a reaction when the user has none succeeds without doing anything.


Sign a Bridge Share image for a picture-restricted community​

Android ≥ 1.12.1iOS ≥ 1.12.3Flutter ≥ 1.12.2React Native ≥ 1.13.0Unity ≥ 1.12.2

This section applies to the create-post editor opened with prefilled content, not to the bridge posts of the steps above. When you open that editor prefilled with an image and the target community forbids member pictures, the SDK needs a signed token that authorizes the image.

Provide a signing callback. The SDK computes a content fingerprint (SHA-256 of the post's text, call to action and image) and calls your callback with it. Your backend returns a compact JWT signed HS256 with your shared secret, whose bridge_fingerprint claim equals the fingerprint. Sign on your backend: never ship the secret in the app. Without a callback, an image in such a community is rejected. Text-only posts, and communities that allow pictures, need no signature.

  • CreatePostScreenInfo(prefilledPost, bridgeShareTokenProvider) — bridgeShareTokenProvider: (suspend (String) -> String?)? receives the fingerprint and returns the JWT. Returning null sends the post unsigned.
  • Both entry points accept it: navigateToOctopusCreatePost(info = …) in the community navigation, and OctopusCreatePostScreen(navController, info) as a standalone screen.
val info = CreatePostScreenInfo(
prefilledPost = prefilledPost,
bridgeShareTokenProvider = { bridgeFingerprint ->
// Ask YOUR backend to sign the fingerprint and return the JWT
myBackend.signBridgeShare(bridgeFingerprint)
},
)

// In the community navigation
navController.navigateToOctopusCreatePost(info = info)

// Or as a standalone screen
OctopusCreatePostScreen(navController = navController, info = info)

See Generate a JWT for a bridge "Share" with an image for the JWT contract your backend implements.


See it in the samples​

Each sample app implements the full bridge flow.

The Octopus Sample app: MainViewModel prepares the client post and gets the post.

Behavior and limits​

  • The content is used only when the post is created. To change a live post, edit it from the back office; do not delete and recreate it.
  • The token provider is called only when a post is created, never for an existing post.
  • The reaction call takes the Octopus post id, not your object id.
  • Validate the content against the constraints of step 1 before you send it: an invalid field fails the request.

Next steps​