gvl_comments 1.2.0
gvl_comments: ^1.2.0 copied to clipboard
Drop-in comments widget and comment section for Flutter, with threaded replies, reactions and moderation. Managed backend included, no Firebase setup, just an install key.
gvl_comments — Add comments to any Flutter app #
Drop-in comment section for Flutter: threaded replies, reactions, moderation and instant (optimistic) posting, with no backend to build. Powered by GoodVibesLab Cloud.
- Android & iOS
- Free plan: 1,000 comments/month, AI moderation included, no credit card (pricing)
- Data hosted in the EU (Stockholm)
Why gvl_comments? #
| What you get | What you skip |
|---|---|
| Comments UI (list + composer) | Designing a database schema |
| Threaded replies (depth 2) | Writing security rules / RLS |
| 6 emoji reactions | Building pagination & cursor logic |
| AI + user report moderation | Rate-limiting and abuse prevention |
| Cursor-based pagination | Token management |
| Material 3 theming (5 presets) | Maintaining backend infrastructure |
One install key, zero backend code.
Quick start #
1. Get an install key #
- Sign up at goodvibeslab.cloud (free) and create a project.
- In the project's Platforms card, register your app: Android Package name (
applicationId) and/or iOS Bundle ID. - Click Add key and copy the install key (
cmt_live_…).
Install keys only work for the apps registered on the project. Skipping step 2 makes the first token request fail with
api_key_not_bound_to_app.
2. Install #
# pubspec.yaml
dependencies:
gvl_comments: ^1.2.0
3. Initialize #
import 'package:gvl_comments/gvl_comments.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await CommentsKit.initialize(
installKey: const String.fromEnvironment('GVL_INSTALL_KEY'),
);
runApp(const MyApp());
}
flutter run --dart-define=GVL_INSTALL_KEY="cmt_live_xxx"
4. Add the widget #
CommentsList(
threadKey: 'post:550e8400-e29b-41d4-a716-446655440000',
user: UserProfile(id: 'user-1', name: 'Alice'),
)
That's it. Comments load, users post, reactions work — all out of the box.
Using your own auth #
Pass the signed-in user from whatever auth you already use. id must be stable for that user (it's how comments, reactions and reports are attributed):
// Firebase Auth
final u = FirebaseAuth.instance.currentUser!;
final user = UserProfile(id: u.uid, name: u.displayName, avatarUrl: u.photoURL);
// Supabase Auth
final s = Supabase.instance.client.auth.currentUser!;
final user = UserProfile(id: s.id, name: s.userMetadata?['name']);
Features #
CommentsList #
Full-featured comment thread with built-in composer, pagination, and optimistic posting.
CommentsList(
threadKey: 'article:01HV9ZJ7Q4X2M0YB8K9E',
user: currentUser,
newestAtBottom: false, // feed mode (default) or chat mode
limit: 30, // comments per page (1–100)
reactionsEnabled: true, // emoji reaction bar
shrinkWrap: true, // embed inside a parent scrollable
header: MyArticleCard(), // scrolls with comments
theme: GvlCommentsThemeData.bubble(context),
)
CommentCount #
Lightweight counter — no full list load.
CommentCount(
threadKey: 'post:550e8400-e29b-41d4-a716-446655440000',
user: currentUser,
builder: (context, count, refresh) => Text('$count comments'),
)
TopComment #
Display the most-engaged comment (highest reactions) as a preview.
TopComment(
threadKey: 'post:550e8400-e29b-41d4-a716-446655440000',
user: currentUser,
onTap: () => Navigator.push(/* full thread */),
)
Batch prefetch #
Avoid N+1 in lists — prefetch counts and top comments for multiple threads at once.
await CommentsKit.I().prefetchThreads(
posts.map((p) => 'post:${p.id}').toList(), // e.g. UUIDs from your DB
user: currentUser,
);
// CommentCount and TopComment now read from cache
Theming #
Five built-in presets, all Material 3 compatible:
GvlCommentsThemeData.defaults(context) // adapts to app theme
GvlCommentsThemeData.neutral(context) // minimal, clean
GvlCommentsThemeData.compact(context) // dense, for dashboards
GvlCommentsThemeData.card(context) // elevated cards
GvlCommentsThemeData.bubble(context) // chat-style bubbles
Full control via properties:
GvlCommentsThemeData(
bubbleColor: Colors.blue.shade50,
avatarSize: 32,
spacing: 12,
bubbleRadius: BorderRadius.circular(16),
authorStyle: TextStyle(fontWeight: FontWeight.bold),
)
Or use Theme.of(context).extension<GvlCommentsThemeData>() for app-wide styling.
Reactions #
Six reactions: like, love, laugh, wow, sad, angry.
- Tap to toggle the default reaction (like)
- Long-press to open the reaction picker
- Disable per widget:
reactionsEnabled: false
Moderation #
Comments pass through a moderation pipeline:
| Status | Behavior |
|---|---|
approved |
Visible normally |
pending |
Visible to author, placeholder for others when flagged |
rejected |
Replaced by "This comment has been moderated" |
- User reports — from the ⋯ menu on each comment, with a confirmation step; duplicate-safe
- AI moderation — automatic flagging, on by default on every plan (free included)
- Configure thresholds and sensitivity from the dashboard
Programmatic API #
Full control beyond the widget:
final kit = CommentsKit.I();
// Thread keys must be high-entropy (UUID/ULID/Firestore doc id, 20+ chars).
const threadKey = 'post:3f2b8c1e-9a4d-4e7f-b2c6-1d8e5a7f9b30';
// List with pagination
final page = await kit.listPage(threadKey, user: user);
final comments = page.items;
if (page.hasMore) {
final next = await kit.listPage(threadKey, user: user, cursor: page.nextCursor);
}
// Post
final comment = await kit.post(
threadKey: threadKey,
body: 'Hello!',
user: user,
parentId: parentComment.id, // optional, for replies
);
// React
await kit.setCommentReaction(
commentId: comment.id,
reaction: Reaction.love.id, // or null to remove
user: user,
);
// Report
final isDuplicate = await kit.report(commentId: id, user: user);
// User identity (tokens are per user: a user switch is detected
// automatically; call invalidateToken() on logout to drop cached data)
await kit.identify(newUser);
kit.invalidateToken();
Builder hooks #
Override any part of the UI:
| Builder | Controls |
|---|---|
commentItemBuilder |
Entire comment row |
avatarBuilder |
Avatar widget |
sendButtonBuilder |
Send button |
composerBuilder |
Full input area |
separatorBuilder |
Dividers between comments |
loadMoreButtonBuilder |
Pagination button |
Thread keys #
Thread keys identify comment threads. They must be:
- 20+ characters long
- High-entropy (UUID, ULID, Firestore doc ID)
- Characters:
a-zA-Z0-9:_-.
post:550e8400-e29b-41d4-a716-446655440000 ✅
article:01HV9ZJ7Q4X2M0YB8K9E ✅
post-123 ❌ guessable
No pre-creation needed — threads are resolved server-side on first use.
Webhooks #
Subscribe to events from the dashboard:
| Event | Trigger |
|---|---|
comment.created |
New comment posted |
comment.replied |
Reply to existing comment |
comment.liked |
Reaction added |
comment.mentioned |
User mentioned in flattened reply |
Payloads are signed with HMAC-SHA256. See the webhook docs for verification examples.
Security #
- Short-lived JWTs — tokens expire after 1 hour
- App binding — lock install keys to Android SHA-256 / iOS Team ID
- Rate limiting — per IP, per user, per thread
- Row-Level Security — tenant-isolated data, no cross-project access
- 15s request timeout — prevents indefinite hangs
Localization #
Register the SDK's localization delegates in your MaterialApp:
MaterialApp(
localizationsDelegates: GvlCommentsL10n.localizationsDelegates,
supportedLocales: GvlCommentsL10n.supportedLocales,
)
Ships with 5 locales: English, French, Spanish, German, and Portuguese. All UI strings (timestamps, errors, hints, reaction labels) go through the l10n system.
Logging #
await CommentsKit.initialize(
installKey: key,
logLevel: CommentsLogLevel.trace, // off | error | info | debug | trace
);
Defaults to error in release, debug in debug mode. Sensitive values (keys, tokens) are redacted.
FAQ #
Do I need my own backend or database? No. Comments, reactions, reports and moderation are stored and served by GoodVibesLab Cloud. You only ship the SDK and an install key.
Are new comments pushed in real time? No. Your own comments appear instantly (optimistic UI), and other people's comments show up on pull-to-refresh or when the thread is reopened.
Does it work offline? No. Loading and posting need a network connection. A failed post keeps the user's text and offers a retry.
Which platforms are supported? Android and iOS. Web and desktop are not supported yet.
Where is the data stored? In the EU (Stockholm). Each account's data is isolated by row-level security.
Can I restyle or replace the UI?
Yes: use a theme preset, GvlCommentsThemeData properties, or the builder hooks to replace any part (item, avatar, composer, send button…).
Requirements #
| Minimum | |
|---|---|
| Flutter | 3.19 |
| Dart | 3.3 |
| iOS | 13.0 |
| Android | API 24 |
Example app #
git clone https://github.com/GoodVibesLab/gvl_comments.git
cd gvl_comments/example
flutter run
Runs with a built-in demo key. Shows posting, reactions, theming, dark mode, and guest identity.
Links #
- Dashboard — create projects, install keys, configure moderation
- Documentation — full API reference and guides
- Issues — bug reports and feature requests
- Contact — support
License #
Commercial license. Included with all GoodVibesLab plans (free tier available).
