A model-agnostic Flutter package for rating summaries, sortable review lists, and review cards — extracted from a production marketplace app so you don't have to rebuild the same avatar-name-stars-text layout from scratch.

Every marketplace, booking, or service app eventually needs a reviews feature — and every team ends up rebuilding the same four things: an average-rating summary, a sortable list of review cards, a star-picker for leaving a new review, and the loading skeletons to match. We'd built that stack three separate times across different Azarr projects before we finally extracted it into a package. reviews_ui_kit is that extraction, generalized so it drops into any Flutter app regardless of your backend, state management, or model shapes.
It's live on pub.dev now: pub.dev/packages/reviews_ui_kit.
reviews_ui_kit started life as the review screens inside Masters Konnect, a services marketplace app we maintain. Customers leave star ratings and written reviews for vendors; vendors reply; everyone sorts and filters. The widgets worked well, but they were wired directly to the app's own NewReviewModel, AppColor theme constants, and a specific set of sub-rating categories (communication, recommend-to-friend, service-as-described). None of that is reusable as-is.
Pulling it into a standalone package meant answering one question for every widget: what's the minimum shape of data this actually needs? The answer became ReviewData — a small, plain, immutable class with no ties to any ORM or model. Map your backend's review object into it once, and every widget in the kit just works.
dependencies: reviews_ui_kit: ^0.1.0
import 'package:reviews_ui_kit/reviews_ui_kit.dart'; OverallRatingSummary( averageRating: 4.8, totalReviews: 128, subRatings: { 'Communication': 4.9, 'Quality': 4.8, 'Value': 4.6, }, )
subRatings is a plain Map<String, double> rather than a fixed set of named parameters — so it works whether your app tracks three sub-ratings, five, or none at all. Leave it out (or pass an empty map) and you get just the headline number, no breakdown.
final reviews = <ReviewData>[ ReviewData( customerName: 'Alicia M.', rating: 5, reviewText: 'Absolutely fantastic work, delivered early.', date: DateTime.now().subtract(const Duration(days: 2)), country: 'Jamaica', vendorReply: 'Thank you so much!', ), // ... ]; ListView( children: [for (final r in reviews) ReviewListItem(review: r)], )
ReviewListItem renders the full card — avatar, name, country, star rating, relative date, review text (or a large decorative star row when there's no written text), attachment thumbnails, and the vendor's reply bubble if one exists. Wire up sorting with ReviewSortOption and the matching bottom sheet:
ReviewSortOption sort = ReviewSortOption.newest; IconButton( icon: const Icon(Icons.sort), onPressed: () => ReviewFilterSheet.show( context, current: sort, onSelected: (opt) => setState(() => sort = opt), ), )
onSelected just tells you which option the user picked — you sort your own list however you fetch or cache it, so this works whether reviews live in local state, a paginated API, or a database query.
double? rating; final controller = TextEditingController(); Column( children: [ RatingRow( label: 'Overall rating', value: rating, onChanged: (v) => setState(() => rating = v), ), ReviewInputField(controller: controller, hintText: 'Write your review...'), ], )
RatingRow is one labeled row of five tappable stars — drop in several of them for sub-ratings on a review form, same as the summary widget above but for input instead of display.
Every widget ships with sensible defaults, but the three places apps differ most — date formatting, country flags, and attachment rendering — are builder callbacks, not hardcoded behavior:
ReviewListItem( review: review, dateLabelBuilder: (date) => DateFormat.yMMMd().format(date), flagBuilder: (country) => countryFlagEmoji(country), attachmentBuilder: (context, url, allUrls, index) => GestureDetector( onTap: () => openYourOwnGallery(allUrls, index), child: YourMediaThumbnail(url: url), ), )
That attachmentBuilder hook is deliberate — it's the exact seam where a package like our own rich_multimedia slots in to render photo, video, audio, and PDF attachments on a review, without reviews_ui_kit needing to depend on it directly. Two small packages, composed, instead of one large one.
isLoading ? const ReviewListShimmer(count: 5) : reviewListView
ReviewListItemShimmer, ReviewLoadMoreShimmer, and ReviewListShimmer match ReviewListItem's real layout pixel-for-pixel, so your loading state doesn't jump around once data arrives.
We went back and forth on whether ReviewListItem should take a generic ReviewListItem<T> with a dozen field-extractor callbacks, or a small, explicit ReviewData class. We landed on the plain class: review widgets almost always need the same handful of fields — name, avatar, rating, text, date, attachments, a reply — so mapping your model into ReviewData is usually a one-line .toReviewData() extension, and the widget's public API stays readable instead of disappearing behind type parameters.
dependencies: reviews_ui_kit: ^0.1.0
Full API reference, more examples, and the source are on pub.dev. If you build something with it, we'd like to hear about it.