Kotlin SDK
The FeedbackKit Kotlin SDK adds a feedback board, submissions and voting to Android apps, with Jetpack Compose components and coroutine-based API calls. Add the Gradle dependency, configure it with your project API key, and show the list and submit composables.
Installation
Gradle (Kotlin DSL)
Add the artifact from Maven Central to your module-level build.gradle.kts. These lines install version 1.0.1; see Versions above for what this page covers.
repositories {
mavenCentral()
}
dependencies {
implementation("com.getfeedbackkit:feedbackkit:1.0.1")
}
Gradle (Groovy)
The same coordinates in a Groovy build.gradle:
repositories {
mavenCentral()
}
dependencies {
implementation 'com.getfeedbackkit:feedbackkit:1.0.1'
}
Quick Start
Configure FeedbackKit once, from your Application, then display the feedback list. The lambda below is FeedbackKitConfig.Builder, so the options are method calls. The other overload, configure(context) { apiKey = "…" }, assigns properties and needs import com.swiftlydeveloped.feedbackkit.configure. A second configure in the same process returns the first instance.
import com.swiftlydeveloped.feedbackkit.*
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
FeedbackKit.configure(this, "your-api-key") {
userId("user_12345")
environment(Environment.PRODUCTION)
}
setContent {
FeedbackList(
onFeedbackClick = { feedback ->
// Navigate to detail
}
)
}
}
}
On 2.1.0, omit userId and configure stores local_ plus a lowercase UUID in private SharedPreferences (feedbackkit_user_id). List, submit, vote, unvote, and comment send that id. Maven 1.0.1 does not generate it, and a vote with no id throws a validation error. Assign FeedbackKit.shared.userId to replace it. logout() clears SharedPreferences and DataStore. The next process start generates a new id only when both stores are empty.
Configuration
The FeedbackKitConfig class accepts the following options:
| Option | Type | Default | Description |
|---|---|---|---|
apiKey |
String | Required | Project API key |
baseUrl |
String | Production URL | API origin. The DSL sets this from environment. |
userId |
String? | null | Optional. On 2.1.0, a null value becomes a persisted local_ id. |
timeout |
Long | 30000 | Request timeout in milliseconds |
debug |
Boolean | false | Enable debug logging |
Environment
The Environment enum controls the target server:
| Value | Description |
|---|---|
PRODUCTION |
Production server (default) |
STAGING |
Staging environment |
LOCAL |
Emulator loopback, http://10.0.2.2:8080 |
LOCAL_DEVICE |
http://localhost:8080 |
Builder Pattern
You can configure FeedbackKit using the builder pattern or pass a config object directly:
// Option 1: Builder pattern with trailing lambda
FeedbackKit.configure(context, "your-api-key") {
userId("user_12345")
environment(Environment.PRODUCTION)
timeout(60000L)
debug(true)
}
// Option 2: Config object. FeedbackKitConfig has baseUrl, not environment.
val config = FeedbackKitConfig(
apiKey = "your-api-key",
userId = "user_12345",
baseUrl = Environment.PRODUCTION.baseUrl,
debug = true
)
FeedbackKit.configure(context, config)
// Access the shared singleton
val kit = FeedbackKit.shared
API Reference
All API methods are suspend functions designed for use with Kotlin coroutines. Access them via FeedbackKit.shared.
Feedback API
Access via FeedbackKit.shared.feedback:
| Method | Description |
|---|---|
suspend fun list(options: ListFeedbackOptions = ListFeedbackOptions()): List<Feedback> |
List all feedback with optional filters |
suspend fun get(id: String): Feedback |
Get a single feedback by ID |
suspend fun create(request: CreateFeedbackRequest): Feedback |
Submit new feedback |
// List all feedback
val feedbacks = FeedbackKit.shared.feedback.list()
// List with filters
val filtered = FeedbackKit.shared.feedback.list(
ListFeedbackOptions(
status = FeedbackStatus.APPROVED,
category = FeedbackCategory.FEATURE_REQUEST
)
)
// Get single feedback
val feedback = FeedbackKit.shared.feedback.get("feedback-id")
// Create feedback
val newFeedback = FeedbackKit.shared.feedback.create(
CreateFeedbackRequest(
title = "Add dark mode",
description = "Would love dark mode support",
category = FeedbackCategory.FEATURE_REQUEST
)
)
Votes API
Access via FeedbackKit.shared.votes:
| Method | Description |
|---|---|
suspend fun vote(feedbackId, notifyOnStatusChange = false, …, email = null, notifyStatusChange = null) |
Pass email and notifyStatusChange by name. The second positional argument is the older notifyOnStatusChange Boolean, not the email. The user id comes from configure. 1.0.1 has no email parameter. |
suspend fun unvote(feedbackId: String): VoteResponse |
Remove the configured user's vote. 2.1.0 sends user_id in the DELETE body. 1.0.1 put it in the query string, which the server does not read, so that call returns 400. |
// Vote on feedback
val result = FeedbackKit.shared.votes.vote(
feedbackId = "feedback-id",
email = "user@example.com",
notifyStatusChange = true
)
// Remove vote
val unvoteResult = FeedbackKit.shared.votes.unvote("feedback-id")
Comments API
Access via FeedbackKit.shared.comments:
| Method | Description |
|---|---|
suspend fun list(feedbackId: String, page: Int? = null, perPage: Int? = null): List<Comment> |
List comments for a feedback item. page and perPage are optional. |
suspend fun create(feedbackId: String, request: CreateCommentRequest): Comment |
Add a comment to feedback |
// List comments
val comments = FeedbackKit.shared.comments.list("feedback-id")
// Add a comment
val comment = FeedbackKit.shared.comments.create(
feedbackId = "feedback-id",
request = CreateCommentRequest(
content = "Great idea! We'll look into this.",
userId = "user_12345"
)
)
Users API
Access via FeedbackKit.shared.users:
| Method | Description |
|---|---|
suspend fun register(email = null, name = null, externalId = null, metadata = null, userId = null, mrr = null) |
Pass userId and mrr by name. The four leading parameters still compile and are not serialized. The body is user_id and an optional mrr. Keep SdkUser.userId, not the row id. Maven 1.0.1 has no userId or mrr parameter. |
// Register a user
val user = FeedbackKit.shared.users.register(
userId = "user_12345",
mrr = 9.99
)
FeedbackKit.shared.userId = user.userId
Events API
Access via FeedbackKit.shared.events:
| Method | Description |
|---|---|
suspend fun track(name: String, properties: Map<String, String>? = null): TrackEventResponse |
Track an event. The configured user id is attached. PredefinedEventNames holds the three built-in names. |
// Track an event
val event = FeedbackKit.shared.events.track(
name = "feedback_viewed",
properties = mapOf(
"screen" to "detail",
"feedback_id" to "abc123"
)
)
FeedbackKit.shared.events.track(PredefinedEventNames.FEEDBACK_LIST)
Composables
Pre-built Jetpack Compose UI components for common feedback patterns. All composables support Material 3 theming.
FeedbackList
A scrollable list. Filter with statusFilter and categoryFilter, or pass a FeedbackListState. There is no search field, no sort control, and no pull-to-refresh gesture. Call state.refresh() yourself. isRefreshing drives the spinner.
| Parameter | Type | Description |
|---|---|---|
onFeedbackClick |
(Feedback) -> Unit | Callback when a feedback item is tapped |
modifier |
Modifier | Compose modifier for layout customization |
FeedbackDetailView
Shows full feedback details including description, votes, comments, and status.
| Parameter | Type | Description |
|---|---|---|
feedback |
Feedback | The item to display. Not an id. |
onBack |
() -> Unit | Back button |
onVoteChange |
(VoteResponse) -> Unit | After a vote or unvote |
modifier |
Modifier | Compose modifier for layout customization |
SubmitFeedbackView
A form for submitting new feedback with title, description, and category picker.
| Parameter | Type | Description |
|---|---|---|
onSubmitSuccess |
(Feedback) -> Unit | Called with the created item |
onBack |
() -> Unit | Back button |
showAppBar |
Boolean | Default true. Set false inside a sheet that already has chrome. |
modifier |
Modifier | Compose modifier for layout customization |
FeedbackCard
A single feedback item card with title, status, vote count, and category.
| Parameter | Type | Description |
|---|---|---|
feedback |
Feedback | The feedback data to display |
onClick |
() -> Unit | Callback when the card is tapped |
modifier |
Modifier | Compose modifier for layout customization |
VoteButton
A toggle button for voting/unvoting on feedback items.
| Parameter | Type | Description |
|---|---|---|
feedback |
Feedback | The feedback item to vote on |
onVoteChange |
(VoteResponse) -> Unit | Called with the server vote payload. The button does not collect an email or a status-change opt-in. |
modifier |
Modifier | Compose modifier for layout customization |
StatusBadge
Displays a colored badge for a feedback status.
| Parameter | Type | Description |
|---|---|---|
status |
FeedbackStatus | The status to display |
modifier |
Modifier | Compose modifier for layout customization |
CategoryBadge
Displays a badge for a feedback category.
| Parameter | Type | Description |
|---|---|---|
category |
FeedbackCategory | The category to display |
modifier |
Modifier | Compose modifier for layout customization |
Usage Example
@Composable
fun FeedbackScreen() {
var selectedFeedback by remember { mutableStateOf<Feedback?>(null) }
var showSubmit by remember { mutableStateOf(false) }
if (showSubmit) {
SubmitFeedbackView(
onSubmitSuccess = { feedback ->
showSubmit = false
},
onBack = { showSubmit = false }
)
} else if (selectedFeedback != null) {
FeedbackDetailView(
feedback = selectedFeedback!!,
onBack = { selectedFeedback = null }
)
} else {
FeedbackList(
onFeedbackClick = { feedback ->
selectedFeedback = feedback
}
)
}
}
Models
Core data classes used throughout the SDK.
Feedback
The primary model representing a feedback item.
| Field | Type | Description |
|---|---|---|
id |
String | Unique identifier |
title |
String | Feedback title |
description |
String | Detailed description |
status |
FeedbackStatus | Current status |
category |
FeedbackCategory | Feedback category |
voteCount |
Int | Total number of votes |
hasVoted |
Boolean | Whether the current user has voted |
commentCount |
Int | Number of comments |
createdAt |
String | ISO 8601 creation timestamp |
updatedAt |
String? | ISO 8601 update timestamp, when the server sent one |
userId |
String? | Creator's user ID |
email |
String? | Creator's email. Decoded from user_email. |
isHiddenOverLimit |
Boolean? | Set when a Free-plan item was stored past the visible cap. List reads omit those rows. |
Helper Methods
| Method | Returns | Description |
|---|---|---|
canVote |
Boolean | Whether voting is allowed based on status |
withVote() |
Feedback | withVote(hasVoted, voteCount) copies those two fields |
withCommentCount() |
Feedback | withCommentCount(count) sets the count |
withStatus() |
Feedback | Returns copy with updated status |
FeedbackStatus
Enum representing the lifecycle status of a feedback item.
| Value | Display Name | Can Vote | JSON Value |
|---|---|---|---|
PENDING |
Pending | Yes | pending |
APPROVED |
Approved | Yes | approved |
IN_PROGRESS |
In Progress | Yes | in_progress |
TESTFLIGHT |
TestFlight | Yes | testflight |
COMPLETED |
Completed | No | completed |
REJECTED |
Rejected | No | rejected |
Properties: canVote: Boolean, displayName: String, jsonValue: String
FeedbackCategory
Enum representing the type of feedback.
| Value | Display Name | JSON Value |
|---|---|---|
FEATURE_REQUEST |
Feature Request | feature_request |
BUG_REPORT |
Bug Report | bug_report |
IMPROVEMENT |
Improvement | improvement |
OTHER |
Other | other |
Properties: displayName: String, jsonValue: String
Comment
Represents a comment on a feedback item.
| Field | Type | Description |
|---|---|---|
id |
String | Unique identifier |
content |
String | Comment text content |
userId |
String? | Author's user ID |
userName |
String? | Display name, from user_name |
isOfficial |
Boolean | Admin comment. The wire key is is_admin. The property is not named isAdmin. |
createdAt |
String | ISO 8601 creation timestamp |
VoteResponse
Returned after voting or unvoting on a feedback item.
| Field | Type | Description |
|---|---|---|
feedbackId |
String? | The feedback that was voted on, when the payload includes it |
success |
Boolean | Defaults to true. The server has never sent this field. A 2xx response is the success signal. |
voteCount |
Int | Updated total vote count |
hasVoted |
Boolean | Current user's vote state |
Other Models
SdkUser
userId is the id you registered. id is the row id. Optional mrr.
RegisterUserRequest
Sent fields are userId and optional mrr. Email, name, and external id are ignored.
TrackedEvent
Represents a tracked analytics event.
CreateFeedbackRequest
Request body with title, description, and category.
CreateCommentRequest
Request body with content and userId.
ListFeedbackOptions
Filter options for listing feedback (status, category).
Error Handling
The SDK uses a sealed class hierarchy for type-safe error handling. All API errors extend FeedbackKitError.
| Error | Description |
|---|---|
FeedbackKitError |
Base sealed class for all SDK errors |
ValidationError |
Invalid request data (400) |
AuthenticationError |
Invalid or missing API key (401) |
PaymentRequiredError |
Subscription tier limit exceeded (402) |
ForbiddenError |
Insufficient permissions (403) |
NotFoundError |
Resource not found (404) |
ConflictError |
Resource conflict, e.g. duplicate vote (409) |
NetworkError |
Network connectivity issue |
ServerError |
Unexpected server error (5xx). statusCode is a property, not a constructor you have to pass. |
RateLimitError |
429. The SDK does not retry. isRecoverable is true. |
UnknownError |
Anything else, including HTTP 422. statusCode is kept when the transport had one. |
Usage with Coroutines
Catch FeedbackKitError and switch on the subclass. Branch on e.code for a server token. The Free plan's 10-item feedback cap is not a 402: create succeeds and later lists omit the hidden rows. An archived project's writes are ForbiddenError.
import kotlinx.coroutines.*
fun loadFeedback() {
viewModelScope.launch {
try {
val feedbacks = FeedbackKit.shared.feedback.list()
// Update UI with feedbacks
} catch (e: FeedbackKitError) {
when (e) {
is FeedbackKitError.AuthenticationError -> {
// Invalid API key
showError("Authentication failed")
}
is FeedbackKitError.PaymentRequiredError -> {
showUpgradePrompt()
}
is FeedbackKitError.RateLimitError -> {
showError("Too many requests")
}
is FeedbackKitError.NetworkError -> {
// No internet connection
showError("Check your connection")
}
is FeedbackKitError.ServerError -> {
// Server-side issue
showError("Server error: ${e.statusCode}")
}
else -> {
showError(e.message ?: "Unknown error")
}
}
}
}
}
Guides
How to Add a Feedback Board to an Android App with Kotlin
Add a feedback board to an Android app with the Kotlin SDK 1.0.1 from Maven Central: Gradle setup, user ids, Compose views, filters, theming and known gaps.
12 min read
What Is a Feedback SDK? A Practical Guide for App Teams
A feedback SDK lets users send, vote on and track feature requests inside your app. How it differs from surveys, support desks and analytics.
9 min read