Skip to content

Commit c6bc902

Browse files
Merge pull request #12 from jayadevpanthaplavil/development
Add Remote Config redirection URL and fix update handling priority and checks
2 parents 650eb91 + ccb0d31 commit c6bc902

File tree

10 files changed

+129
-38
lines changed

10 files changed

+129
-38
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
6+
## [1.2.1] - 2025-10-22
7+
### Added
8+
- Support for separate redirection URL in Remote Config, allowing different URLs for update redirection based on configuration.
9+
10+
### Fixed
11+
- Prioritized forced update handling over patch updates when the minimum version changes.
12+
- Ensured update check is triggered even when store navigation is skipped, improving reliability of update detection.
13+
514
## [1.2.0] - 2025-10-15
615
### Added
716
- Support for common update tracks: `stable`, `beta`, and `staging` for both release and patch updates.

README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -238,12 +238,13 @@ class _MyHomePageState extends State<MyHomePage> {
238238
2. Add the following default parameters and values.
239239
3. Refer to [`example/remoteconfig.template.json`](example/remoteconfig.template.json) for a complete example.
240240

241-
| Key | Example Value | Description |
242-
|------------------------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------|
243-
| `min_required_version` | `{"stable": "1.0.0", "beta": "1.0.5", "staging": "1.0.10"}` | Minimum app version required **per track**. JSON map of track versions (`{"stable": "1.0.0", "beta": "1.0.5"}`). |
244-
| `latest_version` | `{"stable": "1.0.29", "beta": "1.0.30", "staging": "1.0.31"}` | Latest available version **per track**. JSON map of track versions (`{"stable": "1.0.29", "beta": "1.0.30"}`). |
245-
| `patch_enabled` | `true` | Global flag to enable or disable patch checking. |
246-
| `patch_info` | `{ "1.0.25": { "stable": 1, "beta": 0, "staging": 0 }, "1.0.1": { "stable": 1, "beta": 2, "staging": 3 } }` | Patch numbers per version and track. The format is `{ "version": { "track": patchNumber } }`. |
241+
| Key | Example Value | Description |
242+
|------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------|
243+
| `min_required_version` | `{"stable": "1.0.0", "beta": "1.0.5", "staging": "1.0.10"}` | Minimum app version required **per track**. JSON map of track versions (`{"stable": "1.0.0", "beta": "1.0.5"}`). |
244+
| `latest_version` | `{"stable": "1.0.29", "beta": "1.0.30", "staging": "1.0.31"}` | Latest available version **per track**. JSON map of track versions (`{"stable": "1.0.29", "beta": "1.0.30"}`). |
245+
| `patch_enabled` | `true` | Global flag to enable or disable patch checking. |
246+
| `patch_info` | `{ "1.0.25": { "stable": 1, "beta": 0, "staging": 0 }, "1.0.1": { "stable": 1, "beta": 2, "staging": 3 } }` | Patch numbers per version and track. The format is `{ "version": { "track": patchNumber } }`. |
247+
| `redirect_url` | `{ "android": { "stable": "https://play.google.com/store/apps/details?id=com.example.stable", "beta": "https://play.google.com/store/apps/details?id=com.example.beta", "staging": "https://play.google.com/store/apps/details?id=com.example.staging"}, "ios": {"stable": "https://apps.apple.com/app/idxxxx", "beta": "https://apps.apple.com/app/idxxxx", "staging": "https://apps.apple.com/app/idxxxx"}}` | Redirection URLs for each platform and track. JSON {"android": {"stable": "<url>", ...}, "ios": {"stable": "<url>", ...}} |
247248

248249
**Example `patch_info` value:**
249250

example/pubspec.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -368,10 +368,10 @@ packages:
368368
dependency: transitive
369369
description:
370370
name: url_launcher_ios
371-
sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7
371+
sha256: "6b63f1441e4f653ae799166a72b50b1767321ecc263a57aadf825a7a2a5477d9"
372372
url: "https://pub.dev"
373373
source: hosted
374-
version: "6.3.4"
374+
version: "6.3.5"
375375
url_launcher_linux:
376376
dependency: transitive
377377
description:
@@ -384,10 +384,10 @@ packages:
384384
dependency: transitive
385385
description:
386386
name: url_launcher_macos
387-
sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f
387+
sha256: "8262208506252a3ed4ff5c0dc1e973d2c0e0ef337d0a074d35634da5d44397c9"
388388
url: "https://pub.dev"
389389
source: hosted
390-
version: "3.2.3"
390+
version: "3.2.4"
391391
url_launcher_platform_interface:
392392
dependency: transitive
393393
description:

example/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
1616
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
1717
# In Windows, build-name is used as the major, minor, and patch parts
1818
# of the product and file versions while build-number is used as the build suffix.
19-
version: 1.0.51+51
19+
version: 1.0.54+54
2020

2121
environment:
2222
sdk: ^3.9.2

example/remoteconfig.template.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@
2727
},
2828
"description": "Latest available version per track. JSON {\"stable\": \"1.0.29\", \"beta\": \"1.0.30\"}",
2929
"valueType": "STRING"
30+
},
31+
"redirect_url": {
32+
"defaultValue": { "value": "{\"android\": {\"stable\": \"https://play.google.com/store/apps/details?id=com.example.stable\", \"beta\": \"https://play.google.com/store/apps/details?id=com.example.beta\", \"staging\": \"https://play.google.com/store/apps/details?id=com.example.staging\"}, \"ios\": {\"stable\": \"https://apps.apple.com/app/id1234567890\", \"beta\": \"https://apps.apple.com/app/id1234567891\", \"staging\": \"https://apps.apple.com/app/id1234567892\"}}" },
33+
"description": "Redirection URLs for each platform and track. JSON {\"android\": {\"stable\": \"<url>\", ...}, \"ios\": {\"stable\": \"<url>\", ...}}",
34+
"valueType": "STRING"
3035
}
3136
}
3237
}

