| example | ||
| lib | ||
| screenshots | ||
| test | ||
| .gitignore | ||
| .metadata | ||
| analysis_options.yaml | ||
| CHANGELOG.md | ||
| LICENSE | ||
| pubspec.yaml | ||
| README.md | ||
UpdatifyWidget is a Flutter widget designed to seamlessly integrate with the Updatify service. It
allows to display recent updates, announcements, or notifications from projects directly within SaaS
applications. By leveraging the Updatify API, the widget provides a user-friendly interface for
showcasing project updates, enhancing user engagement and communication.
For more info visit the Updatify website.
Screenshots(default styles)
| Desktop | Mobile | Bottom sheet |
|---|---|---|
![]() |
![]() |
![]() |
Trigger button (recommended)
UpdatifyTrigger is a drop-in button that opens the updates dialog and shows a
"new updates" indicator when there are posts the user hasn't seen yet. It's the
most idiomatic way to surface Updatify in an app bar or toolbar:
import 'package:updatify_flutter/updatify_flutter.dart';
AppBar(
actions: [
UpdatifyTrigger(projectId: 'your_project_id'),
],
)
On build it asks the API how many updates exist since the dialog was last opened
and, if any, shows a pulsing indicator. Opening the dialog records the current
time, so the indicator clears once the updates have been seen. It accepts every
showUpdatifyDialog customization (borderRadius, width, title,
itemDecoration, …) and forwards them to the dialog it opens.
On mobile a bottom sheet often feels more natural than a centered dialog. Set
popupType to switch:
UpdatifyTrigger(
projectId: 'your_project_id',
popupType: UpdatifyPopupType.bottomSheet,
)
By default it renders a bell IconButton with a red ping. Supply a builder to
render your own control and place the indicator however you like:
UpdatifyTrigger(
projectId: projectId,
borderRadius: BorderRadius.circular(8),
builder: (context, hasNewUpdates, openUpdates) => IconButton(
onPressed: openUpdates,
icon: Badge(
isLabelVisible: hasNewUpdates,
child: const Icon(Icons.notifications_outlined),
),
),
)
| Parameter | Effect |
|---|---|
builder |
Renders the control from (context, hasNewUpdates, openUpdates). Defaults to a bell icon with a ping. |
pingColor |
Color of the default ping. A PingColor preset (red, yellow, green, blue) or any custom Color. Defaults to PingColor.red. |
popupType |
Whether tapping opens a modal dialog (UpdatifyPopupType.modal, the default) or a bottom sheet (UpdatifyPopupType.bottomSheet, better on mobile). |
alwaysShowIndicator |
Forces the indicator on regardless of unseen updates. Useful for previewing. |
| (dialog options) | All showUpdatifyDialog parameters below are accepted and forwarded. |
To mark updates as unseen again (e.g. in tests or demos), call
UpdatifyTrigger.resetLastViewed(projectId), then rebuild the trigger.
Manual trigger
To open the dialog yourself, for example from an existing button, call
showUpdatifyDialog:
import 'package:updatify_flutter/updatify_flutter.dart';
void main() {
const projectId = 'your_project_id'; // Replace with your actual project ID
runApp(
MaterialApp(
home: Scaffold(
body: Center(
child: Builder(
builder: (context) => ElevatedButton(
onPressed: () => showUpdatifyDialog(context, projectId: projectId),
child: const Text('Recent updates'),
)
),
),
),
),
);
}
For a bottom sheet instead, call showUpdatifyBottomSheet with the same
projectId (it adds bottom-sheet options like heightFactor, showDragHandle,
and isScrollControlled):
showUpdatifyBottomSheet(context, projectId: projectId);
Styling
Theming
The widget follows your app's ThemeData. Post title and body text, the
header/meta text, code block backgrounds, the divider between posts, the dialog
surface, and the "Powered by" footer all read from the active ColorScheme, so
they adapt to light and dark mode and to your custom colors automatically. To
recolor them, change your theme; there is nothing Updatify-specific to set.
Two colors are intentionally not theme-derived and have their own overrides:
- Post-type chips use a fixed palette (blue for update, green for
announcement, yellow for bug fix, teal for feature highlight), tinted for light
and dark surfaces. Change them with
chipColors(see Posts below). - The trigger's ping indicator is red. Recolor it with
pingColor(aPingColorpreset or a customColor), or replace the whole control with abuilder(see Trigger button below).
Dialog
UpdatifyTrigger forwards every dialog option, so you style the dialog right on
the button:
UpdatifyTrigger(
projectId: projectId,
// Rounded corners; convenience for a RoundedRectangleBorder.
borderRadius: BorderRadius.circular(20),
// Or pass a full ShapeBorder via `shape` (takes precedence over borderRadius).
width: 480, // content width; defaults to as wide as allowed
backgroundColor: Colors.white,
elevation: 8,
insetPadding: const EdgeInsets.all(24),
title: 'What\'s new',
titleStyle: const TextStyle(fontWeight: FontWeight.bold),
showCloseButton: true,
barrierDismissible: true,
)
The same options work on showUpdatifyDialog when you open the dialog yourself.
| Parameter | Effect |
|---|---|
borderRadius |
Rounds the dialog corners. Ignored if shape is set. |
shape |
Full ShapeBorder for the dialog. |
width |
Width of the dialog content. |
backgroundColor, elevation |
Dialog surface color and shadow. |
insetPadding |
Padding between the dialog and the screen edges. |
title, titleStyle, titlePadding, titleDecoration |
The dialog header. |
showCloseButton, closeButtonColor, closeButtonStyle |
The close button. |
contentPadding, contentMargin |
Spacing around the list of posts. |
Posts
Each post's appearance is controlled by UpdatifyPostDecoration, passed via
itemDecoration to UpdatifyTrigger, showUpdatifyDialog, or UpdatifyWidget:
UpdatifyTrigger(
projectId: projectId,
itemDecoration: UpdatifyPostDecoration(
// Title
titleStyle: const TextStyle(fontSize: 22, fontWeight: FontWeight.w700),
titleAlign: TextAlign.start,
// Body
bodyStyle: const TextStyle(fontSize: 14, height: 1.4),
bodyPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
// Code (inline + code blocks) - merged onto a monospace default
codeStyle: const TextStyle(fontSize: 13),
codeBlockDecoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(6),
),
// Type chip ("Update", "Announcement", "Bug Fix", "Feature Highlight")
chipStyle: const TextStyle(fontWeight: FontWeight.w600),
chipDecoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(6),
),
chipPosition: ChipPosition.leading,
// Per-type base color - used for the chip text and, tinted, its
// background/border. Defaults: blue (update), green (announcement),
// yellow (bugfix), teal (feature highlight).
chipColors: const {
PostType.update: Color(0xFF2563EB),
PostType.announcement: Color(0xFF16A34A),
PostType.bugfix: Color(0xFFCA8A04),
PostType.featureHighlight: Color(0xFF0D9488),
},
// Header (chip + date/author row)
headerPadding: const EdgeInsets.all(16),
// Image
imagePadding: const EdgeInsets.only(bottom: 12),
// Date formatting (defaults to DateFormat.yMMMd)
dateFormatter: (date) => DateFormat.yMMMMd().format(date),
),
)
| Group | Fields |
|---|---|
| Title | titleStyle, titleAlign |
| Body | bodyStyle, bodyAlign, bodyPadding |
| Code | codeStyle, codeBlockDecoration |
| Chip | chipStyle, chipDecoration, chipPadding, chipPosition, chipColors |
| Header | headerStyle, headerDecoration, headerPadding, headerHorizontalSpacing, headerVerticalSpacing, headerVerticalDirection |
| Image | imagePadding, imageLoadingBuilder, imageErrorBuilder |
| Date | dateFormatter |
For full control over a post's layout, pass an itemBuilder to the trigger. It
receives the BuildContext and the UpdatifyPost and replaces the default
rendering entirely, so itemDecoration no longer applies to that post:
UpdatifyTrigger(
projectId: projectId,
itemBuilder: (context, post) => ListTile(
leading: const Icon(Icons.campaign_outlined),
title: Text(post.title),
subtitle: post.body == null ? null : Text(post.body!),
),
)
Each UpdatifyPost exposes id, type (a PostType), title, createdAt,
author, imageUrl, and body, so you can build any layout from its data.
Change the separator drawn between posts with dividerBuilder:
UpdatifyTrigger(
projectId: projectId,
dividerBuilder: (context) => const Divider(height: 32, thickness: 0.5),
)
The list's scrolling also forwards through the trigger via controller,
physics, and reverse.
Trigger button
The button itself, and where its "new updates" indicator sits, is fully yours
via builder. It receives hasNewUpdates and an openUpdates callback, so you
can wrap any widget:
UpdatifyTrigger(
projectId: projectId,
builder: (context, hasNewUpdates, openUpdates) => TextButton.icon(
onPressed: openUpdates,
icon: Badge(
isLabelVisible: hasNewUpdates,
backgroundColor: Colors.red,
child: const Icon(Icons.campaign_outlined),
),
label: const Text("What's new"),
),
)
Omit builder to get the default: a bell IconButton that shows a pulsing dot
while there are unseen updates. Recolor that dot with pingColor, using a
PingColor preset or any custom Color:
UpdatifyTrigger(projectId: projectId, pingColor: PingColor.green)
UpdatifyTrigger(projectId: projectId, pingColor: const Color(0xFF9333EA))
Set alwaysShowIndicator: true to force the indicator on while you style it.
Opening links
Links in post bodies open in the device's default browser via
url_launcher. For this to work in
release builds, the host app must declare the appropriate platform config:
iOS - add to ios/Runner/Info.plist:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>https</string>
<string>http</string>
</array>
Android - add to android/app/src/main/AndroidManifest.xml, as a direct
child of the <manifest> element:
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https" />
</intent>
</queries>
macOS - the app is sandboxed, so add the network client entitlement to both
macos/Runner/DebugProfile.entitlements and macos/Runner/Release.entitlements:
<key>com.apple.security.network.client</key>
<true/>
Windows, Linux, and web need no additional configuration.
See the url_launcher docs
for details.


