
Play reviews are not a backlog.
A user writes “add dark mode” as a 2-star. You cannot merge it with the eleven other people who asked. You cannot tell them you shipped. You cannot see that three of them are on the Pro plan.
The fix is a board inside the app: list, vote, comment, submit. One tap from Settings. Not a Chrome Custom Tab of your Canny page. Not a WebView that needs SSO before it renders.
This is the Android version. One Maven artifact, one init, one screen. Same widget ID as the SwiftUI board so iOS and Play share one vote count.
SDK: github.com/feedbackjar/kotlin-sdk
Min SDK: 21 (Android 5.0)
Artifact: com.feedbackjar:sdk:1.5.1
If you landed here because WishKit has no Android package: WishKit alternative for Android.
What you are adding
A native screen that can:
- List public requests
- Let a guest upvote without creating an account
- Open a thread and comment
- Submit a request with Android version, device model, screen size, app version, and locale attached
Prebuilt UI ships in two flavours: framework Views (no Compose, no Material pulled in) and a Compose board that only resolves if your app already uses Compose. You can also call submit / listFeedback / vote from your own layout.
Step 1 — Get a widget ID
- Start a 7-day trial (no card).
- Create a project.
- Copy the widget ID from the dashboard.
That ID is what the SDK sends on every request. Reuse it in the Swift package if you also ship iOS.
Step 2 — Add the dependency
Maven Central. No extra repo for most projects.
settings.gradle.kts:
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
} build.gradle.kts (app module):
dependencies {
implementation("com.feedbackjar:sdk:1.5.1")
} The AAR declares INTERNET. You do not add it to your manifest.
Step 3 — Init once
Do this before any screen that touches the board. Application.onCreate() is enough.
import com.feedbackjar.sdk.FeedbackJar
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
FeedbackJar.init(this, "your-widget-id")
}
} Register MyApp in the manifest if it is not already. Wrong widget ID = empty board or failed submits.
Step 4 — Present the board
Views (zero extra UI libraries)
import com.feedbackjar.sdk.ui.FeedbackJarActivity
import com.feedbackjar.sdk.ui.FeedbackJarView
// Full-screen activity shipped by the SDK
startActivity(FeedbackJarActivity.intent(this))
// Or embed
val board = FeedbackJarView(this)
board.setAccentColor(Color.parseColor("#e5484d"))
setContentView(board) If you embed FeedbackJarView, give it back-stack control:
override fun onBackPressed() {
if (!board.onBackPressed()) super.onBackPressed()
} Compose (only if the app already uses Compose)
The published AAR does not force Compose on you. The composable compiles only in a Compose app.
import androidx.compose.ui.graphics.Color
import com.feedbackjar.sdk.ui.compose.FeedbackJarBoard
FeedbackJarBoard(
accentColor = Color(0xFFE5484D),
boardId = "board-id" // optional
) The board follows light/dark, hides votes or comments when those are off in the dashboard, and never throws. Failures show the server message inline.
Step 5 — Put it where people will open it
Settings row. Default. Label it “Feature requests” or “Feedback.” Not “Ideas.”
// In your Settings list
row.setOnClickListener {
startActivity(FeedbackJarActivity.intent(this))
} Compose Settings:
ListItem(
headlineContent = { Text("Feature requests") },
leadingContent = { Icon(Icons.Outlined.Lightbulb, contentDescription = null) },
modifier = Modifier.clickable { showBoard = true }
) Tab only if feedback is a core loop. A fourth tab nobody asked for is clutter.
Do not hide it behind shake-only. Shake is for crashes. Feature requests need a row a human can find on purpose.
Step 6 — Tell the SDK who the user is
Anonymous voting works without this. Each install gets a random id in SharedPreferences (not advertising id, not SSAID). Reinstall or clear-data resets it.
If the user is signed in:
FeedbackJar.setIdentity(name = user.name, email = user.email) On logout:
FeedbackJar.clearIdentity() Attach plan or flavor when you submit from your own form:
lifecycleScope.launch {
FeedbackJar.submit(
userText,
properties = mapOf(
"plan" to "pro",
"flavor" to BuildConfig.FLAVOR
)
)
} Values: String, Number, Boolean. No nested maps. These merge into the auto-collected packageName, versionName, versionCode.
Callback form if you are not in a coroutine:
FeedbackJar.submit(userText) { result ->
result.onSuccess { response -> /* response.postId */ }
result.onFailure { error -> /* show error.text */ }
} Rate limit: 5 submits per 15 minutes per IP. Handle failure in the UI.
Step 7 — Test it like a user
- Run on an emulator or device.
- Seed two real requests in the dashboard first. An empty board looks broken.
- Open the board from Settings. Submit “test from emulator.”
- Confirm the post in the FeedbackJar dashboard. Check Android version, model, app version.
- Upvote from a second emulator (different install id). Count should move.
- Change status in the dashboard, reopen the board.
If submit fails: widget ID, network, or the rate limit. The UI prints the server text.
Docs: Android SDK.
Custom UI (only if you need it)
lifecycleScope.launch {
val page = FeedbackJar.listFeedback(limit = 20).getOrNull()
page?.posts?.forEach { post ->
// post.title, post.upvotes, post.status, post.hasVoted
}
FeedbackJar.vote(post.id)
FeedbackJar.unvote(post.id)
} Read dashboard flags before you draw fields:
val config = FeedbackJar.getConfig().getOrNull()
showNameField = config?.collectName == true
showEmailField = config?.collectEmail == true
showVoteButton = config?.allowVotes == true
showComments = config?.allowComments == true What this is not
- Not a Canny / Featurebase / Nolt WebView. Those need SSO or cookie hacks. Does Canny have a mobile SDK?
- Not WishKit. WishKit has no Android artifact. WishKit alternative
- Not Instabug. Shake-to-report is a crash tool. This is a voting board.
- Not a second backlog. Same widget ID on iOS.
After it is live
Connect MCP so Cursor / Claude Code can list top-voted posts, read OS + app version, mark Done, and notify voters.
A board that stays on Planned trains people to leave Play reviews instead.
Copy-paste checklist
- Trial account, widget ID copied
-
implementation("com.feedbackjar:sdk:1.5.1") -
FeedbackJar.init(this, widgetId)inApplication - Settings row →
FeedbackJarActivity.intent(this)orFeedbackJarBoard() -
setIdentityafter login - Two seed posts so the board is not empty
- One test submit, metadata visible in the dashboard
Start a 7-day trial — no card. Same widget ID works on Android and iOS.