Flutter SDK
The FeedbackKit Flutter SDK adds feedback list, submit and voting widgets to Flutter apps. Add the package, wrap your app in FeedbackKitProvider with your project API key, and place the widgets where your users look for them.
feedbackkit_flutter:
git:
url: https://github.com/Swiftly-Developed/SwiftlyFeedbackKit-Flutter-SDK.git
ref: 2.1.0
Installation
This page documents package version 2.1.0. flutter pub add feedbackkit_flutter resolves pub.dev 1.0.1, which does not match the snippets below. 2.1.0 is not on pub.dev, so depend on the public repository's 2.1.0 tag with a git dependency. Use the placeholder your-api-key. Never commit a real project key. The published 1.0.1 example contains a real key; rotate any copy of it.
# pub.dev 1.0.1 is not this API. Depend on tag 2.1.0.
dependencies:
feedbackkit_flutter:
git:
url: https://github.com/Swiftly-Developed/SwiftlyFeedbackKit-Flutter-SDK.git
ref: 2.1.0
# Requires Flutter 3.10+ and Dart 3.0+.
Quick Start
Wrap your app with FeedbackKitProvider and start using the pre-built widgets.
import 'package:flutter/material.dart';
import 'package:feedbackkit_flutter/feedbackkit_flutter.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FeedbackKitProvider(
apiKey: 'your-api-key',
userId: 'user_12345',
child: MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Feedback')),
body: FeedbackList(),
),
),
);
}
}
Configuration
FeedbackKit.configure builds a client from FeedbackKitConfig. Widgets usually take the same values on FeedbackKitProvider, which also accepts theme, locale, translations, and storage. The provider does not take a prebuilt client, and it is not the provider package.
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
| apiKey | String | Yes | — | Your project API key |
| baseUrl | String | No | production API | HTTPS required. Loopback HTTP is allowed for a local server. |
| userId | String? | No | — | Explicit id wins and is stored. Otherwise the stored id, otherwise local_ plus a UUID. |
| timeout | int | No | 30000 | Request timeout in ms |
configure does not create an anonymous id. The provider resolves one before the list loads. setUserId replaces and persists the id. clearStorage drops it. votes.vote, unvote, and events.track throw ValidationError when the id is still empty. On pub.dev 1.0.1 a stored id overwrites an explicit one, and the list can load before any id exists.
Factory Configuration
final client = FeedbackKit.configure(
apiKey: 'your-api-key',
baseUrl: 'https://api.prod.getfeedbackkit.com/api/v1',
userId: 'user_12345',
timeout: 10000,
);
Provider
The FeedbackKitProvider widget wraps your app and makes the SDK client available throughout your widget tree.
// of() returns the provider context; the HTTP client is .client
final fk = FeedbackKitProvider.of(context);
// Use the client to make API calls
final feedbacks = await fk.client.feedback.list();
API Reference
Feedback API
client.feedback
| Method | Description |
|---|---|
| list([ListFeedbackOptions?]): Future<List<FeedbackItem>> | List feedback. Optional status, category, page, and perPage filters. Unknown status or category rows are dropped. |
| get(String id): Future<FeedbackItem> | Get single feedback by ID |
| create(CreateFeedbackRequest): Future<FeedbackItem> | Submit new feedback |
// Get all feedback
final all = await client.feedback.list();
// Get single feedback
final feedback = await client.feedback.get('feedback-uuid');
print('${feedback.title}: ${feedback.voteCount}');
// Submit new feedback
final created = await client.feedback.create(
CreateFeedbackRequest(
title: 'Add dark mode',
description: 'Please add a dark mode option.',
category: FeedbackCategory.featureRequest,
email: 'person@example.com',
),
);
Votes API
client.votes
| Method | Description |
|---|---|
| vote(String feedbackId, {notifyStatusChange, email, ...}): Future<VoteResponse> | Vote. The server stores notifyStatusChange only together with email. Body keys are user_id, email, and notify_status_change. |
| unvote(String feedbackId): Future<VoteResponse> | Remove vote |
// Vote and ask for a status email. Both fields are required for the opt-in to stick.
final result = await client.votes.vote(
'feedback-id',
notifyStatusChange: true,
email: 'person@example.com',
);
// Remove a vote. The user id is a JSON body field, not a query parameter.
final removed = await client.votes.unvote('feedback-id');
Comments API
client.comments
| Method | Description |
|---|---|
| list(String feedbackId): Future<List<Comment>> | List comments for feedback |
| create(String feedbackId, CreateCommentRequest): Future<Comment> | Add comment |
// List all comments
final comments = await client.comments.list('feedback-id');
for (final comment in comments) {
print('${comment.userId}: ${comment.content}');
}
// Add a comment
final comment = await client.comments.create(
'feedback-id',
CreateCommentRequest(
content: 'This would be really helpful!',
userId: 'user_12345',
),
);
Users API
client.users
| Method | Description |
|---|---|
| register(RegisterUserRequest): Future<SDKUser> | Register SDK user |
// Register a user
final user = await client.users.register(
RegisterUserRequest(userId: 'user_12345', mrr: 9.99),
);
Events API
client.events
| Method | Description |
|---|---|
| track(TrackedEvent): Future<void> | Track an event. Also trackEvent(name, {properties}). There is no TrackEventRequest. Widgets do not call this. |
// The client adds user_id. The body key is event_name.
await client.events.track(
const TrackedEvent(
name: PredefinedEventNames.feedbackList,
properties: {'filter': 'feature_request'},
),
);
Widgets
Pre-built Flutter widgets. FeedbackList waits until a user id exists, does not page, and is a scrollable only after rows load, so a refresh indicator around an empty or loading list does not fire. Call feedbackList.refresh(). VoteButton toggles a vote and does not take email or notifyStatusChange; use client.votes.vote for the status email. Completed and rejected items disable the button. Widgets do not post MRR and do not track feedback_list, feedback_detail, or submit_feedback on their own. SubmitFeedbackView shows the mailing-list controls when the email field is non-empty. Locales: en, es, fr, de, ja, zh-Hans, pt-BR, ko, it, ar, ru. Arabic is right-to-left.
| Widget | Description |
|---|---|
| FeedbackList | Display list of all feedback items |
| FeedbackDetailView | Detailed view of a single feedback item |
| SubmitFeedbackView | Form for submitting new feedback |
| FeedbackCard | Card component for displaying feedback |
| VoteButton | Interactive vote button with count |
| StatusBadge | Colored badge for feedback status |
| CategoryBadge | Badge for feedback category |
Models
FeedbackItem
| 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 | bool | Whether the current user has voted |
| commentCount | int | Total number of comments |
| userId | String? | ID of the user who submitted, when the server sent one |
| userEmail | String? | Submitter email, when stored |
| isHiddenOverLimit | bool? | True when the Free cap hid the row from everyone except its creator |
| createdAt | DateTime | When the feedback was created |
| updatedAt | DateTime | When the feedback was last updated |
Enums
FeedbackStatus
| Value | Can Vote |
|---|---|
| pending | Yes |
| approved | Yes |
| inProgress | Yes |
| testflight | Yes |
| completed | No |
| rejected | No |
FeedbackCategory
| Value | Display Name |
|---|---|
| featureRequest | Feature Request |
| bugReport | Bug Report |
| improvement | Improvement |
| other | Other |
Theming
Customize the appearance of FeedbackKit widgets with FeedbackKitTheme.
final customTheme = FeedbackKitTheme(
primaryColor: Colors.blue,
backgroundColor: Colors.white,
cardBackgroundColor: Colors.grey.shade50,
textColor: Colors.black87,
borderRadius: 12.0,
);
// Use dark theme
final darkTheme = FeedbackKitTheme.dark;
// Apply to provider
FeedbackKitProvider(
apiKey: 'your-api-key',
theme: customTheme,
child: MyApp(),
);
Metrics
client.metrics is a MetricsApi with summary, timeseries, breakdown, and events. There is no FeedbackKit.metrics. The API key identifies the project. Pass days or dateFrom / dateTo. A window wider than the plan allows returns HTTP 200 and sets meta.retentionClamped. Read meta.retentionWindowDays. Windows are Free 30 days, Pro 90 days, and Team 365 days. Below Pro, a summary can return 200 with meta.eventScope of predefinedOnly. HTTP 402 with METRICS_CUSTOM_EVENTS_REQUIRE_PRO is only for dimension: eventName, a custom eventName filter, and the raw event feed. sdkUserCount, mrrSum, mrrAverage, and usersWithMrr need a bearer token and return 403 METRICS_MEASURE_REQUIRES_BEARER on an API key. Tracking itself works on every plan, including Free. The metrics route allows 60 requests per 60 seconds and this SDK does not retry. perPage is clamped to 1 through 200 on the client; the server returns 400 outside that range. MetricSeriesPoint.bucket is a string. pub.dev 1.0.1 has no metrics API.
final summary = await client.metrics.summary(
MetricSummaryOptions(
measures: [MetricMeasure.feedbackCount, MetricMeasure.eventCount],
days: 7,
),
);
Error Handling
The SDK provides a typed error hierarchy extending FeedbackKitError. code is the server token when the detailed envelope sent one, otherwise the class default. field is set only on that envelope. The Free plan's 10-feedback cap is not a 402: create returns 2xx and sets isHiddenOverLimit. The creator still sees their own hidden rows because the list sends X-User-Id. A duplicate vote is 409. Voting on completed or rejected feedback is 403. NetworkError has no HTTP status.
| Error Class | Status Code | Description |
|---|---|---|
| FeedbackKitError | — | Base error class |
| AuthenticationError | 401 | Invalid or missing API key |
| PaymentRequiredError | 402 | A paid gate, such as custom metrics. Not the Free feedback cap. |
| ForbiddenError | 403 | Action not allowed |
| NotFoundError | 404 | Resource not found |
| ConflictError | 409 | Duplicate action (already voted) |
| ValidationError | 400 | Request validation failed |
| NetworkError | none | No HTTP response. Timeout sets isTimeout. |
| RateLimitError | 429 | retryAfterSeconds when the server sent Retry-After. The SDK does not retry. |
| ServerError | 5xx | Server-side error |
try {
final feedback = await client.feedback.create(
CreateFeedbackRequest(
title: 'My feedback',
description: 'Details here',
category: FeedbackCategory.featureRequest,
),
);
} on AuthenticationError {
print('Invalid API key');
} on PaymentRequiredError {
print('Upgrade your plan to continue');
} on NotFoundError {
print('Resource not found');
} on NetworkError {
print('Check your internet connection');
} on FeedbackKitError catch (e) {
print('Error: ${e.message}');
}
Guides
How to Add a Feedback Board to a Flutter App
Wire a public feedback board into a Flutter app with feedbackkit_flutter 1.0.1 from pub.dev: install, provider, widgets, user ids, theming, tests, known gaps.
12 min read
How to Collect Feature Requests in a Flutter or React Native App
Collect, vote on and close the loop on feature requests in a Flutter or React Native app, step by step, with FeedbackKit's SDKs.
7 min read