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:
feedbackjaron pub.dev - Min SDK: Dart 3.0 / Flutter 3.10
- Platforms: Android & iOS
Installation
Add to your pubspec.yaml:
dependencies:
feedbackjar: ^1.6.1 Then run:
flutter pub get Setup
Initialize once before use — call configure in main() before runApp:
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:
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:
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:
// 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).
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:
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:
await FeedbackJar.shared.submit(userText, properties: {'plan': 'pro'}); List feedback
Fetch the public feedback feed with optional pagination:
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):
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):
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:
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 */ }
} | Field | Meaning |
|---|---|
collectName | Org asks submitters for their name (“Ask for Name”) |
collectEmail | Org asks submitters for their email (“Ask for Email”) |
allowVotes | Guest upvoting is enabled for this project |
allowComments | Guest commenting is enabled for this project |
API reference
| Method | Description |
|---|---|
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 —
setIdentityattaches 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.