Android-native SDK with Jetpack Compose UI components, Material 3 theming, and Kotlin coroutines for async operations. Add feedback collection to your Android app in minutes.
Add the dependency to your module-level build.gradle.kts:
dependencies {
implementation("com.swiftlydeveloped:feedbackkit:1.0.1")
}
Alternatively, if using Groovy-based Gradle:
implementation 'com.swiftlydeveloped:feedbackkit:1.0.1'
Configure FeedbackKit in your main activity and display the feedback list with Jetpack Compose:
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
}
)
}
}
}
The FeedbackKitConfig class accepts the following options:
| Option | Type | Default | Description |
|---|---|---|---|
apiKey |
String | Required | Project API key |
userId |
String? | null | User identifier |
timeout |
Long | 30000 | Request timeout in milliseconds |
debug |
Boolean | false | Enable debug logging |
The Environment enum controls the target server:
| Value | Description |
|---|---|
PRODUCTION |
Production server (default) |
STAGING |
Staging environment |
LOCAL |
Local development (localhost) |
LOCAL_DEVICE |
Local development from physical device |
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
val config = FeedbackKitConfig(
apiKey = "your-api-key",
userId = "user_12345",
environment = Environment.PRODUCTION,
debug = true
)
FeedbackKit.configure(context, config)
// Access the shared singleton
val kit = FeedbackKit.shared
All API methods are suspend functions designed for use with Kotlin coroutines. Access them via FeedbackKit.shared.
Access via FeedbackKit.shared.feedback:
| Method | Description |
|---|---|
suspend fun list(options: ListFeedbackOptions? = null): List<Feedback> |
List all feedback with optional filters |
suspend fun get(feedbackId: 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
)
)
Access via FeedbackKit.shared.votes:
| Method | Description |
|---|---|
suspend fun vote(feedbackId: String, userId: String, email: String? = null, notifyStatusChange: Boolean = false): VoteResponse |
Vote on feedback |
suspend fun unvote(feedbackId: String, userId: String): VoteResponse |
Remove vote |
// Vote on feedback
val result = FeedbackKit.shared.votes.vote(
feedbackId = "feedback-id",
userId = "user_12345",
email = "user@example.com",
notifyStatusChange = true
)
// Remove vote
val unvoteResult = FeedbackKit.shared.votes.unvote(
feedbackId = "feedback-id",
userId = "user_12345"
)
Access via FeedbackKit.shared.comments:
| Method | Description |
|---|---|
suspend fun list(feedbackId: String): List<Comment> |
List comments for a feedback item |
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"
)
)
Access via FeedbackKit.shared.users:
| Method | Description |
|---|---|
suspend fun register(request: RegisterUserRequest): SDKUser |
Register an SDK user |
// Register a user
val user = FeedbackKit.shared.users.register(
RegisterUserRequest(
userId = "user_12345",
email = "user@example.com"
)
)
Access via FeedbackKit.shared.events:
| Method | Description |
|---|---|
suspend fun track(eventName: String, userId: String, properties: Map<String, String>? = null): TrackedEvent |
Track a custom event |
// Track an event
val event = FeedbackKit.shared.events.track(
eventName = "feedback_viewed",
userId = "user_12345",
properties = mapOf(
"screen" to "detail",
"feedback_id" to "abc123"
)
)
Pre-built Jetpack Compose UI components for common feedback patterns. All composables support Material 3 theming.
Displays a scrollable list of feedback items with search, filtering, and sorting.
| Parameter | Type | Description |
|---|---|---|
onFeedbackClick |
(Feedback) -> Unit | Callback when a feedback item is tapped |
modifier |
Modifier | Compose modifier for layout customization |
Shows full feedback details including description, votes, comments, and status.
| Parameter | Type | Description |
|---|---|---|
feedbackId |
String | The ID of the feedback to display |
modifier |
Modifier | Compose modifier for layout customization |
A form for submitting new feedback with title, description, and category picker.
| Parameter | Type | Description |
|---|---|---|
onSuccess |
(Feedback) -> Unit | Called after successful submission |
onDismiss |
() -> Unit | Called when the form is dismissed |
modifier |
Modifier | Compose modifier for layout customization |
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 |
A toggle button for voting/unvoting on feedback items.
| Parameter | Type | Description |
|---|---|---|
feedback |
Feedback | The feedback item to vote on |
onVoteChange |
(Boolean) -> Unit | Called when vote state changes |
modifier |
Modifier | Compose modifier for layout customization |
Displays a colored badge for a feedback status.
| Parameter | Type | Description |
|---|---|---|
status |
FeedbackStatus | The status to display |
modifier |
Modifier | Compose modifier for layout customization |
Displays a badge for a feedback category.
| Parameter | Type | Description |
|---|---|---|
category |
FeedbackCategory | The category to display |
modifier |
Modifier | Compose modifier for layout customization |
@Composable
fun FeedbackScreen() {
var selectedFeedback by remember { mutableStateOf<Feedback?>(null) }
var showSubmit by remember { mutableStateOf(false) }
if (showSubmit) {
SubmitFeedbackView(
onSuccess = { feedback ->
showSubmit = false
// Handle new feedback
},
onDismiss = { showSubmit = false }
)
} else if (selectedFeedback != null) {
FeedbackDetailView(
feedbackId = selectedFeedback!!.id
)
} else {
FeedbackList(
onFeedbackClick = { feedback ->
selectedFeedback = feedback
}
)
}
}
Core data classes used throughout the SDK.
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 |
userId |
String? | Creator's user ID |
email |
String? | Creator's email address |
| Method | Returns | Description |
|---|---|---|
canVote |
Boolean | Whether voting is allowed based on status |
withVote() |
Feedback | Returns copy with vote toggled |
withCommentCount() |
Feedback | Returns copy with incremented comment count |
withStatus() |
Feedback | Returns copy with updated status |
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
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
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 |
isAdmin |
Boolean | Whether the author is a project admin |
createdAt |
String | ISO 8601 creation timestamp |
Returned after voting or unvoting on a feedback item.
| Field | Type | Description |
|---|---|---|
feedbackId |
String | The feedback that was voted on |
voteCount |
Int | Updated total vote count |
hasVoted |
Boolean | Current user's vote state |
Registered SDK user with userId and email.
Request body for registering a new user.
Represents a tracked analytics event.
Request body with title, description, and category.
Request body with content and userId.
Filter options for listing feedback (status, category).
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(statusCode: Int) |
Unexpected server error (5xx) |
Use standard Kotlin try/catch with when expressions for exhaustive error handling:
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 -> {
// Subscription limit reached
showUpgradePrompt()
}
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")
}
}
}
}
}