Skip to content
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
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,32 @@ FlutterForegroundTask.init(

For a full list of persisted keys and architecture details, see [android_shared_preferences_usage.md](documentation/android_shared_preferences_usage.md).

**Multiple Services (Android)**:

By default the library provides a single foreground service. If your app needs **multiple independent services** running simultaneously (e.g. a location tracker and a media player), you can register additional service classes and control each one independently from Dart using `FlutterForegroundTaskController`:

```dart
final locationCtrl = FlutterForegroundTaskController.of('locationTracker');
locationCtrl.init(
androidNotificationOptions: AndroidNotificationOptions(
channelId: 'location_channel',
channelName: 'Location Tracker',
),
iosNotificationOptions: const IOSNotificationOptions(),
foregroundTaskOptions: ForegroundTaskOptions(
eventAction: ForegroundTaskEventAction.repeat(5000),
),
);

await locationCtrl.startService(
notificationTitle: 'Tracking',
notificationText: 'Running...',
callback: locationCallback,
);
```

This requires a small Kotlin subclass and `AndroidManifest.xml` entry per service. The existing single-service API is fully unchanged. For the complete setup guide, see [multiple_services.md](documentation/multiple_services.md).

### :baby_chick: iOS

You can also run `flutter_foreground_task` on the iOS platform. However, it has the following limitations.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.pravera.flutter_foreground_task

import android.content.Intent
import com.pravera.flutter_foreground_task.service.ForegroundService
import com.pravera.flutter_foreground_task.service.FlutterForegroundServiceBase
import com.pravera.flutter_foreground_task.service.ForegroundServiceManager
import com.pravera.flutter_foreground_task.service.ForegroundServiceRuntime
import com.pravera.flutter_foreground_task.service.FlutterForegroundServiceRegistry
import com.pravera.flutter_foreground_task.service.NotificationPermissionManager
import com.pravera.flutter_foreground_task.service.ServiceProvider
import io.flutter.embedding.engine.plugins.FlutterPlugin
Expand All @@ -13,12 +15,24 @@ import io.flutter.plugin.common.PluginRegistry.NewIntentListener
/** FlutterForegroundTaskPlugin */
class FlutterForegroundTaskPlugin : FlutterPlugin, ActivityAware, ServiceProvider, NewIntentListener {
companion object {
/** Add a lifecycle listener for the default service. */
fun addTaskLifecycleListener(listener: FlutterForegroundTaskLifecycleListener) {
ForegroundService.addTaskLifecycleListener(listener)
addTaskLifecycleListener(FlutterForegroundServiceRegistry.DEFAULT_ID, listener)
}

/** Add a lifecycle listener for a specific service id. */
fun addTaskLifecycleListener(serviceId: String, listener: FlutterForegroundTaskLifecycleListener) {
ForegroundServiceRuntime.addTaskLifecycleListener(serviceId, listener)
}

/** Remove a lifecycle listener from the default service. */
fun removeTaskLifecycleListener(listener: FlutterForegroundTaskLifecycleListener) {
ForegroundService.removeTaskLifecycleListener(listener)
removeTaskLifecycleListener(FlutterForegroundServiceRegistry.DEFAULT_ID, listener)
}

/** Remove a lifecycle listener from a specific service id. */
fun removeTaskLifecycleListener(serviceId: String, listener: FlutterForegroundTaskLifecycleListener) {
ForegroundServiceRuntime.removeTaskLifecycleListener(serviceId, listener)
}
}

Expand All @@ -29,6 +43,11 @@ class FlutterForegroundTaskPlugin : FlutterPlugin, ActivityAware, ServiceProvide
private lateinit var methodCallHandler: MethodCallHandlerImpl

override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
// The default service is registered inside FlutterForegroundServiceRegistry's
// static initializer so that it is available at cold boot, before any Flutter
// engine has attached. We just need to make sure the registry class is loaded.
FlutterForegroundServiceRegistry.registeredIds()

notificationPermissionManager = NotificationPermissionManager()
foregroundServiceManager = ForegroundServiceManager()

Expand All @@ -50,7 +69,7 @@ class FlutterForegroundTaskPlugin : FlutterPlugin, ActivityAware, ServiceProvide
activityBinding = binding

val intent = binding.activity.intent
ForegroundService.handleNotificationContentIntent(intent)
FlutterForegroundServiceBase.handleNotificationContentIntent(intent)
}

override fun onDetachedFromActivityForConfigChanges() {
Expand All @@ -70,7 +89,7 @@ class FlutterForegroundTaskPlugin : FlutterPlugin, ActivityAware, ServiceProvide
}

override fun onNewIntent(intent: Intent): Boolean {
ForegroundService.handleNotificationContentIntent(intent)
FlutterForegroundServiceBase.handleNotificationContentIntent(intent)
return true
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import android.content.Intent

import com.pravera.flutter_foreground_task.errors.ActivityNotAttachedException
import com.pravera.flutter_foreground_task.models.NotificationPermission
import com.pravera.flutter_foreground_task.service.FlutterForegroundServiceRegistry
import com.pravera.flutter_foreground_task.service.NotificationPermissionCallback
import com.pravera.flutter_foreground_task.service.ServiceProvider
import com.pravera.flutter_foreground_task.utils.ErrorHandleUtils
Expand All @@ -29,6 +30,12 @@ class MethodCallHandlerImpl(private val context: Context, private val provider:
private var methodCodes: MutableMap<Int, Int> = mutableMapOf()
private var methodResults: MutableMap<Int, MethodChannel.Result> = mutableMapOf()

private fun extractServiceId(args: Any?): String {
val map = args as? Map<*, *>
return map?.get("serviceId") as? String
?: FlutterForegroundServiceRegistry.DEFAULT_ID
}

override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
val args = call.arguments
try {
Expand Down Expand Up @@ -56,29 +63,46 @@ class MethodCallHandlerImpl(private val context: Context, private val provider:
}

"startService" -> {
provider.getForegroundServiceManager().start(context, args)
val serviceId = extractServiceId(args)
provider.getForegroundServiceManager().start(context, serviceId, args)
result.success(true)
}

"restartService" -> {
provider.getForegroundServiceManager().restart(context)
val serviceId = extractServiceId(args)
provider.getForegroundServiceManager().restart(context, serviceId)
result.success(true)
}

"updateService" -> {
provider.getForegroundServiceManager().update(context, args)
val serviceId = extractServiceId(args)
provider.getForegroundServiceManager().update(context, serviceId, args)
result.success(true)
}

"stopService" -> {
provider.getForegroundServiceManager().stop(context)
val serviceId = extractServiceId(args)
provider.getForegroundServiceManager().stop(context, serviceId)
result.success(true)
}

"sendData" -> provider.getForegroundServiceManager().sendData(args)
"sendData" -> {
val map = args as? Map<*, *>
if (map != null) {
val serviceId = map["serviceId"] as? String
?: FlutterForegroundServiceRegistry.DEFAULT_ID
val data = map["data"]
provider.getForegroundServiceManager().sendData(serviceId, data)
} else {
provider.getForegroundServiceManager().sendData(
FlutterForegroundServiceRegistry.DEFAULT_ID, args)
}
}

"isRunningService" ->
result.success(provider.getForegroundServiceManager().isRunningService())
"isRunningService" -> {
val serviceId = extractServiceId(args)
result.success(provider.getForegroundServiceManager().isRunningService(serviceId))
}

"attachedActivity" -> result.success(activity != null)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,24 @@ package com.pravera.flutter_foreground_task.models

import android.content.Context
import com.pravera.flutter_foreground_task.PreferencesKey as PrefsKey
import com.pravera.flutter_foreground_task.service.FlutterForegroundServiceRegistry
import com.pravera.flutter_foreground_task.storage.ForegroundTaskStorageProvider

data class ForegroundServiceStatus(val action: String) {
companion object {
fun getData(context: Context): ForegroundServiceStatus {
fun getData(context: Context, serviceId: String = FlutterForegroundServiceRegistry.DEFAULT_ID): ForegroundServiceStatus {
val prefs = ForegroundTaskStorageProvider.getPreferences(
context, PrefsKey.FOREGROUND_SERVICE_STATUS_PREFS)
context, serviceId, PrefsKey.FOREGROUND_SERVICE_STATUS_PREFS)

val action = prefs.getString(PrefsKey.FOREGROUND_SERVICE_ACTION, null)
?: ForegroundServiceAction.API_STOP

return ForegroundServiceStatus(action = action)
}

fun setData(context: Context, action: String) {
fun setData(context: Context, serviceId: String = FlutterForegroundServiceRegistry.DEFAULT_ID, action: String) {
val prefs = ForegroundTaskStorageProvider.getPreferences(
context, PrefsKey.FOREGROUND_SERVICE_STATUS_PREFS)
context, serviceId, PrefsKey.FOREGROUND_SERVICE_STATUS_PREFS)

with(prefs.edit()) {
putString(PrefsKey.FOREGROUND_SERVICE_ACTION, action)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import android.content.Context
import android.content.pm.ServiceInfo
import android.os.Build
import com.pravera.flutter_foreground_task.PreferencesKey
import com.pravera.flutter_foreground_task.service.FlutterForegroundServiceRegistry
import com.pravera.flutter_foreground_task.storage.ForegroundTaskStorageProvider

data class ForegroundServiceTypes(val value: Int) {
companion object {
fun getData(context: Context): ForegroundServiceTypes {
fun getData(context: Context, serviceId: String = FlutterForegroundServiceRegistry.DEFAULT_ID): ForegroundServiceTypes {
val prefs = ForegroundTaskStorageProvider.getPreferences(
context, PreferencesKey.FOREGROUND_SERVICE_TYPES_PREFS)
context, serviceId, PreferencesKey.FOREGROUND_SERVICE_TYPES_PREFS)

val value = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
prefs.getInt(PreferencesKey.FOREGROUND_SERVICE_TYPES, ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST)
Expand All @@ -21,9 +22,9 @@ data class ForegroundServiceTypes(val value: Int) {
return ForegroundServiceTypes(value = value)
}

fun setData(context: Context, map: Map<*, *>?) {
fun setData(context: Context, serviceId: String = FlutterForegroundServiceRegistry.DEFAULT_ID, map: Map<*, *>?) {
val prefs = ForegroundTaskStorageProvider.getPreferences(
context, PreferencesKey.FOREGROUND_SERVICE_TYPES_PREFS)
context, serviceId, PreferencesKey.FOREGROUND_SERVICE_TYPES_PREFS)

var value = 0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
Expand All @@ -46,9 +47,9 @@ data class ForegroundServiceTypes(val value: Int) {
}
}

fun clearData(context: Context) {
fun clearData(context: Context, serviceId: String = FlutterForegroundServiceRegistry.DEFAULT_ID) {
val prefs = ForegroundTaskStorageProvider.getPreferences(
context, PreferencesKey.FOREGROUND_SERVICE_TYPES_PREFS)
context, serviceId, PreferencesKey.FOREGROUND_SERVICE_TYPES_PREFS)

with(prefs.edit()) {
clear()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ package com.pravera.flutter_foreground_task.models

import android.content.Context
import com.pravera.flutter_foreground_task.PreferencesKey as PrefsKey
import com.pravera.flutter_foreground_task.service.FlutterForegroundServiceRegistry
import com.pravera.flutter_foreground_task.storage.ForegroundTaskStorageProvider

data class ForegroundTaskData(val callbackHandle: Long?) {
companion object {
fun getData(context: Context): ForegroundTaskData {
fun getData(context: Context, serviceId: String = FlutterForegroundServiceRegistry.DEFAULT_ID): ForegroundTaskData {
val prefs = ForegroundTaskStorageProvider.getPreferences(
context, PrefsKey.FOREGROUND_TASK_OPTIONS_PREFS)
context, serviceId, PrefsKey.FOREGROUND_TASK_OPTIONS_PREFS)

val callbackHandle = if (prefs.contains(PrefsKey.CALLBACK_HANDLE)) {
prefs.getLong(PrefsKey.CALLBACK_HANDLE, 0L)
Expand All @@ -19,9 +20,9 @@ data class ForegroundTaskData(val callbackHandle: Long?) {
return ForegroundTaskData(callbackHandle = callbackHandle)
}

fun setData(context: Context, map: Map<*, *>?) {
fun setData(context: Context, serviceId: String = FlutterForegroundServiceRegistry.DEFAULT_ID, map: Map<*, *>?) {
val prefs = ForegroundTaskStorageProvider.getPreferences(
context, PrefsKey.FOREGROUND_TASK_OPTIONS_PREFS)
context, serviceId, PrefsKey.FOREGROUND_TASK_OPTIONS_PREFS)

val callbackHandle = "${map?.get(PrefsKey.CALLBACK_HANDLE)}".toLongOrNull()

Expand All @@ -32,9 +33,9 @@ data class ForegroundTaskData(val callbackHandle: Long?) {
}
}

fun updateData(context: Context, map: Map<*, *>?) {
fun updateData(context: Context, serviceId: String = FlutterForegroundServiceRegistry.DEFAULT_ID, map: Map<*, *>?) {
val prefs = ForegroundTaskStorageProvider.getPreferences(
context, PrefsKey.FOREGROUND_TASK_OPTIONS_PREFS)
context, serviceId, PrefsKey.FOREGROUND_TASK_OPTIONS_PREFS)

val callbackHandle = "${map?.get(PrefsKey.CALLBACK_HANDLE)}".toLongOrNull()

Expand All @@ -44,9 +45,9 @@ data class ForegroundTaskData(val callbackHandle: Long?) {
}
}

fun clearData(context: Context) {
fun clearData(context: Context, serviceId: String = FlutterForegroundServiceRegistry.DEFAULT_ID) {
val prefs = ForegroundTaskStorageProvider.getPreferences(
context, PrefsKey.FOREGROUND_TASK_OPTIONS_PREFS)
context, serviceId, PrefsKey.FOREGROUND_TASK_OPTIONS_PREFS)

with(prefs.edit()) {
clear()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.pravera.flutter_foreground_task.models
import android.content.Context
import org.json.JSONObject
import com.pravera.flutter_foreground_task.PreferencesKey as PrefsKey
import com.pravera.flutter_foreground_task.service.FlutterForegroundServiceRegistry
import com.pravera.flutter_foreground_task.storage.ForegroundTaskStorageProvider

data class ForegroundTaskOptions(
Expand All @@ -15,9 +16,9 @@ data class ForegroundTaskOptions(
val stopWithTask: Boolean?
) {
companion object {
fun getData(context: Context): ForegroundTaskOptions {
fun getData(context: Context, serviceId: String = FlutterForegroundServiceRegistry.DEFAULT_ID): ForegroundTaskOptions {
val prefs = ForegroundTaskStorageProvider.getPreferences(
context, PrefsKey.FOREGROUND_TASK_OPTIONS_PREFS)
context, serviceId, PrefsKey.FOREGROUND_TASK_OPTIONS_PREFS)

val eventActionJsonString = prefs.getString(PrefsKey.TASK_EVENT_ACTION, null)
val eventAction: ForegroundTaskEventAction = if (eventActionJsonString != null) {
Expand Down Expand Up @@ -55,9 +56,9 @@ data class ForegroundTaskOptions(
)
}

fun setData(context: Context, map: Map<*, *>?) {
fun setData(context: Context, serviceId: String = FlutterForegroundServiceRegistry.DEFAULT_ID, map: Map<*, *>?) {
val prefs = ForegroundTaskStorageProvider.getPreferences(
context, PrefsKey.FOREGROUND_TASK_OPTIONS_PREFS)
context, serviceId, PrefsKey.FOREGROUND_TASK_OPTIONS_PREFS)

val eventActionJson = map?.get(PrefsKey.TASK_EVENT_ACTION) as? Map<*, *>
var eventActionJsonString: String? = null
Expand All @@ -84,9 +85,9 @@ data class ForegroundTaskOptions(
}
}

fun updateData(context: Context, map: Map<*, *>?) {
fun updateData(context: Context, serviceId: String = FlutterForegroundServiceRegistry.DEFAULT_ID, map: Map<*, *>?) {
val prefs = ForegroundTaskStorageProvider.getPreferences(
context, PrefsKey.FOREGROUND_TASK_OPTIONS_PREFS)
context, serviceId, PrefsKey.FOREGROUND_TASK_OPTIONS_PREFS)

val eventActionJson = map?.get(PrefsKey.TASK_EVENT_ACTION) as? Map<*, *>
var eventActionJsonString: String? = null
Expand All @@ -113,14 +114,14 @@ data class ForegroundTaskOptions(
}
}

fun clearData(context: Context) {
fun clearData(context: Context, serviceId: String = FlutterForegroundServiceRegistry.DEFAULT_ID) {
val prefs = ForegroundTaskStorageProvider.getPreferences(
context, PrefsKey.FOREGROUND_TASK_OPTIONS_PREFS)
context, serviceId, PrefsKey.FOREGROUND_TASK_OPTIONS_PREFS)

with(prefs.edit()) {
clear()
commit()
}
}
}
}
}
Loading
Loading