Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
6316689
feat: add mediaProcessing foreground service type
simonpham Sep 7, 2025
e698150
Fix FLAG_STOP_WITH_TASK bitmask check
deakjahn Dec 19, 2025
2b8bca0
Add stopWithTask override
deakjahn Dec 20, 2025
42a88ad
release: 9.2.0
Dev-hwang Dec 24, 2025
e55150b
fix(android): Prevent crash in onDestroy
udyan-dev Jan 17, 2026
d628c36
release: 9.2.1
Dev-hwang Mar 3, 2026
eea409e
fix: DidNotStartInTimeException & DidNotStopInTimeException
meliksahcakirr Feb 25, 2026
b3d3d30
release: 9.2.2
Dev-hwang Apr 1, 2026
65868ab
Migrate to built-in Kotlin
ravdeepsingh22 Jun 8, 2026
33aa919
feat(ios): add Swift Package Manager support
TeddyYeung Jun 26, 2026
d487d59
Add UIScene life cycle support
Mar 19, 2026
82cd8a2
release: 10.0.0
Dev-hwang Jul 15, 2026
0f8fdd8
Merge branch 'master' into migrate-to-built-in-kotlin
Dev-hwang Jul 15, 2026
5ac0c73
chore(android): upgrade Gradle, AGP, Kotlin and compileSdk
Dev-hwang Jul 15, 2026
dce8112
Merge branch 'master' into feat/add-spm-support
Dev-hwang Jul 15, 2026
afc8c9e
feat(ios): add Swift Package Manager support
ravdeepsingh22 Jun 8, 2026
bdf3cb0
Merge branch 'mg-3.44.0-spm'
Dev-hwang Aug 14, 2026
e335c5b
fix(ios): Fix wrong dependencies
Dev-hwang Aug 14, 2026
25a2b7b
fix(ios): Guard addSceneDelegate call with iOS 13 availability check
Dev-hwang Aug 14, 2026
7909dba
fix(ios): Resolve iOS availability mismatch in FlutterSceneLifeCycleD…
Dev-hwang Aug 14, 2026
1e7ec26
ci(ios): Disable Swift Package Manager
Dev-hwang Aug 14, 2026
f773bf2
Revert "ci(ios): Disable Swift Package Manager"
Dev-hwang Aug 18, 2026
a8c0b59
feat(ios): add Swift Package Manager support
Dev-hwang Aug 18, 2026
1250c19
docs: update example app (SPM, UIScene, env)
Dev-hwang Aug 18, 2026
7c4a1f1
release: 11.0.0
Dev-hwang Aug 18, 2026
c7a6fc0
docs: update README
Dev-hwang Aug 20, 2026
2760bc5
chore: upgrade shared_preferences from 2.5.3 to 2.5.5
Dev-hwang Aug 20, 2026
3e40a51
release: 11.0.1
Dev-hwang Aug 20, 2026
9a959f7
fix(ios): Add objective-c shim for FlutterForegroundTaskPlugin
Dev-hwang Sep 4, 2026
f2adde7
release: 11.0.2
Dev-hwang Sep 4, 2026
d163b53
fix(ios): include the Obj-C shim in the podspec source_files
bohdansnappic Sep 4, 2026
e6d5bcb
release: 11.0.3
bohdanvela Sep 4, 2026
f2acda8
release: 11.0.3
Dev-hwang Sep 7, 2026
c62d5a4
merge: upstream 9.2.2 (Dev-hwang/flutter_foreground_task)
JCPistori Sep 22, 2026
5aafaf1
merge: upstream 10.0.0 (Dev-hwang/flutter_foreground_task)
JCPistori Sep 22, 2026
9b26bc1
merge: upstream 11.0.3 (Dev-hwang/flutter_foreground_task)
JCPistori Sep 22, 2026
305c25d
chore: para de versionar example/ios/Flutter/ephemeral
JCPistori Sep 22, 2026
1a5ee44
Merge branch 'fix/crashes-foreground-service' into feat/atualizacao-f…
JCPistori Sep 22, 2026
04ba95d
doc: simplificação de comentários
JCPistori Oct 1, 2026
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
.idea/
.packages
.pub/
.build/
.swiftpm/

