Skip to content

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.

iOS Android Web Flutter 3.10+ GitHub
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.

terminal
# 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.

main.dart
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

config.dart
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.

provider.dart
// 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
feedback_api.dart
// 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
votes_api.dart
// 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
comments_api.dart
// 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
users_api.dart
// 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.
events_api.dart
// 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.

theme.dart
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.

metrics.dart
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
error_handling.dart
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}');
}