lib/src/remote_config/remote_config_service.dart

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'dart:convert';
2+
import 'dart:io';
23
import 'package:firebase_remote_config/firebase_remote_config.dart';
34
import 'package:flutter/cupertino.dart';
45
import 'package:package_info_plus/package_info_plus.dart';
@@ -74,6 +75,18 @@ class RemoteConfigService {
7475
RemoteConfigVariables.latestVersion: _packageInfo.version,
7576
RemoteConfigVariables.patchEnabled: false,
7677
RemoteConfigVariables.patchInfo: '{}',
78+
RemoteConfigVariables.redirectUrl: jsonEncode({
79+
"android": {
80+
"stable": "",
81+
"beta": "",
82+
"staging": "",
83+
},
84+
"ios": {
85+
"stable": "",
86+
"beta": "",
87+
"staging": "",
88+
},
89+
}),
7790
});
7891

7992
await _remoteConfig.fetchAndActivate();
@@ -209,4 +222,47 @@ class RemoteConfigService {
209222
return null;
210223
}
211224
}
225+
226+
/// Get redirection URL for the current or specified track, platform-specific only
227+
String getRedirectUrl({UpdateTrackType? track}) {
228+
final configValue =
229+
_remoteConfig.getString(RemoteConfigVariables.redirectUrl);
230+
231+
if (configValue.isEmpty) {
232+
debugPrint("Redirect URL not found in Remote Config. Using default.");
233+
return "";
234+
}
235+
236+
try {
237+
final dynamic parsed = jsonDecode(configValue);
238+
final currentTrack = track ?? _currentTrack;
239+
240+
if (parsed is! Map<String, dynamic>) {
241+
return "";
242+
}
243+
244+
// Determine platform
245+
final platformKey = Platform.isAndroid ? 'android' : 'ios';
246+
final platformUrls = parsed[platformKey];
247+
248+
if (platformUrls is Map<String, dynamic>) {
249+
final trackUrl = platformUrls[currentTrack.name];
250+
if (trackUrl != null && trackUrl is String && trackUrl.isNotEmpty) {
251+
return trackUrl;
252+
} else {
253+
// Platform exists but track URL not found
254+
debugPrint(
255+
"Track '${currentTrack.name}' not found for platform '$platformKey'");
256+
return "";
257+
}
258+
}
259+
260+
// Platform not found
261+
debugPrint("Platform '$platformKey' not found in redirect_url");
262+
return "";
263+
} catch (e) {
264+
debugPrint("Redirect URL parse error: $e");
265+
return "";
266+
}
267+
}
212268
}

lib/src/remote_config/remote_config_variables.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ class RemoteConfigVariables {
33
static const String latestVersion = "latest_version";
44
static const String patchEnabled = "patch_enabled";
55
static const String patchInfo = "patch_info";
6+
static const String redirectUrl = "redirect_url";
67
}

