← All posts
Guides · · 6 min read

Add in-app feedback to an Android app in four calls

One Gradle line, one initialize call, one composable. What each step actually does, and the three places integrations usually go wrong.

Adding a feedback board to an Android app takes one dependency and three SDK calls. The composable renders the whole thing — list, details, editor, voting, comments — inside your own navigation, and there is no backend to run.

As of the 0.3.0 SDK (August 2026), the complete integration is this:

MyApplication.kt
Fedo.initialize(apiKey = "your-api-key-here")
FeedbackRoute.kt
FeedbacksScreen(
    onDismiss = { navController.popBackStack() }
)

Everything below is detail on those two blocks, plus the optional user identity calls and the three mistakes that actually cost people an afternoon.

Key takeaways

  • The dependency is com.getfedo:sdk-android:0.3.0 on Maven Central.
  • Fedo.initialize must run before any other SDK method — the SDK is a singleton and every other call reads state it sets up.
  • FeedbacksScreen() is a @Composable, not a launcher function. It renders in place, inside your Compose hierarchy, and manages its own internal navigation across three screens.
  • onDismiss fires only when the user presses back on the root list screen. Wire it to your own back-stack pop or the back button looks broken.
  • If you never call setUserID, the SDK creates a cached anonymous user and everything still works.

1. Add the dependency

app/build.gradle.kts
dependencies {
    implementation("com.getfedo:sdk-android:0.3.0")
}

The SDK follows semantic versioning, and the Android changelog lists what changed in each release. Initial release was 0.1.4 in July 2026; 0.3.0 landed on 11 August 2026.

2. Initialize once, early

MyApplication.kt
import android.app.Application
import com.fedo.sdk.Fedo

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        Fedo.initialize(apiKey = "your-api-key-here")
    }
}

Application.onCreate() is the natural home. Your launcher Activity works too, as long as it runs before anything touches the SDK.

This is the first mistake. Fedo.initialize must be called before any other SDK method. Calling setUserID from a repository that happens to construct earlier than your Application subclass — or from a DI graph that eagerly instantiates — puts the call ahead of initialization. Initialize first, then everything else.

If you need to change SDK behaviour, pass a config block:

MyApplication.kt
Fedo.init(
    apiKey = "your-api-key",
    config = {
        httpTimeoutInMillis = 15_000
        debug = true
    }
)

Two options exist today. httpTimeoutInMillis defaults to 10_000 and caps API calls. debug defaults to false; turning it on writes verbose logging to logcat under the FedoLogger tag, which is the fastest way to see whether a failing call is yours or the SDK's. Full list on the configuration page.

3. Tell the SDK who the user is

All four identity methods are optional, and all of them must come after initialize:

AuthRepository.kt
Fedo.setUserID("user-abc-123")
Fedo.setUserDisplayName("Jane Doe")
Fedo.setUserEmail("jane@example.com")
Fedo.addUserProperty("plan", "enterprise")

setUserID is the one that matters. Call it when you actually know who the user is — at registration and at login. Calling it again with the same ID is a no-op, so you can call it on every app start without guarding.

This is the second mistake. Do not call setUserID for anonymous visitors. If you skip it entirely, the SDK creates its own anonymous user with a random ID and caches it, so the same person keeps the same identity across sessions. Inventing your own placeholder ID for signed-out users defeats that and produces a new "user" every time your placeholder changes.

addUserProperty attaches arbitrary key-value metadata — plan tier, team, beta cohort. Reapplying a key overwrites it:

kotlin
Fedo.addUserProperty("tier", "gold")
Fedo.addUserProperty("tier", "silver") // overrides the line before

You do not need to attach device metadata yourself. The SDK collects it during initialization and refreshes it each session: manufacturer, brand, model, device name, OS version, SDK int, screen width and height, density, locale, timezone, and app version name and code. They arrive as properties prefixed with an underscore (_model, _osVersion, _appVersionName) and cannot be disabled. Full table in the user management docs.

There is a logout() too. It clears the cached token, ID, name, email, and properties, then immediately creates a fresh anonymous user. On an already-anonymous user it does nothing.

4. Put the screen somewhere

FeedbackRoute.kt
import androidx.compose.runtime.Composable
import com.fedo.sdk.ui.FeedbacksScreen

@Composable
fun FeedbackRoute(navController: NavController) {
    FeedbacksScreen(
        onDismiss = { navController.popBackStack() }
    )
}

The signature is small:

kotlin
@Composable
fun FeedbacksScreen(
    modifier: Modifier = Modifier,
    onDismiss: () -> Unit,
)

What you get for that is a complete board with its own internal navigation across three screens. The list has All and Mine tabs, pull-to-refresh, vote buttons per item, and a FAB to create feedback. The details screen has the full description, threaded comments, voting, replies, and edit or delete of the user's own feedback when its status allows it. The editor creates and edits, pre-populating fields and validating before submit. Loading, error, and empty states are handled internally — there is no extra UI to build.

This is the third mistake. Back navigation inside the composable is automatic: editor returns to details or list, details returns to list. onDismiss fires only when the user presses back on the root list screen. If you leave it empty, the board traps the user on its first screen and the back button reads as broken. Pop your own stack there.

To adjust the chrome, pass a config block. Today it controls the app bar's back icon — null removes it, which is what you want when the board is a tab rather than a pushed screen:

kotlin
FeedbacksScreen(
    // ...
    config = { backButtonIcon = null }
)

More slots are planned. The current set is on the UI configuration page.

What the SDK does not do yet

Worth knowing before you ship rather than after:

  • No retry, no offline support. The identity methods run through an internal serial queue, so you can call them synchronously and in any order without racing. But a failed call is skipped, not retried, and the queue moves on. If setUserID fails, you call it again. Offline support is planned, not present.
  • Android only. iOS, Flutter, and React Native are in development. The dashboard is shared, so nothing you configure now is Android-specific.
  • Anonymous-to-authenticated migration is one-way. Covered in its own post — the short version is that calling setUserID on an anonymous user moves their feedback, comments, and votes onto the authenticated account and then deletes the anonymous one.

The whole thing

kotlin
// 1. build.gradle.kts
implementation("com.getfedo:sdk-android:0.3.0")

// 2. Application.onCreate
Fedo.initialize(apiKey = "your-api-key-here")

// 3. after login (optional)
Fedo.setUserID(user.id)
Fedo.setUserEmail(user.email)

// 4. anywhere in your Compose tree
FeedbacksScreen(onDismiss = { navController.popBackStack() })

Four calls, one of which is a build file. The getting started guide has the same material in reference form, and the API reference is generated from KDoc if you want the full surface.

AndroidKotlinJetpack ComposeSDKIntegration

Ship feedback that listens.

Start free with 1 board, upgrade whenever you need more.

See pricing