version_gate 1.0.4
version_gate: ^1.0.4 copied to clipboard
Lightweight Flutter package that checks for app updates on App Store and Google Play with zero configuration, smart frequency guards, and customizable UI.
version_gate #
A lightweight Flutter package that checks for app updates on the App Store and Google Play Store with zero configuration.
Table of Contents #
- Why version_gate?
- Features
- Screenshots
- Installation
- Quick Start
- Usage Patterns
- Configuration
- API Reference
- How It Works
- Comparison with Other Packages
- Troubleshooting
- Contributing
- License
Why version_gate? #
Two popular packages exist for version checking — new_version (broken since 2022, no Dart 3 support) and upgrader (overly complex, heavy dependencies, hard to customize UI). version_gate was built to be:
- Zero-config — reads bundle ID and app version automatically
- Lightweight — only 4 dependencies, all Flutter Favorites
- Customizable — use built-in widgets or bring your own UI
- Modern — built for Dart 3 and Flutter 3.x
Features #
- Zero configuration — no API keys, no manual bundle IDs
- Smart frequency guard — checks once daily (configurable)
- Three update modes — optional, flexible, forced
- Built-in widgets — dialog, banner, full-screen blocker
- Custom UI friendly — get the data, build your own UI
- Release notes — shows "What's new" from the store
- Fail-safe — never crashes your app, even on network errors
- Minimal footprint — 4 lightweight, production-grade dependencies
Screenshots #
Update dialog with version info, release notes, and action buttons
Installation #
1. Add dependency #
dependencies:
version_gate: ^1.0.4
2. Install packages #
flutter pub get
3. Import #
import 'package:version_gate/version_gate.dart';
4. Platform Setup #
The url_launcher dependency requires a small platform config so your app can open store URLs.
Android — add to android/app/src/main/AndroidManifest.xml (inside <manifest>, before <application>):
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https" />
</intent>
</queries>
iOS — add to ios/Runner/Info.plist (inside <dict>):
<key>LSApplicationQueriesSchemes</key>
<array>
<string>https</string>
</array>
Note: If you skip this step,
openStore()may silently fail on Android 11+ and iOS.
Quick Start #
The simplest possible usage — 3 lines, zero config:
final result = await VersionGate().check();
if (result != null && result.hasUpdate) {
result.showBuiltInDialog(context);
}
That's it. The package automatically reads your app's bundle ID and version, checks the store, and shows a Material dialog if an update exists.
Usage Patterns #
Pattern A: Custom UI #
Maximum flexibility — handle the result yourself.
final result = await VersionGate().check();
if (result != null && result.hasUpdate) {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text('Update Available'),
content: Text('Version ${result.storeVersion} is ready.'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('Later'),
),
ElevatedButton(
onPressed: () => result.openStore(),
child: Text('Update Now'),
),
],
),
);
}
Pattern B: Built-in Dialog #
Drop-in dialog — customize text only.
final result = await VersionGate().check();
if (result != null && result.hasUpdate) {
result.showBuiltInDialog(
context,
title: 'New Version Available',
message: 'Version ${result.storeVersion} is now available.',
updateButtonText: 'Update Now',
laterButtonText: 'Later',
showReleaseNotes: true,
);
}
Pattern C: Forced Update #
Block the entire app until the user updates.
final result = await VersionGate(
updateMode: UpdateMode.forced,
).check();
if (result != null && result.hasUpdate) {
result.showBuiltInDialog(context);
// Shows full-screen blocker — cannot be dismissed
}
Pattern D: Banner #
Persistent banner while the user continues using the app.
final result = await VersionGate(
updateMode: UpdateMode.flexible,
).check();
if (result != null && result.hasUpdate) {
// Add to your widget tree:
UpdateBanner(
result: result,
onDismiss: () => setState(() => showBanner = false),
);
}
Pattern E: Weekly Check #
final result = await VersionGate(
checkFrequency: CheckFrequency.oncePerWeek,
).check();
Pattern F: Override Package ID #
Only needed when the store bundle ID differs from the app's local ID (e.g., white-label apps).
final result = await VersionGate(
packageIdOverride: 'com.production.appid',
).check();
Configuration #
All parameters are optional:
VersionGate(
checkFrequency: CheckFrequency.onceDaily, // How often to check
updateMode: UpdateMode.optional, // Dialog behavior
packageIdOverride: 'com.production.appid', // Override bundle ID
countryCode: 'us', // iTunes region
)
Check Frequencies #
| Value | Behavior |
|---|---|
CheckFrequency.onceDaily |
Check once every 24 hours (default) |
CheckFrequency.oncePerSession |
Check on every cold app launch |
CheckFrequency.oncePerWeek |
Check once every 7 days |
CheckFrequency.always |
Always check — dev/testing only |
CheckFrequency.custom(hours: 12) |
Check every N hours |
Update Modes #
| Value | Behavior |
|---|---|
UpdateMode.optional |
Dismissible dialog — user can tap "Later" (default) |
UpdateMode.flexible |
Persistent banner — app still usable |
UpdateMode.forced |
Full-screen blocker — cannot dismiss |
API Reference #
VersionGate #
The main entry point. Create an instance and call check().
final gate = VersionGate(
checkFrequency: CheckFrequency.onceDaily,
updateMode: UpdateMode.optional,
packageIdOverride: null,
countryCode: 'us',
);
final result = await gate.check(); // Returns VersionCheckResult?
check() returns null when the frequency guard skips the check (e.g., already checked today).
VersionCheckResult #
| Property | Type | Description |
|---|---|---|
hasUpdate |
bool |
true if store version > local version |
localVersion |
String |
Installed version (e.g., "1.2.0") |
storeVersion |
String |
Store version (e.g., "1.5.0") |
releaseNotes |
String? |
"What's new" from the store |
storeUrl |
String |
Direct link to store listing |
updateMode |
UpdateMode |
The mode set in VersionGate() |
lastChecked |
DateTime |
Timestamp of this check |
error |
String? |
Error message if check failed |
| Method | Description |
|---|---|
showBuiltInDialog(context, {...}) |
Shows the built-in update UI (dialog or block screen) |
openStore() |
Opens the store listing in the platform browser |
How It Works #
App launches
│
▼
Was a check already done today? ──YES──▶ Skip. Return null.
│
NO
│
▼
Read local version + package name (automatic via package_info_plus)
│
▼
Detect platform
│
├── iOS ──▶ iTunes Lookup API (JSON) ── free, no auth
└── Android ──▶ Google Play Store page (HTML parse)
│
▼
Save timestamp to SharedPreferences (only after successful API call)
│
▼
Compare versions (semantic versioning)
│
├── Same ──▶ VersionCheckResult(hasUpdate: false)
└── Store is newer ──▶ VersionCheckResult(hasUpdate: true)
Key design decisions:
- Timestamp is saved after a successful API response, not after showing a dialog. If the network fails, it retries on next launch.
- The version comparator handles all semver formats:
1.2.3,1.2.3+45,1.2,2.0.0-beta.1. - All errors are caught internally.
check()never throws — it returnshasUpdate: falsewith anerrorfield on failure.
Comparison with Other Packages #
| Feature | version_gate | upgrader | new_version |
|---|---|---|---|
| Zero config (auto bundle ID) | ✅ | ❌ Manual | ❌ Manual |
| Dart 3 / Flutter 3.x | ✅ | ❌ Broken | ⚠️ Issues |
| Once-daily check | ✅ | ✅ | ✅ |
| Custom check frequency | ✅ | ❌ | ❌ |
| Forced update mode | ✅ | ✅ | ❌ |
| Custom UI (bring your own) | ✅ Easy | ❌ Hard | ❌ |
| Built-in dismissible dialog | ✅ | ✅ | ❌ |
| Release notes from store | ✅ | ✅ | ❌ |
| Lightweight dependencies | ✅ (4 pkgs) | ❌ Heavy | ✅ Minimal |
| Active maintenance (2026) | ✅ | ❌ Stale | ❌ Stale |
Troubleshooting #
Q: check() always returns null.
The frequency guard is skipping the check. You already checked within the configured period. Use CheckFrequency.always for testing, or call FrequencyGuard.reset() to clear the stored timestamp.
Q: hasUpdate is always false on Android.
Google may have changed the Play Store HTML structure. File an issue — the parser may need a new regex pattern.
Q: The app on the store uses a different bundle ID.
Pass packageIdOverride: 'com.your.store.id' to VersionGate().
Q: I need to check a specific App Store region.
Pass countryCode: 'sa' (or any valid iTunes country code) to VersionGate().
Contributing #
Contributions are welcome! Here's how:
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Please make sure to update tests as appropriate and run dart pub publish --dry-run before submitting.
Reporting Bugs #
Found a bug? Open an issue with:
- Flutter version (
flutter --version) - Platform (iOS/Android)
- Steps to reproduce
- Expected vs actual behavior
Feature Requests #
Have an idea? Open a feature request.
License #
MIT License — Copyright (c) 2026 aboelnasr.com
See LICENSE for details.
Made with ❤️ by aboelnasr.com