FeedbackJar

Flutter

The FeedbackJar Flutter SDK is a data layer plus an optional prebuilt board. Use just the data layer if you’re building your own UI, or drop in FeedbackJarBoard for a complete list → detail → new feedback flow with voting and comments already wired up.

  • Package: feedbackjar on pub.dev
  • Min SDK: Dart 3.0 / Flutter 3.10
  • Platforms: Android & iOS

Installation

Add to your pubspec.yaml:

yaml
dependencies:
  feedbackjar: ^1.6.1

Then run:

sh
flutter pub get

Setup

Initialize once before use — call configure in main() before runApp:

dart
import 'package:feedbackjar/feedbackjar.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  FeedbackJar.configure('your-widget-id');
  runApp(const MyApp());
}

Get your widget ID from Settings → Widgets in your FeedbackJar project.

Prebuilt board (optional)

Skip building your own screens — FeedbackJarBoard is a complete board built only on Flutter’s own Material widgets, no extra package dependency:

dart
import 'package:feedbackjar/feedbackjar.dart';

// Embed it
FeedbackJarBoard(accentColor: Color(0xFFE5484D)) // optional accent, defaults to FeedbackJar red

// Or push it as a full route
showFeedbackJar(context, accentColor: Color(0xFFE5484D), boardId: 'board_123');

Gives you list, upvote, detail, comments, and a new-feedback form, built only on the API methods below. Pass onClose to FeedbackJarBoard to show a “Close” affordance in the header.

Identify the submitter (optional)

Remember a name/email so later submit, vote, and addComment calls attribute to the same person, and so their name/email are synced to the server against this install’s anonymous id:

dart
await FeedbackJar.shared.setIdentity(name: user.name, email: user.email);
final identity = await FeedbackJar.shared.getIdentity();

// On logout:
await FeedbackJar.shared.clearIdentity();

Skip this and submissions stay fully anonymous — setIdentity is optional, not required.

Verified identity (signed-in users)

If your app has its own signed-in users, attribute their votes/comments to a real, verifiable account — consistent across every device they use — instead of the unverified name/email above. Your backend computes the signature:

dart
// Your backend, once the user is authenticated:
//   timestamp = DateTime.now().millisecondsSinceEpoch
//   signature = HMAC-SHA256(orgSecretKey, "$userId:$email:$timestamp")
// (org secret key is in the FeedbackJar dashboard)

await FeedbackJar.shared.setIdentity(
  userId: user.id,
  email: user.email,
  signature: signatureFromYourBackend,
  timestamp: timestampFromYourBackend,
);

Once set, vote, addComment, submit, and hasVoted all attach to this real user on every device — immediately, and any votes/comments already made anonymously on this device are folded in automatically. timestamp must be the exact value your backend signed, and expires after 7 days — call setIdentity again on each sign-in.

Submit feedback

Each submission automatically includes device metadata (OS version, device model, screen size, app version, locale).

dart
final result = await FeedbackJar.shared.submit(userText);

switch (result) {
  case FeedbackSuccess(:final value):
    print('Submitted: ${value.postId}');
  case FeedbackFailure(:final error):
    print('Failed: $error');
}

A callback variant is available when you are not in an async context:

dart
FeedbackJar.shared.submitCallback(userText, (result) {
  // handle result
});

Pass email/name for a headless submission where identity isn’t already known, and properties for your own key/value context merged into the auto-collected app metadata:

dart
await FeedbackJar.shared.submit(userText, properties: {'plan': 'pro'});

List feedback

Fetch the public feedback feed with optional pagination:

dart
final result = await FeedbackJar.shared.listFeedback(limit: 20);

if (result case FeedbackSuccess(:final value)) {
  for (final post in value.posts) {
    print('${post.title} — ${post.upvotes} upvotes');
  }
}

Pass boardId to filter by board, or cursor for pagination.

FeedbackJar.shared.getPost(postId) fetches a single public post the same way.

Voting

Guest upvoting as this install’s anonymous guest — idempotent, and gated by the project’s Allow guest votes setting (WidgetConfig.allowVotes, see Get widget config):

dart
await FeedbackJar.shared.vote(postId);                // upvote
await FeedbackJar.shared.unvote(postId);               // remove the upvote
final state = await FeedbackJar.shared.getVoteState(postId); // current count + hasVoted

Comments

Anonymous, two-level threads — gated by Allow guest comments (WidgetConfig.allowComments):

dart
final comments = await FeedbackJar.shared.listComments(postId, limit: 20);

// Reply to a post, or to a root comment via parentId (replies-to-replies aren't allowed)
await FeedbackJar.shared.addComment(postId, 'Same issue here!');

name/email fall back to the remembered identity (setIdentity) when omitted.

Get widget config

Check whether the org asks for name/email, and whether voting/comments are enabled, before building your own UI around them:

dart
final result = await FeedbackJar.shared.getConfig();
if (result case FeedbackSuccess(:final config)) {
  if (config.collectEmail) { /* show an email field */ }
  if (config.allowVotes) { /* show your vote button */ }
}
FieldMeaning
collectNameOrg asks submitters for their name (“Ask for Name”)
collectEmailOrg asks submitters for their email (“Ask for Email”)
allowVotesGuest upvoting is enabled for this project
allowCommentsGuest commenting is enabled for this project

API reference

MethodDescription
configure(widgetId)Configure the SDK. Call once before anything else (static).
shared.setIdentity({name?, email?})Remember a submitter’s name/email for later calls.
shared.setIdentity({userId, email, signature, timestamp, ...})Set a verified, org-signed identity — see Verified identity.
shared.getIdentity()The currently remembered identity, if any.
shared.clearIdentity()Forget the remembered identity (e.g. on logout).
shared.submit(content, {email?, name?, properties?})Submit feedback. Returns FeedbackResult<FeedbackResponse>.
shared.listFeedback({boardId?, limit?, cursor?})List public feedback. limit is clamped to 1–50.
shared.getPost(postId)Fetch a single public post.
shared.getConfig()Fetch this project’s WidgetConfig.
shared.vote(postId) / shared.unvote(postId)Cast or remove this install’s upvote. Idempotent.
shared.getVoteState(postId)Current upvote count and whether this install voted.
shared.listComments(postId, {limit?, cursor?})List a post’s comments (two levels).
shared.addComment(postId, content, {parentId?, name?, email?})Add a comment or reply.

Every method above has a *Callback variant (e.g. submitCallback, voteCallback) for use outside an async context. Results use Dart 3 pattern matching via a sealed FeedbackResult<T> class with FeedbackSuccess and FeedbackFailure subtypes.

Notes

  • Submission, voting, and comments are all anonymous by default — setIdentity attaches a name/email, it does not require a full account or portal login.
  • Voting and commenting only work when the project has them enabled — check getConfig() before showing that UI.
  • The server rate-limits submissions (5 per 15 minutes per IP).
  • Private boards and non-public posts are never returned by listFeedback.