lib/src/ui/ui_handler/ui_handler.dart

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ class UpdateUIHandler {
4141
static const String _patchBannerKey = 'patch_banner';
4242
static const String _patchOverlayKey = 'patch_overlay';
4343

44+
// Remote Config Service
45+
final remoteConfigService = RemoteConfigService.instance;
46+
4447
UpdateUIHandler({
4548
required this.context,
4649
required this.config,
@@ -920,36 +923,47 @@ class UpdateUIHandler {
920923

921924
Future<void> _navigateToStore() async {
922925
final isAndroid = Theme.of(context).platform == TargetPlatform.android;
923-
final url = isAndroid
924-
? config.androidPlayStoreUrl
925-
: config.iosAppStoreUrl ?? config.iosTestFlightUrl;
926+
927+
final redirectUrl = remoteConfigService.getRedirectUrl();
928+
final url = (redirectUrl.isEmpty)
929+
? (isAndroid
930+
? config.androidPlayStoreUrl
931+
: config.iosAppStoreUrl ?? config.iosTestFlightUrl)
932+
: redirectUrl;
926933

927934
if (url != null && await canLaunchUrl(Uri.parse(url))) {
928935
await launchUrl(
929936
Uri.parse(url),
930937
mode: LaunchMode.externalApplication,
931938
);
939+
940+
// After returning from the store, recheck the update.
941+
WidgetsBinding.instance.addPostFrameCallback((_) {
942+
_waitForAppResumed();
943+
});
932944
} else {
933945
debugPrint('Could not launch store URL: $url');
934946
if (!context.mounted) return;
935-
ScaffoldMessenger.of(context).showSnackBar(
936-
const SnackBar(
937-
content: Text('Could not open store. Please update manually.'),
938-
),
947+
ScaffoldMessenger.of(context)
948+
.showSnackBar(
949+
const SnackBar(
950+
content: Text('Could not open store. Please update manually.'),
951+
),
952+
)
953+
.closed
954+
.then(
955+
(value) async {
956+
// Immediately check for updates here since user didn't leave app
957+
await remoteConfigService.handleUpdateCheck();
958+
},
939959
);
940960
}
941-
942-
// After returning from the store, recheck the update.
943-
WidgetsBinding.instance.addPostFrameCallback((_) {
944-
_waitForAppResumed();
945-
});
946961
}
947962

948963
void _waitForAppResumed() {
949964
WidgetsBinding.instance.addObserver(
950965
_AppLifecycleObserver(
951966
onResumed: () async {
952-
final remoteConfigService = RemoteConfigService.instance;
953967
await remoteConfigService.handleUpdateCheck();
954968
},
955969
),

lib/src/update_manager.dart

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -141,25 +141,30 @@ class UpdateManager {
141141

142142
_lastUpdateType = type;
143143

144-
// Only trigger Shorebird check if patch source
145-
if (source == UpdateSource.patch && patchNumber != null) {
146-
if (enableShorebird &&
147-
_shorebirdService != null &&
148-
_shorebirdService!.isAvailable) {
149-
await checkShorebirdPatch(track: currentTrackType);
150-
return; // Don't show store UI for patch updates
151-
}
152-
}
153-
154-
// Show store update UI
144+
// --- Step 1: Handle store (release) updates first ---
155145
if (type != UpdateType.none) {
156146
if (onUpdate == null && _uiHandler != null) {
157-
await _uiHandler!.handleStoreUpdate(type: type, source: source);
147+
await _uiHandler!
148+
.handleStoreUpdate(type: type, source: UpdateSource.release);
158149
}
159150

160151
if (onUpdate != null) {
161-
await onUpdate!(type: type, source: source, patchNumber: patchNumber);
152+
await onUpdate!(
153+
type: type, source: UpdateSource.release, patchNumber: patchNumber);
162154
}
155+
156+
// If it's a forced update, stop further checks (don’t continue to Shorebird)
157+
if (type == UpdateType.force) return;
158+
}
159+
160+
// --- Step 2: Only check Shorebird patch updates after handling store updates ---
161+
if (enableShorebird &&
162+
source == UpdateSource.patch &&
163+
patchNumber != null &&
164+
_shorebirdService != null &&
165+
_shorebirdService!.isAvailable) {
166+
await checkShorebirdPatch(track: currentTrackType);
167+
return; // Don't show store UI for patch updates
163168
}
164169
}
165170

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: update_manager
22
description: "A Flutter package to manage app updates with Firebase Remote Config and Shorebird, supporting force, optional, and patch updates easily."
3-
version: 1.2.0
3+
version: 1.2.1
44
homepage: "https://github.com/jayadevpanthaplavil/update-manager"
55
repository: "https://github.com/jayadevpanthaplavil/update-manager"
66
issue_tracker: "https://github.com/jayadevpanthaplavil/update-manager/issues"

0 commit comments

Comments
 (0)