Skip to content

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.

Android API 24+ Jetpack Compose GitHub
implementation("com.getfeedbackkit:feedbackkit:1.0.1")

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.

build.gradle.kts
repositories {
    mavenCentral()
}

dependencies {
    implementation("com.getfeedbackkit:feedbackkit:1.0.1")
}

Gradle (Groovy)

The same coordinates in a Groovy build.gradle:

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.

MainActivity.kt
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:

Configuration.kt
// 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
FeedbackExample.kt
// 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.
VotesExample.kt
// 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
CommentsExample.kt
// 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.
UsersExample.kt
// 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.
EventsExample.kt
// 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

ComposablesExample.kt
@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).

Metrics

FeedbackKit.shared.metrics exposes summary, timeseries, breakdown, and events. The API key identifies the project. Pass days or dateFrom / dateTo. A window wider than the plan allows returns HTTP 200 with meta.retentionClamped. Read retentionWindowDays from the response. The documented windows are Free 30 days, Pro 90 days, and Team 365 days. Maven Central's published artifact is still 1.0.1, which does not include these calls. They are in this 1.2.x checkout.

val summary = FeedbackKit.shared.metrics.summary(
    MetricSummaryOptions(
        measures = listOf(MetricMeasure.FEEDBACK_COUNT, MetricMeasure.EVENT_COUNT),
        days = 7
    )
)

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. Metrics are capped at 60 requests per 60 seconds. 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. METRICS_CUSTOM_EVENTS_REQUIRE_PRO is the 402 for a breakdown by event name, a custom event-name filter, or the raw event feed. An archived project's writes are ForbiddenError.

ErrorHandling.kt
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 -> {
                    if (e.code == "METRICS_CUSTOM_EVENTS_REQUIRE_PRO") {
                        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")
                }
            }
        }
    }
}