Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

release: 8.11.0 #286

Merged
merged 5 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 8.11.0

* [**FEAT**] Allow sending Set collection using `FlutterForegroundTask.sendDataToMain`

## 8.10.4

* [**FIX**] Fixed an issue where main function was called repeatedly when there was no callback to start
Expand Down
36 changes: 17 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ This plugin is used to implement a foreground service on the Android platform.
## Features

* Can perform repetitive tasks with the foreground service.
* Supports two-way communication between the foreground service and UI.
* Provides widget that minimize the app without closing it when the user presses the soft back button.
* Supports two-way communication between the foreground service and UI(main isolate).
* Provides a widget that minimize the app without closing it when the user presses the soft back button.
* Provides useful utilities that can use while performing tasks.
* Provides option to automatically resume the foreground service on boot.
* Provides an option to automatically resume the foreground service on boot.

## Support version

Expand All @@ -21,24 +21,28 @@ This plugin is used to implement a foreground service on the Android platform.
- Android: `5.0+ (minSdkVersion: 21)`
- iOS: `13.0+`

## Structure

<img src="https://github.com/user-attachments/assets/6fc91bd9-62b2-43b8-9109-26d2e8d809c1" width="800">

## Getting started

To use this plugin, add `flutter_foreground_task` as a [dependency in your pubspec.yaml file](https://flutter.io/platform-plugins/). For example:

```yaml
dependencies:
flutter_foreground_task: ^8.10.4
flutter_foreground_task: ^8.11.0
```

After adding the `flutter_foreground_task` plugin to the flutter project, we need to specify the permissions and service to use for this plugin to work properly.
After adding the plugin to your flutter project, we need to declare the platform-specific permissions ans service to use for this plugin to work properly.

### :baby_chick: Android

Open the `AndroidManifest.xml` file and specify the service tag inside the `<application>` tag as follows.
Open the `AndroidManifest.xml` file and declare the service tag inside the `<application>` tag as follows.

If you want the foreground service to run only when the app is running, add `android:stopWithTask="true"`.

As mentioned in the Android guidelines, to start a FG service on Android 14+, you must specify `android:foregroundServiceType`.
As mentioned in the Android guidelines, to start a FG service on Android 14+, you must declare `android:foregroundServiceType`.

* [`camera`](https://developer.android.com/about/versions/14/changes/fgs-types-required#camera)
* [`connectedDevice`](https://developer.android.com/about/versions/14/changes/fgs-types-required#connected-device)
Expand All @@ -54,8 +58,6 @@ As mentioned in the Android guidelines, to start a FG service on Android 14+, yo
* [`specialUse`](https://developer.android.com/about/versions/14/changes/fgs-types-required#special-use)
* [`systemExempted`](https://developer.android.com/about/versions/14/changes/fgs-types-required#system-exempted)

You can read all the details in the [Android Developer Page](https://developer.android.com/about/versions/14/changes/fgs-types-required)

```
<!-- required -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
Expand All @@ -79,7 +81,7 @@ Check runtime requirements before starting the service. If this requirement is n

### :baby_chick: iOS

We can also launch `flutter_foreground_task` on the iOS platform. However, it has the following limitations.
You can also run `flutter_foreground_task` on the iOS platform. However, it has the following limitations.

* If you force close an app in recent apps, the task will be destroyed immediately.
* The task cannot be started automatically on boot like Android OS.
Expand Down Expand Up @@ -160,19 +162,16 @@ import Flutter
GeneratedPluginRegistrant.register(with: self)

// here
SwiftFlutterForegroundTaskPlugin.setPluginRegistrantCallback(registerPlugins)
SwiftFlutterForegroundTaskPlugin.setPluginRegistrantCallback { registry in
GeneratedPluginRegistrant.register(with: registry)
}
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
}

return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

// here
func registerPlugins(registry: FlutterPluginRegistry) {
GeneratedPluginRegistrant.register(with: registry)
}
```

## How to use
Expand All @@ -192,10 +191,9 @@ void main() {
2. Write a `TaskHandler` and a `callback` to request starting a TaskHandler.

```dart
// The callback function should always be a top-level function.
// The callback function should always be a top-level or static function.
@pragma('vm:entry-point')
void startCallback() {
// The setTaskHandler function must be called to handle the task in the background.
FlutterForegroundTask.setTaskHandler(MyTaskHandler());
}

Expand Down Expand Up @@ -496,7 +494,7 @@ Future<ServiceRequestResult> _stopService() async {

### :hatched_chick: deepening

This plugin supports two-way communication between TaskHandler and UI.
This plugin supports two-way communication between TaskHandler and UI(main isolate).

The send function can only send primitive type(int, double, bool), String, Collection(Map, List) provided by Flutter.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ class MethodCallHandlerImpl(private val context: Context, private val provider:

"setOnLockScreenVisibility" -> {
checkActivityNull().let {
val arguments = args as? Map<*, *>
val isVisible = arguments?.get("isVisible") as? Boolean ?: false
PluginUtils.setOnLockScreenVisibility(it, isVisible)
if (args is Boolean) {
PluginUtils.setOnLockScreenVisibility(it, args)
}
}
}

Expand Down
3 changes: 1 addition & 2 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ void main() {
runApp(const ExampleApp());
}

// The callback function should always be a top-level function.
// The callback function should always be a top-level or static function.
@pragma('vm:entry-point')
void startCallback() {
// The setTaskHandler function must be called to handle the task in the background.
FlutterForegroundTask.setTaskHandler(MyTaskHandler());
}

Expand Down
6 changes: 2 additions & 4 deletions lib/flutter_foreground_task.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@ 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/android_notification_options.dart';
import 'models/foreground_task_options.dart';
import 'models/ios_notification_options.dart';
import 'models/notification_button.dart';
import 'models/notification_icon_data.dart';
import 'models/notification_options.dart';
import 'models/notification_permission.dart';
import 'models/service_request_result.dart';
import 'task_handler.dart';
Expand All @@ -23,13 +22,12 @@ 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/android_notification_options.dart';
export 'models/foreground_task_event_action.dart';
export 'models/foreground_task_options.dart';
export 'models/ios_notification_options.dart';
export 'models/notification_button.dart';
export 'models/notification_channel_importance.dart';
export 'models/notification_icon_data.dart';
export 'models/notification_options.dart';
export 'models/notification_permission.dart';
export 'models/notification_priority.dart';
export 'models/notification_visibility.dart';
Expand Down
66 changes: 27 additions & 39 deletions lib/flutter_foreground_task_method_channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import 'package:flutter/widgets.dart';
import 'package:platform/platform.dart';

import 'flutter_foreground_task_platform_interface.dart';
import 'models/android_notification_options.dart';
import 'models/foreground_task_options.dart';
import 'models/ios_notification_options.dart';
import 'models/notification_button.dart';
import 'models/notification_icon_data.dart';
import 'models/notification_options.dart';
import 'models/notification_permission.dart';
import 'models/service_options.dart';
import 'task_handler.dart';

/// An implementation of [FlutterForegroundTaskPlatform] that uses method channels.
Expand Down Expand Up @@ -43,25 +43,19 @@ class MethodChannelFlutterForegroundTask extends FlutterForegroundTaskPlatform {
List<NotificationButton>? notificationButtons,
Function? callback,
}) async {
final Map<String, dynamic> options = {
'serviceId': serviceId,
if (platform.isAndroid)
...androidNotificationOptions.toJson()
else if (platform.isIOS)
...iosNotificationOptions.toJson(),
...foregroundTaskOptions.toJson(),
'notificationContentTitle': notificationTitle,
'notificationContentText': notificationText,
'iconData': notificationIcon?.toJson(),
'buttons': notificationButtons?.map((e) => e.toJson()).toList()
};

if (callback != null) {
options['callbackHandle'] =
PluginUtilities.getCallbackHandle(callback)?.toRawHandle();
}

await mMDChannel.invokeMethod('startService', options);
final Map<String, dynamic> optionsJson = ServiceStartOptions(
serviceId: serviceId,
androidNotificationOptions: androidNotificationOptions,
iosNotificationOptions: iosNotificationOptions,
foregroundTaskOptions: foregroundTaskOptions,
notificationContentTitle: notificationTitle,
notificationContentText: notificationText,
notificationIcon: notificationIcon,
notificationButtons: notificationButtons,
callback: callback,
).toJson(platform);

await mMDChannel.invokeMethod('startService', optionsJson);
}

@override
Expand All @@ -78,20 +72,16 @@ class MethodChannelFlutterForegroundTask extends FlutterForegroundTaskPlatform {
List<NotificationButton>? notificationButtons,
Function? callback,
}) async {
final Map<String, dynamic> options = {
if (foregroundTaskOptions != null) ...foregroundTaskOptions.toJson(),
'notificationContentTitle': notificationTitle,
'notificationContentText': notificationText,
'iconData': notificationIcon?.toJson(),
'buttons': notificationButtons?.map((e) => e.toJson()).toList()
};

if (callback != null) {
options['callbackHandle'] =
PluginUtilities.getCallbackHandle(callback)?.toRawHandle();
}

await mMDChannel.invokeMethod('updateService', options);
final Map<String, dynamic> optionsJson = ServiceUpdateOptions(
foregroundTaskOptions: foregroundTaskOptions,
notificationContentTitle: notificationTitle,
notificationContentText: notificationText,
notificationIcon: notificationIcon,
notificationButtons: notificationButtons,
callback: callback,
).toJson(platform);

await mMDChannel.invokeMethod('updateService', optionsJson);
}

@override
Expand Down Expand Up @@ -143,7 +133,7 @@ class MethodChannelFlutterForegroundTask extends FlutterForegroundTaskPlatform {
break;
case 'onReceiveData':
dynamic data = call.arguments;
if (data is List || data is Map) {
if (data is List || data is Map || data is Set) {
try {
data = jsonDecode(jsonEncode(data));
} catch (e, s) {
Expand Down Expand Up @@ -189,9 +179,7 @@ class MethodChannelFlutterForegroundTask extends FlutterForegroundTaskPlatform {
@override
void setOnLockScreenVisibility(bool isVisible) {
if (platform.isAndroid) {
mMDChannel.invokeMethod('setOnLockScreenVisibility', {
'isVisible': isVisible,
});
mMDChannel.invokeMethod('setOnLockScreenVisibility', isVisible);
}
}

Expand Down
3 changes: 1 addition & 2 deletions lib/flutter_foreground_task_platform_interface.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import 'package:plugin_platform_interface/plugin_platform_interface.dart';

import 'flutter_foreground_task_method_channel.dart';
import 'models/android_notification_options.dart';
import 'models/foreground_task_options.dart';
import 'models/ios_notification_options.dart';
import 'models/notification_button.dart';
import 'models/notification_icon_data.dart';
import 'models/notification_options.dart';
import 'models/notification_permission.dart';
import 'task_handler.dart';

Expand Down
24 changes: 0 additions & 24 deletions lib/models/ios_notification_options.dart

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:flutter_foreground_task/models/notification_channel_importance.dart';
import 'package:flutter_foreground_task/models/notification_priority.dart';
import 'package:flutter_foreground_task/models/notification_visibility.dart';
import 'notification_channel_importance.dart';
import 'notification_priority.dart';
import 'notification_visibility.dart';

/// Notification options for Android platform.
class AndroidNotificationOptions {
Expand Down Expand Up @@ -91,3 +91,28 @@ class AndroidNotificationOptions {
};
}
}

/// Notification options for iOS platform.
class IOSNotificationOptions {
/// Constructs an instance of [IOSNotificationOptions].
const IOSNotificationOptions({
this.showNotification = true,
this.playSound = false,
});

/// Whether to show notifications.
/// The default is `true`.
final bool showNotification;

/// Whether to play sound when creating notifications.
/// The default is `false`.
final bool playSound;

/// Returns the data fields of [IOSNotificationOptions] in JSON format.
Map<String, dynamic> toJson() {
return {
'showNotification': showNotification,
'playSound': playSound,
};
}
}
Loading