/pubspec.lock
build/

# Arquivos gerados pelo Flutter no example (versionados por engano)
example/ios/Flutter/ephemeral/
40 changes: 40 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,43 @@
## 11.0.3

* [**FIX-iOS**] Include the objective-c shim in the podspec source_files so it is compiled under CocoaPods

## 11.0.2

* [**FIX-iOS**] Add objective-c shim for FlutterForegroundTaskPlugin

## 11.0.1

* [**CHORE**] Upgrade shared_preferences from 2.5.3 to 2.5.5
* [**DOCS**] Update README

## 11.0.0

* [**CHORE**] Bump minimum supported SDK version to `Flutter 3.44/Dart 3.12`
* [**FEAT**] Migrate to built-in Kotlin (KGP) [#385](https://github.com/Dev-hwang/flutter_foreground_task/pull/385)
* [**FEAT**] Support Swift Package Manager (SPM) [#387](https://github.com/Dev-hwang/flutter_foreground_task/pull/387)
* Check [migration_documentation](./documentation/migration_documentation.md) for changes

## 10.0.0

* [**CHORE**] Bump minimum supported SDK version to `Flutter 3.38.0`
* [**FEAT**] Add UIScene life cycle support for iOS [#378](https://github.com/Dev-hwang/flutter_foreground_task/pull/378)

## 9.2.2

* [**FIX**] Resolved frequent ForegroundServiceDidNotStartInTime and ForegroundServiceDidNotStopInTime exceptions by avoiding redundant foreground service start contracts. [#377](https://github.com/Dev-hwang/flutter_foreground_task/pull/377)

## 9.2.1

* [**FIX**] Prevent crash in onDestroy [#372](https://github.com/Dev-hwang/flutter_foreground_task/pull/372)

## 9.2.0

* [**FEAT**] Add `allowAutoRestart` option, which enables the service to automatically restart if it is terminated by the system.
* [**FEAT**] Add `stopWithTask` option, to ensure service termination when the app UI is dismissed.
* [**FEAT**] Add `mediaProcessing` foreground service type.
* [**FIX**] Fix `FLAG_STOP_WITH_TASK` bitmask check error.

## 9.1.0

* [**FEAT**] Support manual foregroundServiceType via serviceTypes in startService
Expand Down
21 changes: 7 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,25 @@ This plugin is used to implement a foreground service on the Android platform.

## Support version

- Flutter: `3.22.0+`
- Dart: `3.4.0+`
- Flutter: `3.44.0+`
- Dart: `3.12.0+`
- Android: `5.0+ (minSdkVersion: 21)`
- iOS: `12.0+`
- iOS: `13.0+`

## 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: ^9.1.0
flutter_foreground_task: ^11.0.3
```

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

This plugin requires `Kotlin version 1.9.10+` and `Gradle version 8.6.0+`. Please refer to the migration documentation for more details.
This plugin requires `Kotlin version 2.2.20+` and `Gradle version 8.11.1+`. Please refer to the migration documentation for more details.

- [project/settings.gradle](https://github.com/Dev-hwang/flutter_foreground_task/blob/master/example/android/settings.gradle)
- [project/gradle-wrapper.properties](https://github.com/Dev-hwang/flutter_foreground_task/blob/master/example/android/gradle/wrapper/gradle-wrapper.properties)
Expand Down Expand Up @@ -127,9 +127,7 @@ Open the `ios/Runner/AppDelegate.swift` file and add the commented code.
```objc
#import "AppDelegate.h"
#import "GeneratedPluginRegistrant.h"

// this
#import <flutter_foreground_task/FlutterForegroundTaskPlugin.h>
@import flutter_foreground_task; // this (module import)

// this
void registerPlugins(NSObject<FlutterPluginRegistry>* registry) {
Expand All @@ -156,17 +154,12 @@ void registerPlugins(NSObject<FlutterPluginRegistry>* registry) {

**Swift**:

Declare the import statement below in the `ios/Runner/Runner-Bridging-Header.h` file.

```objc
#import <flutter_foreground_task/FlutterForegroundTaskPlugin.h>
```

Open the `ios/Runner/AppDelegate.swift` file and add the commented code.

```swift
import UIKit
import Flutter
import flutter_foreground_task // this (module import)

@main
@objc class AppDelegate: FlutterAppDelegate {
Expand Down
13 changes: 6 additions & 7 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@ group 'com.pravera.flutter_foreground_task'
version '1.0-SNAPSHOT'

buildscript {
ext.kotlin_version = '1.9.10'
repositories {
google()
mavenCentral()
}

dependencies {
classpath 'com.android.tools.build:gradle:8.6.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

Expand All @@ -22,7 +20,6 @@ rootProject.allprojects {
}

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

android {
// Conditional for compatibility with AGP <4.2.
Expand All @@ -37,10 +34,6 @@ android {
targetCompatibility JavaVersion.VERSION_11
}

kotlinOptions {
jvmTarget = JavaVersion.VERSION_11
}

sourceSets {
main.java.srcDirs += 'src/main/kotlin'
test.java.srcDirs += 'src/test/kotlin'
Expand Down Expand Up @@ -71,3 +64,9 @@ android {
}
}
}

kotlin {
compilerOptions {
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ object PreferencesKey {
const val ALLOW_WAKE_LOCK = "allowWakeLock"
const val ALLOW_WIFI_LOCK = "allowWifiLock"
const val ALLOW_AUTO_RESTART = "allowAutoRestart"
const val STOP_WITH_TASK = "stopWithTask"

// task data
const val CALLBACK_HANDLE = "callbackHandle"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ data class ForegroundServiceTypes(val value: Int) {
10 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE else null
11 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE else null
12 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED else null
13 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING else null
else -> null
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ data class ForegroundTaskOptions(
val autoRunOnMyPackageReplaced: Boolean,
val allowWakeLock: Boolean,
val allowWifiLock: Boolean,
val allowAutoRestart: Boolean
val allowAutoRestart: Boolean,
val stopWithTask: Boolean?
) {
companion object {
fun getData(context: Context): ForegroundTaskOptions {
Expand All @@ -36,6 +37,11 @@ data class ForegroundTaskOptions(
val allowWakeLock = prefs.getBoolean(PrefsKey.ALLOW_WAKE_LOCK, true)
val allowWifiLock = prefs.getBoolean(PrefsKey.ALLOW_WIFI_LOCK, false)
val allowAutoRestart = prefs.getBoolean(PrefsKey.ALLOW_AUTO_RESTART, false)
val stopWithTask: Boolean? =
if (prefs.contains(PrefsKey.STOP_WITH_TASK))
prefs.getBoolean(PrefsKey.STOP_WITH_TASK, false)
else
null

return ForegroundTaskOptions(
eventAction = eventAction,
Expand All @@ -44,6 +50,7 @@ data class ForegroundTaskOptions(
allowWakeLock = allowWakeLock,
allowWifiLock = allowWifiLock,
allowAutoRestart = allowAutoRestart,
stopWithTask = stopWithTask,
)
}

Expand All @@ -62,6 +69,7 @@ data class ForegroundTaskOptions(
val allowWakeLock = map?.get(PrefsKey.ALLOW_WAKE_LOCK) as? Boolean ?: true
val allowWifiLock = map?.get(PrefsKey.ALLOW_WIFI_LOCK) as? Boolean ?: false
val allowAutoRestart = map?.get(PrefsKey.ALLOW_AUTO_RESTART) as? Boolean ?: false
val stopWithTask = map?.get(PrefsKey.STOP_WITH_TASK) as? Boolean

with(prefs.edit()) {
putString(PrefsKey.TASK_EVENT_ACTION, eventActionJsonString)
Expand All @@ -70,6 +78,7 @@ data class ForegroundTaskOptions(
putBoolean(PrefsKey.ALLOW_WAKE_LOCK, allowWakeLock)
putBoolean(PrefsKey.ALLOW_WIFI_LOCK, allowWifiLock)
putBoolean(PrefsKey.ALLOW_AUTO_RESTART, allowAutoRestart)
stopWithTask?.let { putBoolean(PrefsKey.STOP_WITH_TASK, it) } ?: remove(PrefsKey.STOP_WITH_TASK)
commit()
}
}
Expand All @@ -89,6 +98,7 @@ data class ForegroundTaskOptions(
val allowWakeLock = map?.get(PrefsKey.ALLOW_WAKE_LOCK) as? Boolean
val allowWifiLock = map?.get(PrefsKey.ALLOW_WIFI_LOCK) as? Boolean
val allowAutoRestart = map?.get(PrefsKey.ALLOW_AUTO_RESTART) as? Boolean
val stopWithTask = map?.get(PrefsKey.STOP_WITH_TASK) as? Boolean

with(prefs.edit()) {
eventActionJsonString?.let { putString(PrefsKey.TASK_EVENT_ACTION, it) }
Expand All @@ -97,6 +107,7 @@ data class ForegroundTaskOptions(
allowWakeLock?.let { putBoolean(PrefsKey.ALLOW_WAKE_LOCK, it) }
allowWifiLock?.let { putBoolean(PrefsKey.ALLOW_WIFI_LOCK, it) }
allowAutoRestart?.let { putBoolean(PrefsKey.ALLOW_AUTO_RESTART, it) }
stopWithTask?.let { putBoolean(PrefsKey.STOP_WITH_TASK, it) } ?: remove(PrefsKey.STOP_WITH_TASK)
Comment thread
brunonahornygaudium marked this conversation as resolved.
Resolved
commit()
}
}
Expand All @@ -111,4 +122,4 @@ data class ForegroundTaskOptions(
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import androidx.core.content.ContextCompat
import com.pravera.flutter_foreground_task.FlutterForegroundTaskLifecycleListener
import com.pravera.flutter_foreground_task.RequestCode
import com.pravera.flutter_foreground_task.models.*
import com.pravera.flutter_foreground_task.utils.ForegroundServiceUtils
import com.pravera.flutter_foreground_task.utils.*
import com.pravera.flutter_foreground_task.PreferencesKey as PrefsKey
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
Expand Down Expand Up @@ -132,6 +133,17 @@ class ForegroundService : Service() {
isTimeout = false
loadDataFromPreferences()

// Com stopWithTask ativo, o serviço é derrubado quando todas as activities pausam.
val prefs = getSharedPreferences(PrefsKey.FOREGROUND_TASK_OPTIONS_PREFS, Context.MODE_PRIVATE)
if (prefs.contains(PrefsKey.STOP_WITH_TASK) && prefs.getBoolean(PrefsKey.STOP_WITH_TASK, false)) {
(application as? Application)?.let {
TrackVisibilityUtils.install(it) {
// Passar true para preservar reattach de fabricante.
stopForegroundService(true)
}
}
}

var action = foregroundServiceStatus.action
val isSetStopWithTaskFlag = ForegroundServiceUtils.isSetStopWithTaskFlag(this)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ class ForegroundServiceManager {
ForegroundTaskOptions.updateData(context, argsMap)
ForegroundTaskData.updateData(context, argsMap)
NotificationContent.updateData(context, argsMap)
ContextCompat.startForegroundService(context, nIntent)
// Use startService instead of startForegroundService because the service
// is already running in the foreground and we only need to deliver the
// update command. This avoids the startForeground() contract requirement.
Comment on lines +65 to +67

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Esse comentário é seu ou da atualização?
Havíamos especificamente trocado para o contextCompat

pq voltou?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

É da atualização, e conferi que o contextCompat não tem startService, ele tem que ser usado para o startForegroundService.

O startService existe desde a API nível 1 e nisso não precisa de nenhuma compatibilidade com outros Androids, inclusive o ContextCompat para Androids abaixo do SDK 26 redireciona pro startService()
Image
https://developer.android.com/reference/android/content/Context#startService(android.content.Intent)

@JCPistori JCPistori Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pelo que entendi da alteração do repositório original, em um update chamar o startService() permite atualizar o serviço evitando que o startForeground() tenha que ser chamado em 5 segundos

context.startService(nIntent)
}

/** Stop the foreground service. */
Expand All @@ -78,7 +81,10 @@ class ForegroundServiceManager {
ForegroundTaskOptions.clearData(context)
ForegroundTaskData.clearData(context)
NotificationContent.clearData(context)
ContextCompat.startForegroundService(context, nIntent)
// Use startService instead of startForegroundService because the service
// is already running in the foreground and we only need to deliver the
// stop command. This avoids the startForeground() contract requirement.
context.startService(nIntent)
}

/** Send data to TaskHandler. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,4 @@ class RestartReceiver : BroadcastReceiver() {
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,29 @@ import android.content.pm.PackageManager.NameNotFoundException
import android.content.pm.ServiceInfo
import android.util.Log
import com.pravera.flutter_foreground_task.service.ForegroundService
import com.pravera.flutter_foreground_task.PreferencesKey as PrefsKey

class ForegroundServiceUtils {
companion object {
private val TAG = ForegroundServiceUtils::class.java.simpleName

fun isSetStopWithTaskFlag(context: Context): Boolean {
return try {
try {
val prefs = context.getSharedPreferences(PrefsKey.FOREGROUND_TASK_OPTIONS_PREFS, Context.MODE_PRIVATE)
if (prefs.contains(PrefsKey.STOP_WITH_TASK))
return prefs.getBoolean(PrefsKey.STOP_WITH_TASK, false)

val pm = context.packageManager
val cName = ComponentName(context, ForegroundService::class.java)
val flags = pm.getServiceInfo(cName, PackageManager.GET_META_DATA).flags
(flags and ServiceInfo.FLAG_STOP_WITH_TASK) == 1
return (flags and ServiceInfo.FLAG_STOP_WITH_TASK) != 0
} catch (e: NameNotFoundException) {
Log.e(TAG, "isSetStopWithTaskFlag >> The service component cannot be found on the system.")
true
return true
} catch (e: Exception) {
Log.e(TAG, "isSetStopWithTaskFlag >> $e")
true
return true
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.pravera.flutter_foreground_task.utils

import android.app.*
import android.os.*

object TrackVisibilityUtils : Application.ActivityLifecycleCallbacks {
private var installed = false
private val resumed = HashSet<Int>()
private var onAllGone: (() -> Unit)? = null

fun install(application: Application, callback: () -> Unit) {
onAllGone = callback
if (!installed) {
installed = true
application.registerActivityLifecycleCallbacks(this)
}
}

override fun onActivityResumed(activity: Activity) {
resumed.add(System.identityHashCode(activity))
}

override fun onActivityPaused(activity: Activity) {
resumed.remove(System.identityHashCode(activity))
if (resumed.isEmpty()) {
onAllGone?.invoke()
}
}

override fun onActivityStarted(activity: Activity) {}
override fun onActivityStopped(activity: Activity) {}
override fun onActivityCreated(a: Activity, b: Bundle?) {}
override fun onActivitySaveInstanceState(a: Activity, b: Bundle) {}
override fun onActivityDestroyed(a: Activity) {}
}
Loading