Feature SDKs
How to collect in-app feedback in a Flutter app
Pro Reference for feedback_sdk: every variable, option and package it generates. Command: archipelago generate feedback_sdk
Generate the Feedback SDK, set the endpoint through a DI module, submit feedback and handle the three results, use the offline queue, and add feature voting.
What you'll learn
- Generating the Feedback SDK with optional feature voting
- Configuring the feedback endpoint through a DI module
- Submitting feedback and handling the three result types
- Using the offline queue and shake-to-feedback
- Letting users vote on feature requests
Prerequisites
- An existing Archipelago monorepo (see Monorepo Scaffolding)
- A Pro subscription. Run
archipelago auth statusto confirm your tier. - Connectivity SDK, recommended for the offline queue (see Connectivity SDK Setup)
The brick generates storage_sdk and package_info_provider for you if they are missing. You do not install them first.
Step 1: Generate the Feedback SDK
archipelago generate feedback_sdkThe prompts are:
- includeFeatureVoting:
trueturns on feature-request listing and voting (defaulttrue) - defaultEndpoint: your feedback API URL (default empty, set it later in DI)
Or use a config file:
{
"includeFeatureVoting": true,
"defaultEndpoint": ""
}archipelago generate feedback_sdk --config feedback_config.jsonStep 2: Understand the Generated Structure
features/feedback/
├── feedback_api/
│ └── lib/src/
│ ├── feedback_sdk.dart
│ └── models/ # Submission, category, config, results, feature request
└── feedback_impl/
└── lib/src/
├── feedback_sdk_impl.dart
├── data/ # Remote datasource and the local queue
└── di/The post-generation hook registers FeedbackImplPackageModule in the app injector. The SDK is a lazy singleton. Nothing runs at boot.
Step 3: Configure the Endpoint
The generated FeedbackRegisterModule provides a default FeedbackConfig from your prompts. To change it, provide your own config in a DI module:
@module
abstract class AppFeedbackModule {
@lazySingleton
FeedbackConfig get feedbackConfig => const FeedbackConfig(
endpoint: 'https://api.yourapp.com/feedback',
attachAppVersion: true,
attachDeviceInfo: true,
allowAnonymous: true,
queueLimit: 50,
featureVotingEnabled: true,
);
}Register this module before FeedbackImplPackageModule. The SDK uses its own Dio registered as @Named('feedback'), so it does not clash with the app Dio.
Step 4: Submit Feedback
Take FeedbackSDK by constructor and call submit():
final result = await _feedback.submit(
FeedbackSubmission(
text: 'The checkout button is hard to find',
category: FeedbackCategory.bug,
screenshotPath: screenshotPath,
),
);
switch (result) {
case FeedbackSubmitResultSubmitted():
showSnackbar('Thanks for your feedback.');
case FeedbackSubmitResultQueued():
showSnackbar('Saved. We will send it when you are online.');
case FeedbackSubmitResultFailed():
showSnackbar('We could not send your feedback.');
}FeedbackCategory has bug, suggestion, praise and other. Leave screenshotPath empty to send no attachment.
Step 5: Use the Offline Queue
The SDK queues submissions and votes in storage_sdk under the feedback namespace. It queues when the device is offline or the server returns a 5xx. A 4xx is a failure and is not queued.
Wire in connectivity so the queue drains when the network returns:
connectivitySDK.stateStream.listen((state) {
feedbackSDK.recordConnectivity(online: state.isOnline);
});The app flushes the queue. Nothing runs at boot. You can also use getQueuedCount(), flushQueue() and clearQueue().
Step 6: Shake-to-Feedback (Optional)
The shake package is not a brick dependency. Add it to your host app pubspec.yaml:
dependencies:
shake: ^2.2.0Start the detector after DI is ready:
ShakeDetector.autoStart(
onPhoneShake: () async {
final text = await promptUserForText();
await feedbackSDK.submit(
FeedbackSubmission(text: text, category: FeedbackCategory.other),
);
},
);promptUserForText() shows a dialog. Use the UI pattern of your app.
Step 7: Feature Voting
This works when featureVotingEnabled is true:
final requests = await feedbackSDK.getFeatureRequests(page: 1, pageSize: 20);
final result = await feedbackSDK.voteOnFeatureRequest(requests.first.id);
await feedbackSDK.submitFeatureRequest(title: 'Dark mode', description: '...');Votes are deduplicated on the device. A second vote for the same request returns FeatureRequestVoteResultAlreadyVoted with no network call.
switch (result) {
case FeatureRequestVoteResultRecorded():
showSnackbar('Thanks for your vote.');
case FeatureRequestVoteResultAlreadyVoted():
showSnackbar('You already voted for this.');
case FeatureRequestVoteResultFailed(:final reason):
showSnackbar('Could not vote: $reason');
}With voting off, getFeatureRequests returns an empty list. The vote and submit calls return a failed result with the reason featureVotingDisabled.
Common Customizations
| Customization | Where to Change |
|---|---|
| Endpoint and queue limit | FeedbackConfig in your DI module |
| Turn off version or device info | attachAppVersion and attachDeviceInfo |
| Rate-limit shake | Track the last submission time before submit() |
| Disable shake in release builds | Guard ShakeDetector.autoStart with !kReleaseMode |
Next Steps
- Set up the Home Widget to surface key info on the home screen
- Configure monitoring to track feedback submission failures