Blog/Flutter SDK

Flutter Comments SDK 1.2.0: What's New

By Joris Obert5 min read

gvl_comments 1.2.0 is out on pub.dev. It's the drop-in comments widget for Flutter: threaded replies, reactions, AI moderation and user reports, with comments stored and moderated for you.

You can build a comments table and a list widget in an afternoon. What takes months is everything around it: a refresh that lands in the middle of a send, a token that outlives the user it was issued for, a reply that ends up in the wrong thread, a single crafted comment that freezes the screen of everyone who opens it. Most of this release is exactly that kind of work: 20 security and bug fixes, plus a few features that make the widget behave well at scale.

Security: Tokens Bound to Their User

  • Switching users gets a new token. If your app changed the user without calling invalidateToken(), the previous user's token could be reused for up to an hour, so posts, reactions, reports and profile updates were attributed to the previous user. The SDK now requests a new token whenever the user changes.
  • identify() can't overwrite someone else's profile. It only sends a profile with a token issued for that same user.
  • A ReDoS in link detection is fixed. One crafted 5,000-character comment could block the UI thread for about 800 ms on every rebuild, for every viewer of the thread. The pattern is now bounded, with a worst case under 1 ms.
  • TopComment decodes avatars at display size instead of full resolution. Avatar URLs come from your users, so their size can't be trusted.

Quota-Aware Posting

When a project has used up its monthly comment quota, CommentsList shows “Comments are paused for now” (in all five languages) with no Retry button, since retrying can't help. The user's text stays in the composer, and reading, reactions and reports keep working. If you post from your own UI, catch the new exception:

try {
  await CommentsKit.I().post(threadKey: threadKey, body: text, user: user);
} on CommentsQuotaExceededException {
  // Only new comments are paused, until the quota resets or the plan is upgraded.
}

A Comment Actions Menu, and Safer Reporting

Each comment now has a ⋯ menu next to its bubble. “Copy” is always there; “Report” only appears on other people's comments, for signed-in users. The menu no longer covers the text.

Reporting now asks for confirmation first, so a stray tap no longer flags someone. And while a comment is on its way, it shows “Sending…” in place of its timestamp.

Pagination Without Shared State: listPage()

lastNextCursor and lastHasMore were shared by every list, so two lists loading at the same time could overwrite each other's cursor. The new listPage() returns the cursor with the page:

final kit = CommentsKit.I();

final page = await kit.listPage(threadKey, user: user);
// page.items, page.hasMore

if (page.hasMore) {
  final next = await kit.listPage(threadKey, user: user, cursor: page.nextCursor);
}

CommentsList uses it internally. The old getters still work but are deprecated.

Faster Feeds

A feed where every card shows a comment count used to send one request per card. commentCount() lookups issued in the same frame are now merged into a single request, so this costs one call for the whole screen:

CommentCount(
  threadKey: post.threadKey,
  builder: (context, count, refresh) => Text('$count comments'),
)
  • prefetchThreads() splits large requests into chunks of 50 (the API limit). It used to fail with too_many_threads.
  • TopComment and CommentCount ignore responses meant for a previous threadKey, which matters when list items are recycled while scrolling.
  • Moderation settings are cached instead of being refetched every time a CommentsList mounts, and identify() skips a profile that is already synced.

The Reaction Picker Is a Route

It used to be an overlay that could stay on top of the next screen. It's now a dialog route, so Android back, a pop or predictive back closes it, and it keeps the caller's local Theme. The route is named, so screen tracking can ignore it:

@override
void didPush(Route route, Route? previousRoute) {
  if (route.settings.name == reactionPickerRouteName) return;
  analytics.logScreen(route.settings.name);
}

Fixes You'd Rather Not Find in Production

  • Calling identify() before the first token (the default CommentsList flow) left an internal marker stuck, and the profile was never synced for the rest of the session.
  • After switching threads, the draft and reply target were kept, so a reply could be posted in the wrong thread. A slow response for the previous thread or user could also overwrite the current one.
  • A pull-to-refresh during a send could drop the new comment or show it twice. It's now listed exactly once, whatever order the responses arrive in.
  • A reply posted in a collapsed thread was hidden behind “See N more replies”. The thread now expands.
  • A failed reaction stayed on screen although the server never recorded it. It's now rolled back with a message.
  • Moderated and reported comments no longer show Reply, the like button or their reaction counts: only the placeholder is left.
  • Dates older than a week had day and year swapped, and were computed in UTC instead of local time.
  • The “Powered by” link didn't open on Android 11+.

Smaller changes: the send button is disabled while the input is empty, composerMaxLines is honored, relative timestamps refresh every minute, replying no longer scrolls the list to the top, and the Reply and reaction targets are larger and better labelled for screen readers.

Coming from 1.0? Also New in 1.1

1.1.0 didn't get its own post. If you're upgrading from 1.0.x, you also get:

  • An empty state for threads with no comments (customizable with emptyBuilder), pull-to-refresh, and a loading skeleton instead of a bare spinner.
  • Failed sends that give the user their text back, and pagination or refresh errors that show a Retry instead of a spinner that silently stops.
  • Links that ask for confirmation before opening (http, https and mailto only), validated avatar URLs, and HTTPS enforced for the API base.
  • A composer limited to 5,000 characters, with a counter near the limit.
  • Right-to-left layout support and larger, labelled tap targets.
  • onCommentPosted on CommentsList (1.0.1), called with the confirmed comment, for analytics or counters.

How to Upgrade

# pubspec.yaml
dependencies:
  gvl_comments: ^1.2.0
flutter pub upgrade gvl_comments

For most apps it's a version bump. Four things to check:

  • lastNextCursor / lastHasMore are deprecated: use listPage().
  • showCommentReactionPicker now pushes a route, so its context needs a Navigator ancestor.
  • With a custom commentItemBuilder, the ⋯ button is always shown, since Copy is always available.
  • GvlCommentsL10n has 6 new strings. This only matters if you subclass it.

The minimum is Flutter 3.19, and package_info_plus 10.x is now accepted. The full list is in the changelog.

Try gvl_comments 1.2.0

Add a moderated comment section to your Flutter app in minutes. Free up to 1,000 comments a month, AI moderation included, no credit card.