SK Auto-Updater
Flutter plugin for in-app updates
A fully configurable auto-update plugin for Flutter Android apps. Check for updates, download APKs, and trigger installation automatically.
Platform Support
Android
iOS
Web
macOS
Windows
Linux
This plugin only supports Android. Designed for sideloaded apps, internal/enterprise apps, and apps distributed outside the Google Play Store.
Requirements
This plugin requires a ReleaseHub backend to serve version information and APK files:
Hosted
Use releasehub.dev - managed service by Version Two
Self-hosted
Deploy your own ReleaseHub instance on your server
Important: ReleaseHub (either hosted or self-hosted) is required for this plugin to function. The plugin communicates with ReleaseHub's API to check for updates and download files.
Installation
Add to your pubspec.yaml:
dependencies:
releasehub_updater: ^1.0.0
Then run:
GetX Adapter (Optional)
If using the GetX adapter, ensure you have GetX in your dependencies:
dependencies:
get: ^4.6.6
Android Configuration
Required Permissions
Add to your android/app/src/main/AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Required for downloading updates -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- Required for installing APKs -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<!-- Required for Android 9 and below -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<application ...>
<!-- Your activities and other components -->
</application>
</manifest>
File Provider Configuration (Android 7+)
The open_filex package usually handles this automatically. If APK installation fails, add to your AndroidManifest.xml inside the <application> tag:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
And create android/app/src/main/res/xml/file_paths.xml:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="external_files" path="." />
<external-files-path name="external_files_path" path="." />
<cache-path name="cache" path="." />
</paths>
Quick Start
Simple Usage (Recommended)
The easiest way to use the plugin with built-in UI:
import 'package:releasehub_updater/autoupdater.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize the auto-updater with ReleaseHub
await AutoUpdater.init(
baseUrl: 'https://releases.example.com', // your ReleaseHub server URL
projectSlug: 'my-app',
channel: 'stable',
);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// Required for dialogs to work
navigatorKey: AutoUpdater.navigatorKey,
scaffoldMessengerKey: AutoUpdater.scaffoldMessengerKey,
home: HomeScreen(),
);
}
}
// Manual check from settings screen
ElevatedButton(
onPressed: () => AutoUpdater.checkForUpdates(),
child: Text('Check for Updates'),
)
Standalone Mode (No External Dependencies)
Uses Flutter's built-in ValueNotifier for reactive state:
import 'package:releasehub_updater/autoupdater_standalone.dart';
// Create the service
final updater = AutoUpdaterStandalone(
config: AutoUpdaterConfig.releaseHub(
baseUrl: 'https://releases.example.com',
projectSlug: 'my-app',
channel: 'stable',
),
ui: AutoUpdaterDefaultUI.create(
primaryColor: Colors.blue,
),
);
// Initialize (call once at app startup)
await updater.initialize();
// Use ValueListenableBuilder for reactive UI
ValueListenableBuilder<bool>(
valueListenable: updater.isCheckingForUpdate,
builder: (context, isChecking, child) {
return isChecking
? CircularProgressIndicator()
: ElevatedButton(
onPressed: () => updater.checkForUpdate(
silent: false,
context: context,
),
child: Text('Check for Updates'),
);
},
)
// Don't forget to dispose when done
updater.dispose();
GetX Integration
import 'package:releasehub_updater/autoupdater_getx.dart';
// Register the service
Get.put(
AutoUpdaterGetxService(
config: AutoUpdaterConfig.releaseHub(
baseUrl: 'https://releases.example.com',
projectSlug: 'my-app',
channel: 'stable',
),
),
permanent: true,
);
// Use in widgets
final updater = Get.find<AutoUpdaterGetxService>();
Obx(() => updater.isCheckingForUpdate.value
? CircularProgressIndicator()
: Text('Version: ${updater.currentVersion?.version}')
)
Core Service (Maximum Control)
import 'package:releasehub_updater/autoupdater.dart';
final core = AutoUpdaterCore(
config: AutoUpdaterConfig.releaseHub(
baseUrl: 'https://releases.example.com',
projectSlug: 'my-app',
),
);
await core.initialize();
final result = await core.checkForUpdate();
switch (result) {
case UpdateAvailable(versionInfo: final info):
print('Update available: ${info.displayVersion}');
// Download and install manually
final downloadResult = await core.downloadApk(
info.apkUrl,
info.displayVersion,
onProgress: (progress) => print(progress.formattedProgress),
);
case NoUpdateAvailable():
print('Already on latest version');
case UpdateCheckError(message: final msg):
print('Error: $msg');
case UpdateCheckDisabled():
print('Updates disabled');
}
Configuration
AutoUpdaterConfig
| Property | Type | Default | Description |
|---|---|---|---|
baseUrl | String | required | ReleaseHub URL (hosted or self-hosted) |
appId | String | required | Project slug in ReleaseHub |
versionPath | String | 'api/check' | API path (auto-set in ReleaseHub mode) |
environment | String | 'stable' | Release channel name |
releaseHubMode | bool | true | Enable ReleaseHub API format |
checkOnStartup | bool | true | Auto-check on initialization |
startupDelay | Duration | 3 seconds | Delay before startup check |
skipPermissionCheck | bool | false | Skip Android permission dialogs |
isDisabled | bool | false | Completely disable updates |
apkFilenamePattern | String | '{appId}_update_{version}.apk' | Downloaded APK filename |
includeArchitecture | bool | true | Send device arch to server |
httpHeaders | Map? | null | Custom HTTP headers (for API auth) |
connectionTimeout | Duration | 30 seconds | HTTP request timeout |
responseFields | VersionResponseFields? | null | Custom JSON field mapping for non-standard APIs |
logger | Function? | null | Custom logging callback (String) => void |
dialogAutoDismissDelay | Duration? | 8 seconds | Auto-dismiss update dialog after delay (null to disable) |
Note: When using AutoUpdaterConfig.releaseHub(), the defaults are optimized for ReleaseHub.
When using the base constructor directly, defaults differ: versionPath='version', environment='prod', releaseHubMode=false.
Project Visibility & Authentication
Public Projects (No Authentication)
await AutoUpdater.init(
baseUrl: 'https://releases.example.com',
projectSlug: 'my-public-app',
channel: 'stable',
);
Private Projects (API Key Authentication)
await AutoUpdater.initWithConfig(
config: AutoUpdaterConfig.releaseHub(
baseUrl: 'https://releases.example.com',
projectSlug: 'my-private-app',
channel: 'stable',
httpHeaders: {
'Authorization': 'Bearer YOUR_API_KEY',
},
),
);
Customizing UI
Custom Strings (Localization)
AutoUpdater.init(
baseUrl: 'https://releases.example.com',
projectSlug: 'my-app',
strings: AutoUpdaterStrings(
updateAvailable: 'Aktualizácia dostupná',
download: 'Stiahnuť',
later: 'Neskôr',
noUpdateAvailable: 'Mate najnovsiu verziu',
),
);
Custom UI Callbacks (Standalone)
AutoUpdaterStandalone(
config: myConfig,
ui: AutoUpdaterStandaloneUI(
onShowUpdateAvailable: (context, info, onDownload) {
showDialog(
context: context!,
builder: (ctx) => MyCustomUpdateDialog(
version: info.displayVersion,
onDownload: () {
Navigator.pop(ctx);
onDownload();
},
),
);
},
onShowError: (context, title, message) {
MyToast.showError('$title: $message');
},
),
);
Architecture Detection
The plugin automatically detects the Android device's CPU architecture and:
- Sends it as a query parameter (
?arch=arm64-v8a) to ReleaseHub - Normalizes build numbers for Flutter split APK builds (
--split-per-abi)
Supported Android Architectures
arm64-v8a
Most modern devices
armeabi-v7a
Older 32-bit ARM
x86_64
Emulators, Chromebooks
x86
Older emulators
Debug Mode
For troubleshooting update detection issues:
// Get debug info
print(AutoUpdater.getDebugInfo());
// Show debug dialog
AutoUpdater.showDebugDialog();
Output includes:
- Current app version and build number
- Device architecture
- ReleaseHub URL being used
- Raw vs normalized build numbers
Using Custom Backend (Non-ReleaseHub)
If you have your own backend, disable ReleaseHub mode:
AutoUpdaterConfig(
baseUrl: 'https://your-server.com',
appId: 'com.example.app',
versionPath: 'version',
environment: 'prod',
releaseHubMode: false, // Use standard mode
responseFields: VersionResponseFields(
version: 'app_version',
build: 'build_number',
apkUrl: 'download_url',
),
)
Custom Backend Response Format:
{
"app_version": "1.2.0",
"build_number": 42,
"download_url": "https://your-server.com/app-1.2.0.apk"
}
Disabling in Development
To prevent update checks during development:
import 'package:flutter/foundation.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Only enable auto-updater in release builds
if (!kDebugMode) {
await AutoUpdater.init(
baseUrl: 'https://releases.example.com',
projectSlug: 'my-app',
);
}
runApp(MyApp());
}