Switched to WorkManager for reliability (#608)

This commit is contained in:
Imran Remtulla
2023-10-22 00:59:51 -04:00
parent b8c0e18bb4
commit d3247a9ec1
5 changed files with 280 additions and 263 deletions

View File

@@ -12,7 +12,7 @@ import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:dynamic_color/dynamic_color.dart'; import 'package:dynamic_color/dynamic_color.dart';
import 'package:device_info_plus/device_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart';
import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart'; import 'package:workmanager/workmanager.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
// ignore: implementation_imports // ignore: implementation_imports
import 'package:easy_localization/src/easy_localization_controller.dart'; import 'package:easy_localization/src/easy_localization_controller.dart';
@@ -23,7 +23,7 @@ const String currentVersion = '0.14.31';
const String currentReleaseTag = const String currentReleaseTag =
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES 'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
const int bgUpdateCheckAlarmId = 666; const String bgUpdateTaskId = 'bgUpdate';
List<MapEntry<Locale, String>> supportedLocales = const [ List<MapEntry<Locale, String>> supportedLocales = const [
MapEntry(Locale('en'), 'English'), MapEntry(Locale('en'), 'English'),
@@ -71,6 +71,17 @@ Future<void> loadTranslations() async {
fallbackTranslations: controller.fallbackTranslations); fallbackTranslations: controller.fallbackTranslations);
} }
@pragma('vm:entry-point')
void bgTaskDispatcher() {
Workmanager().executeTask((taskId, params) {
if (taskId == bgUpdateTaskId) {
return bgUpdateTask(taskId, params);
} else {
return Future.value(true);
}
});
}
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
try { try {
@@ -88,7 +99,7 @@ void main() async {
); );
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
} }
await AndroidAlarmManager.initialize(); await Workmanager().initialize(bgTaskDispatcher);
runApp(MultiProvider( runApp(MultiProvider(
providers: [ providers: [
ChangeNotifierProvider(create: (context) => AppsProvider()), ChangeNotifierProvider(create: (context) => AppsProvider()),
@@ -158,7 +169,7 @@ class _ObtainiumState extends State<Obtainium> {
var actualUpdateInterval = settingsProvider.updateInterval; var actualUpdateInterval = settingsProvider.updateInterval;
if (existingUpdateInterval != actualUpdateInterval) { if (existingUpdateInterval != actualUpdateInterval) {
if (actualUpdateInterval == 0) { if (actualUpdateInterval == 0) {
AndroidAlarmManager.cancel(bgUpdateCheckAlarmId); Workmanager().cancelByUniqueName(bgUpdateTaskId);
} else { } else {
var settingChanged = existingUpdateInterval != -1; var settingChanged = existingUpdateInterval != -1;
var lastCheckWasTooLongAgo = actualUpdateInterval != 0 && var lastCheckWasTooLongAgo = actualUpdateInterval != 0 &&
@@ -168,12 +179,10 @@ class _ObtainiumState extends State<Obtainium> {
if (settingChanged || lastCheckWasTooLongAgo) { if (settingChanged || lastCheckWasTooLongAgo) {
logs.add( logs.add(
'Update interval was set to ${actualUpdateInterval.toString()} (reason: ${settingChanged ? 'setting changed' : 'last check was ${settingsProvider.lastBGCheckTime.toLocal().toString()}'}).'); 'Update interval was set to ${actualUpdateInterval.toString()} (reason: ${settingChanged ? 'setting changed' : 'last check was ${settingsProvider.lastBGCheckTime.toLocal().toString()}'}).');
AndroidAlarmManager.periodic( Workmanager().registerPeriodicTask(
Duration(minutes: actualUpdateInterval), bgUpdateTaskId, "BG Update Main Loop",
bgUpdateCheckAlarmId, initialDelay: Duration(minutes: actualUpdateInterval),
bgUpdateCheck, existingWorkPolicy: ExistingWorkPolicy.replace);
rescheduleOnReboot: true,
wakeup: true);
} }
} }
existingUpdateInterval = actualUpdateInterval; existingUpdateInterval = actualUpdateInterval;

View File

@@ -1,4 +1,4 @@
import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart'; import 'package:workmanager/workmanager.dart';
import 'package:device_info_plus/device_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -591,10 +591,10 @@ class _SettingsPageState extends State<SettingsPage> {
height16, height16,
TextButton( TextButton(
onPressed: () { onPressed: () {
AndroidAlarmManager.oneShot( Workmanager().registerOneOffTask(
const Duration(seconds: 0), '$bgUpdateTaskId+Manual', bgUpdateTaskId,
bgUpdateCheckAlarmId + 200, existingWorkPolicy:
bgUpdateCheck); ExistingWorkPolicy.replace);
showMessage(tr('bgTaskStarted'), context); showMessage(tr('bgTaskStarted'), context);
}, },
child: Text(tr('runBgCheckNow'))) child: Text(tr('runBgCheckNow')))

View File

@@ -7,7 +7,7 @@ import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart'; import 'package:workmanager/workmanager.dart';
import 'package:android_intent_plus/flag.dart'; import 'package:android_intent_plus/flag.dart';
import 'package:android_package_installer/android_package_installer.dart'; import 'package:android_package_installer/android_package_installer.dart';
import 'package:android_package_manager/android_package_manager.dart'; import 'package:android_package_manager/android_package_manager.dart';
@@ -1356,268 +1356,276 @@ class _APKOriginWarningDialogState extends State<APKOriginWarningDialog> {
/// If there is an error, the offending app is moved to the back of the line of remaining apps, and the task is retried. /// If there is an error, the offending app is moved to the back of the line of remaining apps, and the task is retried.
/// If an app repeatedly fails to install up to its retry limit, the user is notified. /// If an app repeatedly fails to install up to its retry limit, the user is notified.
/// ///
@pragma('vm:entry-point') Future<bool> bgUpdateTask(String taskId, Map<String, dynamic>? params) async {
Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized(); await EasyLocalization.ensureInitialized();
await AndroidAlarmManager.initialize(); await Workmanager().initialize(bgTaskDispatcher);
await loadTranslations(); await loadTranslations();
LogsProvider logs = LogsProvider(); LogsProvider logs = LogsProvider();
NotificationsProvider notificationsProvider = NotificationsProvider(); try {
AppsProvider appsProvider = AppsProvider(isBg: true); NotificationsProvider notificationsProvider = NotificationsProvider();
await appsProvider.loadApps(); AppsProvider appsProvider = AppsProvider(isBg: true);
await appsProvider.loadApps();
int maxAttempts = 4; int maxAttempts = 4;
params ??= {}; params ??= {};
if (params['toCheck'] == null) { if (params['toCheck'] == null) {
appsProvider.settingsProvider.lastBGCheckTime = DateTime.now(); appsProvider.settingsProvider.lastBGCheckTime = DateTime.now();
} }
List<MapEntry<String, int>> toCheck = <MapEntry<String, int>>[ List<MapEntry<String, int>> toCheck = <MapEntry<String, int>>[
...(params['toCheck'] ...(params['toCheck']?.map((str) {
?.map((entry) => MapEntry<String, int>( var temp = str.split(',');
entry['key'] as String, entry['value'] as int)) return MapEntry<String, int>(temp[0], int.parse(temp[1]));
.toList() ?? }).toList() ??
appsProvider appsProvider
.getAppsSortedByUpdateCheckTime( .getAppsSortedByUpdateCheckTime(
onlyCheckInstalledOrTrackOnlyApps: appsProvider onlyCheckInstalledOrTrackOnlyApps: appsProvider
.settingsProvider.onlyCheckInstalledOrTrackOnlyApps) .settingsProvider.onlyCheckInstalledOrTrackOnlyApps)
.map((e) => MapEntry(e, 0))) .map((e) => MapEntry(e, 0)))
]; ];
List<MapEntry<String, int>> toInstall = <MapEntry<String, int>>[ List<MapEntry<String, int>> toInstall = <MapEntry<String, int>>[
...(params['toInstall'] ...(params['toInstall']?.map((str) {
?.map((entry) => MapEntry<String, int>( var temp = str.split(',');
entry['key'] as String, entry['value'] as int)) return MapEntry<String, int>(temp[0], int.parse(temp[1]));
.toList() ?? }).toList() ??
(<List<MapEntry<String, int>>>[])) (<List<MapEntry<String, int>>>[]))
]; ];
var netResult = await (Connectivity().checkConnectivity()); var netResult = await (Connectivity().checkConnectivity());
if (netResult == ConnectivityResult.none) { if (netResult == ConnectivityResult.none) {
var networkBasedRetryInterval = 15; var networkBasedRetryInterval = 15;
var nextRegularCheck = appsProvider.settingsProvider.lastBGCheckTime var nextRegularCheck = appsProvider.settingsProvider.lastBGCheckTime
.add(Duration(minutes: appsProvider.settingsProvider.updateInterval)); .add(Duration(minutes: appsProvider.settingsProvider.updateInterval));
var potentialNetworkRetryCheck = var potentialNetworkRetryCheck =
DateTime.now().add(Duration(minutes: networkBasedRetryInterval)); DateTime.now().add(Duration(minutes: networkBasedRetryInterval));
var shouldRetry = potentialNetworkRetryCheck.isBefore(nextRegularCheck); var shouldRetry = potentialNetworkRetryCheck.isBefore(nextRegularCheck);
logs.add( logs.add(
'BG update task $taskId: No network. Will ${shouldRetry ? 'retry in $networkBasedRetryInterval minutes' : 'not retry'}.'); 'BG task $taskId: No network. Will ${shouldRetry ? 'retry in $networkBasedRetryInterval minutes' : 'not retry'}.');
AndroidAlarmManager.oneShot( await Workmanager().registerOneOffTask("$taskId+Retry", taskId,
const Duration(minutes: 15), taskId + 1, bgUpdateCheck, initialDelay: const Duration(minutes: 15),
params: { existingWorkPolicy: ExistingWorkPolicy.replace,
'toCheck': toCheck inputData: {
.map((entry) => {'key': entry.key, 'value': entry.value}) 'toCheck':
.toList(), toCheck.map((entry) => '${entry.key},${entry.value}').toList(),
'toInstall': toInstall 'toInstall': toInstall
.map((entry) => {'key': entry.key, 'value': entry.value}) .map((entry) => '${entry.key},${entry.value}')
.toList(), .toList(),
}); });
return; }
}
var networkRestricted = false;
if (appsProvider.settingsProvider.bgUpdatesOnWiFiOnly) {
networkRestricted = (netResult != ConnectivityResult.wifi) &&
(netResult != ConnectivityResult.ethernet);
}
bool installMode =
toCheck.isEmpty; // Task is either in update mode or install mode
logs.add(
'BG ${installMode ? 'install' : 'update'} task $taskId: Started (${installMode ? toInstall.length : toCheck.length}).');
if (!installMode) {
// If in update mode, we check for updates.
// We divide the results into 4 groups:
// - toNotify - Apps with updates that the user will be notified about (can't be silently installed)
// - toRetry - Apps with update check errors that will be retried in a while
// - toThrow - Apps with update check errors that the user will be notified about (no retry)
// After grouping the updates, we take care of toNotify and toThrow first
// Then if toRetry is not empty, we schedule another update task to run in a while
// If toRetry is empty, we take care of schedule another task that will run in install mode (toCheck is empty)
// Init. vars.
List<App> updates = []; // All updates found (silent and non-silent)
List<App> toNotify =
[]; // All non-silent updates that the user will be notified about
List<MapEntry<String, int>> toRetry =
[]; // All apps that got errors while checking
var retryAfterXSeconds =
0; // How long to wait until the next attempt (if there are errors)
MultiAppMultiError?
errors; // All errors including those that will lead to a retry
MultiAppMultiError toThrow =
MultiAppMultiError(); // All errors that will not lead to a retry, just a notification
CheckingUpdatesNotification notif = CheckingUpdatesNotification(
plural('apps', toCheck.length)); // The notif. to show while checking
// Set a bool for when we're no on wifi/wired and the user doesn't want to download apps in that state
var networkRestricted = false; var networkRestricted = false;
if (appsProvider.settingsProvider.bgUpdatesOnWiFiOnly) { if (appsProvider.settingsProvider.bgUpdatesOnWiFiOnly) {
var netResult = await (Connectivity().checkConnectivity());
networkRestricted = (netResult != ConnectivityResult.wifi) && networkRestricted = (netResult != ConnectivityResult.wifi) &&
(netResult != ConnectivityResult.ethernet); (netResult != ConnectivityResult.ethernet);
} }
try { bool installMode =
// Check for updates toCheck.isEmpty; // Task is either in update mode or install mode
notificationsProvider.notify(notif, cancelExisting: true);
updates = await appsProvider.checkUpdates(
specificIds: toCheck.map((e) => e.key).toList(),
sp: appsProvider.settingsProvider);
} catch (e) {
// If there were errors, group them into toRetry and toThrow based on max retry count per app
if (e is Map) {
updates = e['updates'];
errors = e['errors'];
errors!.rawErrors.forEach((key, err) {
logs.add(
'BG update task $taskId: Got error on checking for $key \'${err.toString()}\'.');
var toCheckApp = toCheck.where((element) => element.key == key).first;
if (toCheckApp.value < maxAttempts) {
toRetry.add(MapEntry(toCheckApp.key, toCheckApp.value + 1));
// Next task interval is based on the error with the longest retry time
var minRetryIntervalForThisApp = err is RateLimitError
? (err.remainingMinutes * 60)
: e is ClientException
? (15 * 60)
: pow(toCheckApp.value + 1, 2).toInt();
if (minRetryIntervalForThisApp > retryAfterXSeconds) {
retryAfterXSeconds = minRetryIntervalForThisApp;
}
} else {
toThrow.add(key, err, appName: errors?.appIdNames[key]);
}
});
} else {
// We don't expect to ever get here in any situation so no need to catch (but log it in case)
logs.add('Fatal error in BG update task: ${e.toString()}');
rethrow;
}
} finally {
notificationsProvider.cancel(notif.id);
}
// Filter out updates that will be installed silently (the rest go into toNotify) logs.add(
for (var i = 0; i < updates.length; i++) { 'BG ${installMode ? 'install' : 'update'} task $taskId: Started (${installMode ? toInstall.length : toCheck.length}).');
if (networkRestricted ||
!(await appsProvider.canInstallSilently(updates[i]))) {
if (updates[i].additionalSettings['skipUpdateNotifications'] != true) {
toNotify.add(updates[i]);
}
}
}
// Send the update notification if (!installMode) {
if (toNotify.isNotEmpty) { // If in update mode, we check for updates.
notificationsProvider.notify(UpdateNotification(toNotify)); // We divide the results into 4 groups:
} // - toNotify - Apps with updates that the user will be notified about (can't be silently installed)
// - toRetry - Apps with update check errors that will be retried in a while
// - toThrow - Apps with update check errors that the user will be notified about (no retry)
// After grouping the updates, we take care of toNotify and toThrow first
// Then if toRetry is not empty, we schedule another update task to run in a while
// If toRetry is empty, we take care of schedule another task that will run in install mode (toCheck is empty)
// Send the error notifications (grouped by error string) // Init. vars.
if (toThrow.rawErrors.isNotEmpty) { List<App> updates = []; // All updates found (silent and non-silent)
for (var element in toThrow.idsByErrorString.entries) { List<App> toNotify =
notificationsProvider.notify(ErrorCheckingUpdatesNotification( []; // All non-silent updates that the user will be notified about
errors!.errorsAppsString(element.key, element.value), List<MapEntry<String, int>> toRetry =
id: Random().nextInt(10000))); []; // All apps that got errors while checking
} var retryAfterXSeconds =
} 0; // How long to wait until the next attempt (if there are errors)
MultiAppMultiError?
errors; // All errors including those that will lead to a retry
MultiAppMultiError toThrow =
MultiAppMultiError(); // All errors that will not lead to a retry, just a notification
CheckingUpdatesNotification notif = CheckingUpdatesNotification(
plural('apps', toCheck.length)); // The notif. to show while checking
// if there are update checks to retry, schedule a retry task // Set a bool for when we're no on wifi/wired and the user doesn't want to download apps in that state
if (toRetry.isNotEmpty) { var networkRestricted = false;
logs.add( if (appsProvider.settingsProvider.bgUpdatesOnWiFiOnly) {
'BG update task $taskId: Will retry in $retryAfterXSeconds seconds.'); var netResult = await (Connectivity().checkConnectivity());
AndroidAlarmManager.oneShot( networkRestricted = (netResult != ConnectivityResult.wifi) &&
Duration(seconds: retryAfterXSeconds), taskId + 1, bgUpdateCheck, (netResult != ConnectivityResult.ethernet);
params: {
'toCheck': toRetry
.map((entry) => {'key': entry.key, 'value': entry.value})
.toList(),
'toInstall': toInstall
.map((entry) => {'key': entry.key, 'value': entry.value})
.toList(),
});
} else {
// If there are no more update checks, schedule an install task
logs.add(
'BG update task $taskId: Done. Scheduling install task to run immediately.');
AndroidAlarmManager.oneShot(
const Duration(minutes: 0), taskId + 1, bgUpdateCheck,
params: {
'toCheck': [],
'toInstall': toInstall
.map((entry) => {'key': entry.key, 'value': entry.value})
.toList()
});
}
} else {
// In install mode...
// If you haven't explicitly been given updates to install (which is the case for new tasks), grab all available silent updates
if (toInstall.isEmpty && !networkRestricted) {
var temp = appsProvider.findExistingUpdates(installedOnly: true);
for (var i = 0; i < temp.length; i++) {
if (await appsProvider
.canInstallSilently(appsProvider.apps[temp[i]]!.app)) {
toInstall.add(MapEntry(temp[i], 0));
}
} }
}
var didCompleteInstalling = false;
var tempObtArr = toInstall.where((element) => element.key == obtainiumId);
if (tempObtArr.isNotEmpty) {
// Move obtainium to the end of the list as it must always install last
var obt = tempObtArr.first;
toInstall = moveStrToEndMapEntryWithCount(toInstall, obt);
}
// Loop through all updates and install each
for (var i = 0; i < toInstall.length; i++) {
var appId = toInstall[i].key;
var retryCount = toInstall[i].value;
try { try {
logs.add( // Check for updates
'BG install task $taskId: Attempting to update $appId in the background.'); notificationsProvider.notify(notif, cancelExisting: true);
await appsProvider.downloadAndInstallLatestApps([appId], null, updates = await appsProvider.checkUpdates(
notificationsProvider: notificationsProvider); specificIds: toCheck.map((e) => e.key).toList(),
await Future.delayed(const Duration( sp: appsProvider.settingsProvider);
seconds:
5)); // Just in case task ending causes install fail (not clear)
if (i == (toCheck.length - 1)) {
didCompleteInstalling = true;
}
} catch (e) { } catch (e) {
// If you got an error, move the offender to the back of the line (increment their fail count) and schedule another task to continue installing shortly // If there were errors, group them into toRetry and toThrow based on max retry count per app
logs.add( if (e is Map) {
'BG install task $taskId: Got error on updating $appId \'${e.toString()}\'.'); updates = e['updates'];
if (retryCount < maxAttempts) { errors = e['errors'];
var remainingSeconds = retryCount; errors!.rawErrors.forEach((key, err) {
logs.add( logs.add(
'BG install task $taskId: Will continue in $remainingSeconds seconds (with $appId moved to the end of the line).'); 'BG task $taskId: Got error on checking for $key \'${err.toString()}\'.');
var remainingToInstall = moveStrToEndMapEntryWithCount( var toCheckApp =
toInstall.sublist(i), MapEntry(appId, retryCount + 1)); toCheck.where((element) => element.key == key).first;
AndroidAlarmManager.oneShot( if (toCheckApp.value < maxAttempts) {
Duration(seconds: remainingSeconds), taskId + 1, bgUpdateCheck, toRetry.add(MapEntry(toCheckApp.key, toCheckApp.value + 1));
params: { // Next task interval is based on the error with the longest retry time
'toCheck': toCheck var minRetryIntervalForThisApp = err is RateLimitError
.map((entry) => {'key': entry.key, 'value': entry.value}) ? (err.remainingMinutes * 60)
.toList(), : e is ClientException
'toInstall': remainingToInstall ? (15 * 60)
.map((entry) => {'key': entry.key, 'value': entry.value}) : pow(toCheckApp.value + 1, 2).toInt();
.toList(), if (minRetryIntervalForThisApp > retryAfterXSeconds) {
}); retryAfterXSeconds = minRetryIntervalForThisApp;
break; }
} else {
toThrow.add(key, err, appName: errors?.appIdNames[key]);
}
});
} else { } else {
// If the offender has reached its fail limit, notify the user and remove it from the list (task can continue) // We don't expect to ever get here in any situation so no need to catch (but log it in case)
toInstall.removeAt(i); logs.add('Fatal error in BG task: ${e.toString()}');
i--; rethrow;
notificationsProvider }
.notify(ErrorCheckingUpdatesNotification(e.toString())); } finally {
notificationsProvider.cancel(notif.id);
}
// Filter out updates that will be installed silently (the rest go into toNotify)
for (var i = 0; i < updates.length; i++) {
if (networkRestricted ||
!(await appsProvider.canInstallSilently(updates[i]))) {
if (updates[i].additionalSettings['skipUpdateNotifications'] !=
true) {
toNotify.add(updates[i]);
}
} }
} }
// Send the update notification
if (toNotify.isNotEmpty) {
notificationsProvider.notify(UpdateNotification(toNotify));
}
// Send the error notifications (grouped by error string)
if (toThrow.rawErrors.isNotEmpty) {
for (var element in toThrow.idsByErrorString.entries) {
notificationsProvider.notify(ErrorCheckingUpdatesNotification(
errors!.errorsAppsString(element.key, element.value),
id: Random().nextInt(10000)));
}
}
// if there are update checks to retry, schedule a retry task
if (toRetry.isNotEmpty) {
logs.add('BG task $taskId: Will retry in $retryAfterXSeconds seconds.');
await Workmanager().registerOneOffTask("$taskId+Retry", taskId,
initialDelay: Duration(seconds: retryAfterXSeconds),
existingWorkPolicy: ExistingWorkPolicy.replace,
inputData: {
'toCheck': toRetry
.map((entry) => '${entry.key},${entry.value}')
.toList(),
'toInstall': toInstall
.map((entry) => '${entry.key},${entry.value}')
.toList(),
});
} else {
// If there are no more update checks, schedule an install task
logs.add(
'BG task $taskId: Done. Scheduling install task to run immediately.');
await Workmanager().registerOneOffTask(
"$bgUpdateTaskId+Install", taskId,
existingWorkPolicy: ExistingWorkPolicy.replace,
inputData: {
'toCheck': <String>[],
'toInstall': toInstall
.map((entry) => '${entry.key},${entry.value}')
.toList()
});
}
} else {
// In install mode...
// If you haven't explicitly been given updates to install (which is the case for new tasks), grab all available silent updates
if (toInstall.isEmpty && !networkRestricted) {
var temp = appsProvider.findExistingUpdates(installedOnly: true);
for (var i = 0; i < temp.length; i++) {
if (await appsProvider
.canInstallSilently(appsProvider.apps[temp[i]]!.app)) {
toInstall.add(MapEntry(temp[i], 0));
}
}
}
var didCompleteInstalling = false;
var tempObtArr = toInstall.where((element) => element.key == obtainiumId);
if (tempObtArr.isNotEmpty) {
// Move obtainium to the end of the list as it must always install last
var obt = tempObtArr.first;
toInstall = moveStrToEndMapEntryWithCount(toInstall, obt);
}
// Loop through all updates and install each
for (var i = 0; i < toInstall.length; i++) {
var appId = toInstall[i].key;
var retryCount = toInstall[i].value;
try {
logs.add(
'BG task $taskId: Attempting to update $appId in the background.');
await appsProvider.downloadAndInstallLatestApps([appId], null,
notificationsProvider: notificationsProvider);
await Future.delayed(const Duration(
seconds:
5)); // Just in case task ending causes install fail (not clear)
if (i == (toCheck.length - 1)) {
didCompleteInstalling = true;
}
} catch (e) {
// If you got an error, move the offender to the back of the line (increment their fail count) and schedule another task to continue installing shortly
logs.add(
'BG task $taskId: Got error on updating $appId \'${e.toString()}\'.');
if (retryCount < maxAttempts) {
var remainingSeconds = retryCount;
logs.add(
'BG task $taskId: Will continue in $remainingSeconds seconds (with $appId moved to the end of the line).');
var remainingToInstall = moveStrToEndMapEntryWithCount(
toInstall.sublist(i), MapEntry(appId, retryCount + 1));
await Workmanager().registerOneOffTask("$taskId+Retry", taskId,
initialDelay: Duration(seconds: remainingSeconds),
existingWorkPolicy: ExistingWorkPolicy.replace,
inputData: {
'toCheck': toCheck
.map((entry) => '${entry.key},${entry.value}')
.toList(),
'toInstall': remainingToInstall
.map((entry) => '${entry.key},${entry.value}')
.toList(),
});
break;
} else {
// If the offender has reached its fail limit, notify the user and remove it from the list (task can continue)
toInstall.removeAt(i);
i--;
notificationsProvider
.notify(ErrorCheckingUpdatesNotification(e.toString()));
}
}
}
if (didCompleteInstalling || toInstall.isEmpty) {
logs.add('BG task $taskId: Done.');
}
} }
if (didCompleteInstalling || toInstall.isEmpty) { return true;
logs.add('BG install task $taskId: Done.'); } catch (e) {
} logs.add(e.toString());
return false;
} }
} }

View File

@@ -1,14 +1,6 @@
# Generated by pub # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
android_alarm_manager_plus:
dependency: "direct main"
description:
name: android_alarm_manager_plus
sha256: "82fb28c867c4b3dd7e9157728e46426b8916362f977dbba46b949210f00099f4"
url: "https://pub.dev"
source: hosted
version: "3.0.3"
android_intent_plus: android_intent_plus:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -931,6 +923,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.2" version: "1.1.2"
workmanager:
dependency: "direct main"
description:
name: workmanager
sha256: ed13530cccd28c5c9959ad42d657cd0666274ca74c56dea0ca183ddd527d3a00
url: "https://pub.dev"
source: hosted
version: "0.5.2"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
@@ -956,5 +956,5 @@ packages:
source: hosted source: hosted
version: "3.1.2" version: "3.1.2"
sdks: sdks:
dart: ">=3.1.0 <4.0.0" dart: ">=3.1.2 <4.0.0"
flutter: ">=3.13.0" flutter: ">=3.13.0"

View File

@@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 0.14.31+223 # When changing this, update the tag in main() accordingly version: 0.14.31+224 # When changing this, update the tag in main() accordingly
environment: environment:
sdk: '>=3.0.0 <4.0.0' sdk: '>=3.0.0 <4.0.0'
@@ -57,7 +57,6 @@ dependencies:
ref: main ref: main
android_package_manager: ^0.6.0 android_package_manager: ^0.6.0
share_plus: ^7.0.0 share_plus: ^7.0.0
android_alarm_manager_plus: ^3.0.0
sqflite: ^2.2.0+3 sqflite: ^2.2.0+3
easy_localization: ^3.0.1 easy_localization: ^3.0.1
android_intent_plus: ^4.0.0 android_intent_plus: ^4.0.0
@@ -66,6 +65,7 @@ dependencies:
hsluv: ^1.1.3 hsluv: ^1.1.3
connectivity_plus: ^5.0.0 connectivity_plus: ^5.0.0
shared_storage: ^0.8.0 shared_storage: ^0.8.0
workmanager: ^0.5.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: