-
Notifications
You must be signed in to change notification settings - Fork 166
Description
We are experiencing a critical issue where pub.dev is serving incorrect package content for flutter_foreground_task version 9.1.0.
- Initially, this was observed in Dubai, UAE. The downloaded flutter_foreground_task-9.1.0.tar.gz has an SHA-256 hash of 9F1B25A81DB95D7119D2C5CFFC654048CBDD49D4056183E1BEADC1A6A38F3E29.
- This hash corresponds to code from an older version (appears to be v3.10.0 or similar), not v9.1.0, leading to API mismatches (e.g., missing printDevLog in init).
- Crucially, a friend in the UK has also downloaded the package from pub.dev today ([8/12/2025]) and sent the file to me, and when I checked the SHA-256 hash it is the exact same incorrect SHA-256 hash: 9F1B25A81DB95D7119D2C5CFFC654048CBDD49D4056183E1BEADC1A6A38F3E29.
- The expected SHA-256 hash for the correct v9.1.0 (verified by other developers in different regions previously, or by checking older, correctly cached versions if available) is F15A8501865048A092829281A18277C0879007808269A94018698991738D1847.
- This indicates a widespread problem with the distribution of flutter_foreground_task v9.1.0 on pub.dev, potentially affecting multiple regions or even being a global issue with the uploaded package itself.
- This prevents users from utilizing the correct version and is causing build failures. Please investigate urgently with the pub.dev team.
- But if am wrong please inform me.
here is the source code inside the flutter_foreground_task.dart
"
import 'dart:async';
import 'dart:isolate';
import 'dart:ui';
import 'package:flutter/widgets.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'flutter_foreground_task_platform_interface.dart';
import 'errors/service_already_started_exception.dart';
import 'errors/service_not_initialized_exception.dart';
import 'errors/service_not_started_exception.dart';
import 'errors/service_timeout_exception.dart';
import 'models/foreground_service_types.dart';
import 'models/foreground_task_options.dart';
import 'models/notification_button.dart';
import 'models/notification_icon.dart';
import 'models/notification_options.dart';
import 'models/notification_permission.dart';
import 'models/service_request_result.dart';
import 'task_handler.dart';
import 'utils/utility.dart';
export 'errors/service_already_started_exception.dart';
export 'errors/service_not_initialized_exception.dart';
export 'errors/service_not_started_exception.dart';
export 'errors/service_timeout_exception.dart';
export 'models/foreground_service_types.dart';
export 'models/foreground_task_event_action.dart';
export 'models/foreground_task_options.dart';
export 'models/notification_button.dart';
export 'models/notification_channel_importance.dart';
export 'models/notification_icon.dart';
export 'models/notification_options.dart';
export 'models/notification_permission.dart';
export 'models/notification_priority.dart';
export 'models/notification_visibility.dart';
export 'models/service_request_result.dart';
export 'ui/with_foreground_task.dart';
export 'task_handler.dart';
const String _kPortName = 'flutter_foreground_task/isolateComPort';
const String _kPrefsKeyPrefix = 'com.pravera.flutter_foreground_task.prefs.';
typedef DataCallback = void Function(Object data);
/// A class that implements foreground task and provides useful utilities.
class FlutterForegroundTask {
// ====================== Service ======================
@VisibleForTesting
static AndroidNotificationOptions? androidNotificationOptions;
@VisibleForTesting
static IOSNotificationOptions? iosNotificationOptions;
@VisibleForTesting
static ForegroundTaskOptions? foregroundTaskOptions;
@VisibleForTesting
static bool isInitialized = false;
@VisibleForTesting
static bool skipServiceResponseCheck = false;
// platform instance: MethodChannelFlutterForegroundTask
static FlutterForegroundTaskPlatform get _platform =>
FlutterForegroundTaskPlatform.instance;
/// Resets class's static values to allow for testing of service flow.
@VisibleForTesting
static void resetStatic() {
androidNotificationOptions = null;
iosNotificationOptions = null;
foregroundTaskOptions = null;
isInitialized = false;
skipServiceResponseCheck = false;
receivePort?.close();
receivePort = null;
streamSubscription?.cancel();
streamSubscription = null;
dataCallbacks.clear();
}
/// Initialize the [FlutterForegroundTask].
static void init({
required AndroidNotificationOptions androidNotificationOptions,
required IOSNotificationOptions iosNotificationOptions,
required ForegroundTaskOptions foregroundTaskOptions,
}) {
FlutterForegroundTask.androidNotificationOptions =
androidNotificationOptions;
FlutterForegroundTask.iosNotificationOptions = iosNotificationOptions;
FlutterForegroundTask.foregroundTaskOptions = foregroundTaskOptions;
FlutterForegroundTask.isInitialized = true;
}
/// Start the foreground service.
static Future startService({
int? serviceId,
List? serviceTypes,
required String notificationTitle,
required String notificationText,
NotificationIcon? notificationIcon,
List? notificationButtons,
String? notificationInitialRoute,
Function? callback,
}) async {
try {
if (!isInitialized) {
throw ServiceNotInitializedException();
}
if (await isRunningService) {
throw ServiceAlreadyStartedException();
}
await _platform.startService(
androidNotificationOptions: androidNotificationOptions!,
iosNotificationOptions: iosNotificationOptions!,
foregroundTaskOptions: foregroundTaskOptions!,
serviceId: serviceId,
serviceTypes: serviceTypes,
notificationTitle: notificationTitle,
notificationText: notificationText,
notificationIcon: notificationIcon,
notificationButtons: notificationButtons,
notificationInitialRoute: notificationInitialRoute,
callback: callback,
);
if (!skipServiceResponseCheck) {
await checkServiceStateChange(target: true);
}
return const ServiceRequestSuccess();
} catch (error) {
return ServiceRequestFailure(error: error);
}
}
/// Restart the foreground service.
static Future restartService() async {
try {
if (!(await isRunningService)) {
throw ServiceNotStartedException();
}
await _platform.restartService();
return const ServiceRequestSuccess();
} catch (error) {
return ServiceRequestFailure(error: error);
}
}
/// Update the foreground service.
static Future updateService({
ForegroundTaskOptions? foregroundTaskOptions,
String? notificationTitle,
String? notificationText,
NotificationIcon? notificationIcon,
List? notificationButtons,
String? notificationInitialRoute,
Function? callback,
}) async {
try {
if (!(await isRunningService)) {
throw ServiceNotStartedException();
}
await _platform.updateService(
foregroundTaskOptions: foregroundTaskOptions,
notificationText: notificationText,
notificationTitle: notificationTitle,
notificationIcon: notificationIcon,
notificationButtons: notificationButtons,
notificationInitialRoute: notificationInitialRoute,
callback: callback,
);
return const ServiceRequestSuccess();
} catch (error) {
return ServiceRequestFailure(error: error);
}
}
/// Stop the foreground service.
static Future stopService() async {
try {
if (!(await isRunningService)) {
throw ServiceNotStartedException();
}
await _platform.stopService();
if (!skipServiceResponseCheck) {
await checkServiceStateChange(target: false);
}
return const ServiceRequestSuccess();
} catch (error) {
return ServiceRequestFailure(error: error);
}
}
@VisibleForTesting
static Future checkServiceStateChange({required bool target}) async {
// official doc: Once the service has been created, the service must call its startForeground() method within 5 seconds.
// ref: https://developer.android.com/guide/components/services#StartingAService
final bool isCompleted = await Utility.instance.completedWithinDeadline(
deadline: const Duration(seconds: 5),
future: () async {
return target == await isRunningService;
},
);
if (!isCompleted) {
throw ServiceTimeoutException();
}
}
/// Returns whether the foreground service is running.
static Future get isRunningService => _platform.isRunningService;
/// Set up the task handler and start the foreground task.
///
/// It must always be called from a top-level function, otherwise foreground task will not work.
static void setTaskHandler(TaskHandler handler) =>
_platform.setTaskHandler(handler);
// =================== Communication ===================
@VisibleForTesting
static ReceivePort? receivePort;
@VisibleForTesting
static StreamSubscription? streamSubscription;
@VisibleForTesting
static final List dataCallbacks = [];
/// Initialize port for communication between TaskHandler and UI.
static void initCommunicationPort() {
final ReceivePort newReceivePort = ReceivePort();
final SendPort newSendPort = newReceivePort.sendPort;
IsolateNameServer.removePortNameMapping(_kPortName);
if (IsolateNameServer.registerPortWithName(newSendPort, _kPortName)) {
streamSubscription?.cancel();
receivePort?.close();
receivePort = newReceivePort;
streamSubscription = receivePort?.listen((data) {
for (final DataCallback callback in dataCallbacks.toList()) {
callback.call(data);
}
});
}
}
/// Add a callback to receive data sent from the [TaskHandler].
static void addTaskDataCallback(DataCallback callback) {
if (!dataCallbacks.contains(callback)) {
dataCallbacks.add(callback);
}
}
/// Remove a callback to receive data sent from the [TaskHandler].
static void removeTaskDataCallback(DataCallback callback) {
dataCallbacks.remove(callback);
}
/// Send data to [TaskHandler].
static void sendDataToTask(Object data) => _platform.sendDataToTask(data);
/// Send date to main isolate.
static void sendDataToMain(Object data) {
final SendPort? sendPort = IsolateNameServer.lookupPortByName(_kPortName);
sendPort?.send(data);
}
// ====================== Storage ======================
/// Get the stored data with [key].
static Future<T?> getData({required String key}) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.reload();
final Object? data = prefs.get(_kPrefsKeyPrefix + key);
return (data is T) ? data : null;
}
/// Get all stored data.
static Future<Map<String, Object>> getAllData() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.reload();
final Map<String, Object> dataList = {};
for (final String prefsKey in prefs.getKeys()) {
if (prefsKey.contains(_kPrefsKeyPrefix)) {
final Object? data = prefs.get(prefsKey);
if (data != null) {
final String originKey = prefsKey.replaceAll(_kPrefsKeyPrefix, '');
dataList[originKey] = data;
}
}
}
return dataList;
}
/// Save data with [key].
static Future saveData({
required String key,
required Object value,
}) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.reload();
final String prefsKey = _kPrefsKeyPrefix + key;
if (value is int) {
return prefs.setInt(prefsKey, value);
} else if (value is double) {
return prefs.setDouble(prefsKey, value);
} else if (value is String) {
return prefs.setString(prefsKey, value);
} else if (value is bool) {
return prefs.setBool(prefsKey, value);
} else {
return false;
}
}
/// Remove data with [key].
static Future removeData({required String key}) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.reload();
return prefs.remove(_kPrefsKeyPrefix + key);
}
/// Clears all stored data.
static Future clearAllData() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.reload();
for (final String prefsKey in prefs.getKeys()) {
if (prefsKey.contains(_kPrefsKeyPrefix)) {
await prefs.remove(prefsKey);
}
}
return true;
}
// ====================== Utility ======================
/// Minimize the app to the background.
static void minimizeApp() => _platform.minimizeApp();
/// Launch the app at [route] if it is not running otherwise open it.
///
/// It is also possible to pass a route to this function but the route will only
/// be loaded if the app is not already running.
///
/// This function requires the "android.permission.SYSTEM_ALERT_WINDOW" permission and
/// requires using the openSystemAlertWindowSettings()
function to grant the permission.
static void launchApp([String? route]) => _platform.launchApp(route);
/// Toggles lockScreen visibility.
static void setOnLockScreenVisibility(bool isVisible) =>
_platform.setOnLockScreenVisibility(isVisible);
/// Returns whether the app is in the foreground.
static Future get isAppOnForeground => _platform.isAppOnForeground;
/// Wake up the screen of a device that is turned off.
static void wakeUpScreen() => _platform.wakeUpScreen();
/// Returns whether the app has been excluded from battery optimization.
static Future get isIgnoringBatteryOptimizations =>
_platform.isIgnoringBatteryOptimizations;
/// Open the settings page where you can set ignore battery optimization.
static Future openIgnoreBatteryOptimizationSettings() =>
_platform.openIgnoreBatteryOptimizationSettings();
/// Request to ignore battery optimization.
///
/// This function requires the "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" permission.
static Future requestIgnoreBatteryOptimization() =>
_platform.requestIgnoreBatteryOptimization();
/// Returns whether the "android.permission.SYSTEM_ALERT_WINDOW" permission is granted.
static Future get canDrawOverlays => _platform.canDrawOverlays;
/// Open the settings page where you can allow/deny the "android.permission.SYSTEM_ALERT_WINDOW" permission.
static Future openSystemAlertWindowSettings() =>
_platform.openSystemAlertWindowSettings();
/// Returns notification permission status.
static Future checkNotificationPermission() =>
_platform.checkNotificationPermission();
/// Request notification permission.
static Future requestNotificationPermission() =>
_platform.requestNotificationPermission();
/// Returns whether the "android.permission.SCHEDULE_EXACT_ALARM" permission is granted.
static Future get canScheduleExactAlarms =>
_platform.canScheduleExactAlarms;
/// Open the alarms & reminders settings page.
///
/// Use this utility only if you provide services that require long-term survival,
/// such as exact alarm service, healthcare service, or Bluetooth communication.
///
/// This utility requires the "android.permission.SCHEDULE_EXACT_ALARM" permission.
/// Using this permission may make app distribution difficult due to Google policy.
static Future openAlarmsAndRemindersSettings() =>
_platform.openAlarmsAndRemindersSettings();
}
"