Common Flows
Open the feedback modal, push the roadmap as a route, trigger Smart Review, build custom UI on top of the async API, and handle errors. Covers openFeedback, RequestListView, RoadmapView, FeedbackComposeView, SmartReview, programmatic submit / fetch / vote / comment, and FeddyError patterns.
Once the SDK is installed and configured with FeddyProvider mounted,
this page covers the five patterns most apps need: collecting feedback,
displaying the roadmap, triggering Smart Review, building custom UI on
top of the async API, and handling errors.
Submit feedback
Default — Feddy.openFeedback(...)
The bundled compose modal is fully localized in 7 languages (en / zh-Hans / zh-Hant / es / ja / de / fr, auto-picked from the device locale) and handles validation, board picking, async submit, and the success state itself.
ElevatedButton(
onPressed: () => Feddy.openFeedback(boardKey: 'features'),
child: const Text('Suggest a feature'),
)By default the modal shows the workspace's two system boards — Feature and Bug.
Custom workspace boards
The bundled views fetch the workspace's full board set from
GET /v1/boards (1 h cached), so any board you've added under
dashboard.feddy.app/w/<ws>/boards appears without redeploying. For
custom boards, supply per-locale display names via boardTranslations
so each device locale renders the right label:
Feddy.configure(
apiKey: 'fed_xxxxxxxxxxxx',
boardTranslations: const {
'roadmap-2026': {
'en': 'Roadmap 2026',
'ja': 'ロードマップ 2026',
'es': 'Hoja de ruta 2026',
},
'design': {'ja': 'デザインフィードバック'},
},
);Resolution order for any custom board key:
boardTranslations[key][deviceLocale]if set- The server's
board.name(whatever the admin typed in the dashboard) - Capitalized key as a last-ditch label
System keys (features / bugs) always use the SDK's bundled
translations.
Programmatic submit
If you have your own UI, call Feddy.submitRequest(...) directly. Like
all writes, it's synchronous, fire-and-forget, and never throws.
Feddy.submitRequest(title: 'Add dark mode');Feddy.submitRequest(
title: 'Crash on launch',
description: 'Happens after entering passcode on iPhone 15 Pro / iOS 17.4',
);Feddy.submitRequest(
title: 'Confusing onboarding step 3',
boardKey: 'ux-research',
);The two system boards every workspace ships with are 'features' and
'bugs'. Omit boardKey to land in the workspace's primary board.
Direct compose widget
Mount FeedbackComposeView yourself for embedded use cases — useful
when you want the compose form inside a custom container, a settings
page, or a bottom sheet you control:
showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
builder: (_) => const FeedbackComposeView(),
);Surface the roadmap
Default — RoadmapView and RequestListView
Both render as full screens. Push as a MaterialPageRoute from any
button:
Navigator.push(
context,
MaterialPageRoute<void>(builder: (_) => const RoadmapView()),
);Navigator.push(
context,
MaterialPageRoute<void>(builder: (_) => const RequestListView()),
);RequestListView shows all requests with a board picker.
RoadmapView renders three tabs (Planned / In Progress / Completed)
populated by GET /v1/requests?status=…. Detail screens have inline
comments and an upvote button.
Custom UI — async fetch
When the bundled widgets don't fit (custom theming, embedded list, custom empty state), build your own UI on top of the async read methods.
final page = await Feddy.fetchRequests(
boardKey: 'features',
status: 'planned',
limit: 20,
);
for (final item in page.items) {
print('${item.title} ${item.voteCount} ${item.attachments.length}');
}
// Single request detail (with attachments + official reply)
final detail = await Feddy.fetchRequest('req_xyz');Smart Review
Smart Review routes happy users to the App Store / Play Store and unhappy users to a private feedback form, so you stop seeing 1-star reviews from frustrated users who would have engaged constructively if asked.
Call Feddy.requestReviewIfAppropriate(...) from any "user just had a
good moment" hook — onboarding completed, save succeeded, level cleared:
Feddy.requestReviewIfAppropriate(trigger: 'task_50_complete');The SDK's built-in gates decide whether to actually present the prompt (≥7 days install age, ≥5 sessions, ≥90d cooldown, ≤3 prompts per 365d window). If they pass, a two-step sheet appears:
- Step one asks whether the user is enjoying the app.
- Step two confirms before invoking the system review prompt.
A negative answer in step one routes straight to the compose modal so the feedback is captured privately instead of as a public 1-star App Store review.
Vote and comment
All vote / comment methods are async and throw FeddyError on failure.
Toggle a vote
The server is the source of truth — upvote returns the post-toggle
state so you can reconcile against an optimistic UI update:
final state = await Feddy.upvote(requestId: 'req_xyz');
print('voted=${state.voted} total=${state.voteCount}');Read comments
Oldest-first, paginated:
final thread = await Feddy.fetchComments(
requestId: 'req_xyz',
limit: 50,
);Append a comment
final posted = await Feddy.addComment(
requestId: 'req_xyz',
body: 'Looking forward to this!',
);The returned posted is the new comment, so you can append to your
local thread without re-fetching.
Subscription state
Feddy.configure(...) enables automatic subscription detection by
default — no product IDs required:
- iOS: reads
SK2Transaction.transactions()(StoreKit 2) - Android: subscribes to Play Billing's
purchaseStreamand triggersrestorePurchases()
The detected snapshot is attached to the next Feddy.identify(...)
call. Call Feddy.refreshSubscription() after a purchase / restore to
re-read state. Pass autoDetectSubscription: false to disable.
Limitations:
- Android subscriptions surface no expiration timestamp
(
expiresAtis alwaysnull); Play Billing's client-side API does not expose it. - Trial / introductory offer detection is not performed; not-yet-expired
entitlements are reported as
active.
If your source-of-truth for paid state is RevenueCat, Adapty, or your own server, push the snapshot manually — manual overrides always win:
Feddy.setSubscription(const Subscription(
isPaid: true,
status: SubscriptionStatus.active,
productId: 'com.foo.pro_yearly',
expiresAt: '2027-01-01T00:00:00Z',
));
// Pass null to clear the override.
Feddy.setSubscription(null);Both manual and auto values persist across launches via
shared_preferences; the next Feddy.identify(...) call attaches
whichever takes precedence automatically.
Handle errors
Async read / vote / comment methods throw FeddyError. Two shapes:
sealed class FeddyError implements Exception {}
class FeddyNetworkError extends FeddyError {}
class FeddyHttpError extends FeddyError {
final int status;
final String code;
final String message;
}| Case | When | What to do |
|---|---|---|
FeddyNetworkError | DNS / TLS / offline / timeout | Show retry UI; the user's network is unhappy. |
FeddyHttpError | Server responded with 4xx / 5xx | Inspect code for typed handling. |
The code field is a stable string contract — values like
rate_limited, invalid_request, not_found. Match on it rather than
parsing message:
try {
final page = await Feddy.fetchRequests(boardKey: 'features');
// ...
} on FeddyNetworkError {
showOfflineToast();
} on FeddyHttpError catch (e) {
switch (e.code) {
case 'rate_limited':
scheduleRetry(const Duration(seconds: 30));
case 'invalid_request':
debugPrint('invalid request: ${e.message}');
default:
showGenericError(e.message);
}
}Offline behavior (writes)
Writes — submitRequest(...) — never throw. On network failure or 5xx
the payload is persisted to a local FIFO queue (via
shared_preferences) and replayed on the next Feddy.configure(...)
call. 4xx responses are dropped with a console log (replaying bad
payloads would loop forever).
The queue survives app restarts. The host app does not need to manage it.
When to choose programmatic over the bundled widgets
| Use case | Pick |
|---|---|
| Roadmap fits a full screen with default theming | RoadmapView |
| Mixed feedback list with a board picker | RequestListView |
| Embedded inside another screen / custom theme | Programmatic fetchRequests |
| Different empty state, hero, or onboarding | Programmatic |
| Showing votes / comments inline elsewhere | Programmatic |
| Minimal code, no design work | Bundled widgets |