Show updates in your app
OverPush shows nothing on screen. If you want users to see an update arrive, your app listens to OverPush.events and shows it in its own design. This page has a complete banner you can copy.
The banner below sits at the bottom of every screen and appears only while something is happening:
- New update found…
- Downloading update · 42%, with a progress bar
- New update downloaded. Restart the app to apply it. It hides after a few seconds.
It works the same on Android and iOS. Prefer to show nothing? Skip this page; updates still download and apply on their own.
Before you start
- The app depends on
overpush1.0.0 or newer, and the build in the store was made withoverpush release. Events come from the package inside that build.
1. Add the banner widget
Create a new file in your app:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:overpush/overpush.dart';
/// A small banner at the bottom of the screen that shows an OverPush update:
/// "New update found", the download percent, then "Restart the app to apply
/// it". Hidden the rest of the time.
///
/// Put it in a Stack above your app (see main.dart). Restyle it freely: it is
/// your app's code, OverPush only sends the events.
class UpdateBanner extends StatefulWidget {
const UpdateBanner({super.key});
@override
State<UpdateBanner> createState() => _UpdateBannerState();
}
class _UpdateBannerState extends State<UpdateBanner> {
StreamSubscription<OverPushEvent>? _subscription;
Timer? _hideTimer;
String? _message;
double? _progress; // 0..1, or null for an indeterminate bar
@override
void initState() {
super.initState();
_subscription = OverPush.events.listen(_onEvent);
}
void _onEvent(OverPushEvent event) {
_hideTimer?.cancel();
switch (event) {
case OverPushPatchFound():
_show('New update found…', null);
case OverPushDownloading(:final percent, :final progress):
_show(
percent == null
? 'Downloading update…'
: 'Downloading update · $percent%',
progress,
);
case OverPushPatchReady():
_show('New update downloaded. Restart the app to apply it.', 1);
_hideTimer = Timer(const Duration(seconds: 6), () => _show(null, null));
case OverPushFailed():
_show(null, null); // nothing changed on the device; stay quiet
default:
break; // Checking, UpToDate, Revoked: nothing to show
}
}
void _show(String? message, double? progress) {
if (!mounted) return;
setState(() {
_message = message;
_progress = progress;
});
}
@override
void dispose() {
_subscription?.cancel();
_hideTimer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
final String? message = _message;
return Positioned(
left: 16,
right: 16,
// Above the keyboard when it is open, so it is never hidden behind it.
bottom:
MediaQuery.viewInsetsOf(context).bottom +
MediaQuery.paddingOf(context).bottom +
24,
child: IgnorePointer(
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 250),
child: message == null
? const SizedBox.shrink()
: Material(
key: const ValueKey<String>('update-banner'),
color: const Color(0xFF1F2430),
elevation: 6,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.fromLTRB(14, 12, 14, 12),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
message,
style: const TextStyle(
color: Colors.white,
fontSize: 13,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 8),
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: LinearProgressIndicator(
value: _progress,
minHeight: 4,
backgroundColor: Colors.white24,
color: _progress == 1
? const Color(0xFF3CCF7A)
: Theme.of(context).colorScheme.primary,
),
),
],
),
),
),
),
),
);
}
}
2. Put it above your app
Add the banner to your app widget's builder, so it shows over every screen. Pick the app widget you use:
import 'update_banner.dart';
// ...inside your app widget's build():
return MaterialApp(
builder: (context, child) => Stack(
children: [
if (child != null) child,
const UpdateBanner(),
],
),
home: const HomeScreen(),
);
import 'update_banner.dart';
// ...inside your app widget's build():
return MaterialApp.router(
routerConfig: router,
builder: (context, child) => Stack(
children: [
if (child != null) child,
const UpdateBanner(),
],
),
);
import 'update_banner.dart';
// ...inside your app widget's build():
return GetMaterialApp(
builder: (context, child) => Stack(
children: [
if (child != null) child,
const UpdateBanner(),
],
),
home: const HomeScreen(),
);
Already using builder, for example for a theme or text scale? Keep what you have and put it inside the Stack in place of child.
You don't call OverPush.init or change main(). OverPush starts by itself in a release build; the banner only listens.
3. Ship it and try it
- Build a release with the banner in it and install it on your phone:
overpush release androidoverpush release ios --artifact app, then preview it. - Change some Dart code and publish a patch:
overpush patch androidoverpush patch ios. - Close the app fully and open it. A few seconds in, the banner shows the download, then the restart message.
- Close and open the app again. The patch is running.
Make it yours
- Text and colours: change the strings in
_onEventand the colours inbuild. - Only the final message: remove the
OverPushPatchFoundandOverPushDownloadingcases. Users then see just "Restart the app to apply it". - A SnackBar or dialog instead: call your own UI from
_onEvent. Everything else stays the same. - A screen that opens later: read
OverPush.lastEventininitStateto catch up on what already happened. - Checking when you choose: with
auto_update: false, callOverPush.update()yourself; the banner shows that check the same way. See Update strategies.
All events
| Event | When | Fields |
|---|---|---|
OverPushChecking | A check with the server started | – |
OverPushPatchFound | A new patch exists and is about to download | patchNumber |
OverPushDownloading | While downloading, once per percent | percent (0–100 or null), progress (0–1 or null), receivedBytes, totalBytes, patchNumber |
OverPushPatchReady | Downloaded and verified; runs from the next app start | patchNumber |
OverPushUpToDate | Nothing new | – |
OverPushRevoked | The running patch was pulled; the next start runs the store build | – |
OverPushFailed | No network, or the patch didn't verify. Nothing changed on the device | error |
percent is null only when the download size isn't known; the banner above then shows a moving bar instead of a number.