expo-background-task 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +2 -0
- package/CHANGELOG.md +17 -0
- package/README.md +43 -0
- package/android/build.gradle +24 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/expo/modules/backgroundtask/BackgroundTaskConsumer.kt +54 -0
- package/android/src/main/java/expo/modules/backgroundtask/BackgroundTaskExceptions.kt +13 -0
- package/android/src/main/java/expo/modules/backgroundtask/BackgroundTaskModule.kt +66 -0
- package/android/src/main/java/expo/modules/backgroundtask/BackgroundTaskScheduler.kt +212 -0
- package/android/src/main/java/expo/modules/backgroundtask/BackgroundTaskWork.kt +36 -0
- package/app.plugin.js +1 -0
- package/build/BackgroundTask.d.ts +51 -0
- package/build/BackgroundTask.d.ts.map +1 -0
- package/build/BackgroundTask.js +89 -0
- package/build/BackgroundTask.js.map +1 -0
- package/build/BackgroundTask.types.d.ts +41 -0
- package/build/BackgroundTask.types.d.ts.map +1 -0
- package/build/BackgroundTask.types.js +31 -0
- package/build/BackgroundTask.types.js.map +1 -0
- package/build/ExpoBackgroundTaskModule.d.ts +11 -0
- package/build/ExpoBackgroundTaskModule.d.ts.map +1 -0
- package/build/ExpoBackgroundTaskModule.js +3 -0
- package/build/ExpoBackgroundTaskModule.js.map +1 -0
- package/build/ExpoBackgroundTaskModule.web.d.ts +6 -0
- package/build/ExpoBackgroundTaskModule.web.d.ts.map +1 -0
- package/build/ExpoBackgroundTaskModule.web.js +7 -0
- package/build/ExpoBackgroundTaskModule.web.js.map +1 -0
- package/expo-module.config.json +11 -0
- package/ios/BackgorundTaskExceptions.swift +48 -0
- package/ios/BackgroundTaskAppDelegate.swift +44 -0
- package/ios/BackgroundTaskConstants.swift +13 -0
- package/ios/BackgroundTaskConsumer.swift +57 -0
- package/ios/BackgroundTaskDebugHelper.swift +21 -0
- package/ios/BackgroundTaskModule.swift +78 -0
- package/ios/BackgroundTaskRecords.swift +12 -0
- package/ios/BackgroundTaskScheduler.swift +101 -0
- package/ios/ExpoBackgroundTask.podspec +32 -0
- package/package.json +43 -0
- package/plugin/build/withBackgroundTask.d.ts +3 -0
- package/plugin/build/withBackgroundTask.js +29 -0
- package/plugin/src/withBackgroundTask.ts +36 -0
- package/plugin/tsconfig.json +9 -0
- package/src/BackgroundTask.ts +105 -0
- package/src/BackgroundTask.types.ts +45 -0
- package/src/ExpoBackgroundTaskModule.ts +12 -0
- package/src/ExpoBackgroundTaskModule.web.ts +7 -0
- package/tsconfig.json +9 -0
package/.eslintrc.js
ADDED
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## Unpublished
|
|
4
|
+
|
|
5
|
+
### 🛠 Breaking changes
|
|
6
|
+
|
|
7
|
+
### 🎉 New features
|
|
8
|
+
|
|
9
|
+
### 🐛 Bug fixes
|
|
10
|
+
|
|
11
|
+
### 💡 Others
|
|
12
|
+
|
|
13
|
+
## 0.0.0 — 2025-01-21
|
|
14
|
+
|
|
15
|
+
### 🎉 New features
|
|
16
|
+
|
|
17
|
+
- Added expo-background-task package ([#33438](https://github.com/expo/expo/pull/33438) by [@chrfalch](https://github.com/chrfalch))
|
package/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# expo-background-task
|
|
2
|
+
|
|
3
|
+
Expo universal module for BackgroundTask API
|
|
4
|
+
|
|
5
|
+
# API documentation
|
|
6
|
+
|
|
7
|
+
- [Documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/background-task/)
|
|
8
|
+
- [Documentation for the main branch](https://docs.expo.dev/versions/unversioned/sdk/background-task/)
|
|
9
|
+
|
|
10
|
+
# Installation in managed Expo projects
|
|
11
|
+
|
|
12
|
+
For [managed](https://docs.expo.dev/archive/managed-vs-bare/) Expo projects, please follow the installation instructions in the [API documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/background-task/).
|
|
13
|
+
|
|
14
|
+
# Installation in bare React Native projects
|
|
15
|
+
|
|
16
|
+
For bare React Native projects, you must ensure that you have [installed and configured the `expo` package](https://docs.expo.dev/bare/installing-expo-modules/) before continuing.
|
|
17
|
+
|
|
18
|
+
### Add the package to your npm dependencies
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
npx expo install expo-background-task
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Configure for Android
|
|
25
|
+
|
|
26
|
+
No additional set up necessary.
|
|
27
|
+
|
|
28
|
+
### Configure for iOS
|
|
29
|
+
|
|
30
|
+
Run `npx pod-install` after installing the npm package.
|
|
31
|
+
|
|
32
|
+
In order to use `BackgroundTask` API in standalone, detached and bare apps on iOS, your app has to include the background task identifier in the `Info.plist` file. You can do this by adding the following XML snippet to your `Info.plist` file:
|
|
33
|
+
|
|
34
|
+
```xml
|
|
35
|
+
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
|
36
|
+
<array>
|
|
37
|
+
<string>com.expo.modules.backgroundtask.processing</string>
|
|
38
|
+
</array>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
# Contributing
|
|
42
|
+
|
|
43
|
+
Contributions are very welcome! Please refer to guidelines described in the [contributing guide](https://github.com/expo/expo#contributing).
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
apply plugin: 'com.android.library'
|
|
2
|
+
|
|
3
|
+
group = 'host.exp.exponent'
|
|
4
|
+
version = '0.0.0'
|
|
5
|
+
|
|
6
|
+
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
7
|
+
apply from: expoModulesCorePlugin
|
|
8
|
+
applyKotlinExpoModulesCorePlugin()
|
|
9
|
+
useCoreDependencies()
|
|
10
|
+
useDefaultAndroidSdkVersions()
|
|
11
|
+
useExpoPublishing()
|
|
12
|
+
|
|
13
|
+
dependencies {
|
|
14
|
+
implementation 'androidx.work:work-runtime-ktx:2.9.1'
|
|
15
|
+
implementation 'com.facebook.react:react-android'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
android {
|
|
19
|
+
namespace "expo.modules.backgroundtask"
|
|
20
|
+
defaultConfig {
|
|
21
|
+
versionCode 23
|
|
22
|
+
versionName "0.0.0"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
package expo.modules.backgroundtask
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.util.Log
|
|
5
|
+
import expo.modules.interfaces.taskManager.TaskConsumer
|
|
6
|
+
import expo.modules.interfaces.taskManager.TaskConsumerInterface
|
|
7
|
+
import expo.modules.interfaces.taskManager.TaskExecutionCallback
|
|
8
|
+
import expo.modules.interfaces.taskManager.TaskInterface
|
|
9
|
+
import expo.modules.interfaces.taskManager.TaskManagerUtilsInterface
|
|
10
|
+
|
|
11
|
+
class BackgroundTaskConsumer(context: Context?, taskManagerUtils: TaskManagerUtilsInterface?) :
|
|
12
|
+
TaskConsumer(context, taskManagerUtils), TaskConsumerInterface {
|
|
13
|
+
|
|
14
|
+
companion object {
|
|
15
|
+
private const val BACKGROUND_TASK_TYPE = "expo-background-task"
|
|
16
|
+
private val TAG = BackgroundTaskConsumer::class.java.simpleName
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
private var task: TaskInterface? = null
|
|
20
|
+
|
|
21
|
+
override fun taskType(): String {
|
|
22
|
+
return BACKGROUND_TASK_TYPE
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Exposing the execute task function so that the BackgroundTaskWork (WorkManager Work unit)
|
|
27
|
+
* can execute the inner task. A task is only executed if the app is in the background.
|
|
28
|
+
* This only applies when the app is in release mode.
|
|
29
|
+
*/
|
|
30
|
+
fun executeTask(callback: TaskExecutionCallback) {
|
|
31
|
+
Log.d(TAG, "Executing task '${task?.name}'")
|
|
32
|
+
taskManagerUtils.executeTask(task, null, callback)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
override fun didRegister(task: TaskInterface) {
|
|
36
|
+
Log.d(TAG, "didRegister: ${task.name}")
|
|
37
|
+
this.task = task
|
|
38
|
+
|
|
39
|
+
val intervalMinutes = getIntervalMinutes()
|
|
40
|
+
BackgroundTaskScheduler.registerTask(intervalMinutes)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
override fun didUnregister() {
|
|
44
|
+
Log.d(TAG, "didUnregister: ${task?.name}")
|
|
45
|
+
this.task = null
|
|
46
|
+
BackgroundTaskScheduler.unregisterTask()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private fun getIntervalMinutes(): Long {
|
|
50
|
+
val options = task?.options as? Map<String, Any?>
|
|
51
|
+
return (options?.get("minimumInterval") as? Number)?.toLong()
|
|
52
|
+
?: BackgroundTaskScheduler.DEFAULT_INTERVAL_MINUTES
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
package expo.modules.backgroundtask
|
|
2
|
+
|
|
3
|
+
import expo.modules.kotlin.exception.CodedException
|
|
4
|
+
|
|
5
|
+
internal class MissingContextException : CodedException(message = "Application context not found")
|
|
6
|
+
|
|
7
|
+
internal class MissingTaskServiceException : CodedException(message = "TaskService not available.")
|
|
8
|
+
|
|
9
|
+
internal class MissingAppScopeKey : CodedException(message = "Could not find required appScopeKey in worker.")
|
|
10
|
+
|
|
11
|
+
internal class TaskMangerInterfaceNotFoundException : CodedException(message = "TaskManagerInterface not found")
|
|
12
|
+
|
|
13
|
+
internal class TestMethodNotAvailableInProductionBuild : CodedException(message = "Background tasks cannot be triggered in production builds")
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
package expo.modules.backgroundtask
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import com.facebook.react.common.build.ReactBuildConfig
|
|
5
|
+
import expo.modules.interfaces.taskManager.TaskManagerInterface
|
|
6
|
+
import expo.modules.kotlin.functions.Coroutine
|
|
7
|
+
import expo.modules.kotlin.modules.Module
|
|
8
|
+
import expo.modules.kotlin.modules.ModuleDefinition
|
|
9
|
+
import kotlinx.coroutines.runBlocking
|
|
10
|
+
|
|
11
|
+
class BackgroundTaskModule : Module() {
|
|
12
|
+
companion object {
|
|
13
|
+
private val TAG = BackgroundTaskModule::class.java.simpleName
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
private val _taskManager by lazy { appContext.legacyModule<TaskManagerInterface>() }
|
|
17
|
+
private val taskManager: TaskManagerInterface
|
|
18
|
+
get() = _taskManager ?: throw TaskMangerInterfaceNotFoundException()
|
|
19
|
+
|
|
20
|
+
override fun definition() = ModuleDefinition {
|
|
21
|
+
Name("ExpoBackgroundTask")
|
|
22
|
+
|
|
23
|
+
AsyncFunction("getStatusAsync") {
|
|
24
|
+
return@AsyncFunction 2 // WorkManager is always available on Android.
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
AsyncFunction("triggerTaskWorkerForTestingAsync") Coroutine { ->
|
|
28
|
+
if (ReactBuildConfig.DEBUG) {
|
|
29
|
+
Log.d(TAG, "Triggering tasks for testing")
|
|
30
|
+
appContext.reactContext?.let {
|
|
31
|
+
val appScopeKey = it.packageName
|
|
32
|
+
return@Coroutine BackgroundTaskScheduler.runTasks(it, appScopeKey)
|
|
33
|
+
} ?: throw MissingContextException()
|
|
34
|
+
} else {
|
|
35
|
+
throw TestMethodNotAvailableInProductionBuild()
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
AsyncFunction("registerTaskAsync") { taskName: String, options: Map<String, Any?> ->
|
|
40
|
+
Log.d(TAG, "registerTaskAsync: $taskName")
|
|
41
|
+
taskManager.registerTask(taskName, BackgroundTaskConsumer::class.java, options)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
AsyncFunction("unregisterTaskAsync") { taskName: String ->
|
|
45
|
+
Log.d(TAG, "unregisterTaskAsync: $taskName")
|
|
46
|
+
taskManager.unregisterTask(taskName, BackgroundTaskConsumer::class.java)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
OnActivityEntersBackground {
|
|
50
|
+
appContext.reactContext?.let {
|
|
51
|
+
runBlocking {
|
|
52
|
+
val appScopeKey = it.packageName
|
|
53
|
+
BackgroundTaskScheduler.startWorker(it, appScopeKey)
|
|
54
|
+
}
|
|
55
|
+
} ?: throw MissingContextException()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
OnActivityEntersForeground {
|
|
59
|
+
appContext.reactContext?.let {
|
|
60
|
+
runBlocking {
|
|
61
|
+
BackgroundTaskScheduler.stopWorker(it)
|
|
62
|
+
}
|
|
63
|
+
} ?: throw MissingContextException()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
package expo.modules.backgroundtask
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.os.Build
|
|
5
|
+
import android.util.Log
|
|
6
|
+
import androidx.work.Constraints
|
|
7
|
+
import androidx.work.Data
|
|
8
|
+
import androidx.work.ExistingPeriodicWorkPolicy
|
|
9
|
+
import androidx.work.NetworkType
|
|
10
|
+
import androidx.work.Operation
|
|
11
|
+
import androidx.work.PeriodicWorkRequestBuilder
|
|
12
|
+
import androidx.work.WorkInfo
|
|
13
|
+
import androidx.work.WorkManager
|
|
14
|
+
import com.google.common.util.concurrent.ListenableFuture
|
|
15
|
+
import expo.modules.interfaces.taskManager.TaskServiceProviderHelper
|
|
16
|
+
import kotlinx.coroutines.CancellationException
|
|
17
|
+
import kotlinx.coroutines.CompletableDeferred
|
|
18
|
+
import kotlinx.coroutines.Dispatchers
|
|
19
|
+
import kotlinx.coroutines.awaitAll
|
|
20
|
+
import kotlinx.coroutines.withContext
|
|
21
|
+
import java.time.Duration
|
|
22
|
+
import java.util.concurrent.TimeUnit
|
|
23
|
+
|
|
24
|
+
object BackgroundTaskScheduler {
|
|
25
|
+
// Default interval
|
|
26
|
+
const val DEFAULT_INTERVAL_MINUTES = 60L * 24L // Once every day
|
|
27
|
+
|
|
28
|
+
// Unique identifier (generated by us) to identify the worker
|
|
29
|
+
private const val WORKER_IDENTIFIER = "EXPO_BACKGROUND_WORKER"
|
|
30
|
+
|
|
31
|
+
// Log tag
|
|
32
|
+
private val TAG = BackgroundTaskScheduler::class.java.simpleName
|
|
33
|
+
|
|
34
|
+
// Number of active task consumers
|
|
35
|
+
private var numberOfRegisteredTasksOfThisType = 0
|
|
36
|
+
|
|
37
|
+
// Interval
|
|
38
|
+
private var intervalMinutes: Long = DEFAULT_INTERVAL_MINUTES
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Call when a task is registered
|
|
42
|
+
*/
|
|
43
|
+
fun registerTask(intervalMinutes: Long) {
|
|
44
|
+
numberOfRegisteredTasksOfThisType += 1
|
|
45
|
+
this.intervalMinutes = intervalMinutes
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Call when a task is unregistered
|
|
50
|
+
*/
|
|
51
|
+
fun unregisterTask() {
|
|
52
|
+
numberOfRegisteredTasksOfThisType -= 1
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Schedules the worker task to run. The worker should run periodically.
|
|
57
|
+
*/
|
|
58
|
+
suspend fun startWorker(context: Context, appScopeKey: String): Boolean {
|
|
59
|
+
if (numberOfRegisteredTasksOfThisType == 0) {
|
|
60
|
+
Log.d(TAG, "Will not enqueue worker. No registered tasks to run.")
|
|
61
|
+
return false
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Stop the current worker (if any)
|
|
65
|
+
stopWorker(context)
|
|
66
|
+
|
|
67
|
+
Log.d(TAG, "Enqueuing worker with identifier $WORKER_IDENTIFIER")
|
|
68
|
+
|
|
69
|
+
// Build the work request
|
|
70
|
+
val data = Data.Builder()
|
|
71
|
+
.putString("appScopeKey", appScopeKey)
|
|
72
|
+
.build()
|
|
73
|
+
val constraints = Constraints.Builder()
|
|
74
|
+
.setRequiresBatteryNotLow(true)
|
|
75
|
+
.setRequiredNetworkType(NetworkType.CONNECTED)
|
|
76
|
+
.build()
|
|
77
|
+
|
|
78
|
+
// Create the work request
|
|
79
|
+
val builder = PeriodicWorkRequestBuilder<BackgroundTaskWork>(
|
|
80
|
+
repeatIntervalTimeUnit = TimeUnit.MINUTES,
|
|
81
|
+
repeatInterval = intervalMinutes
|
|
82
|
+
)
|
|
83
|
+
.setInputData(data)
|
|
84
|
+
.setConstraints(constraints)
|
|
85
|
+
|
|
86
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
87
|
+
// Add minimum interval here as well so that the work doesn't start immediately
|
|
88
|
+
builder.setInitialDelay(Duration.ofMinutes(intervalMinutes))
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Create work request
|
|
92
|
+
val workRequest = builder.build()
|
|
93
|
+
|
|
94
|
+
// Get Work manager
|
|
95
|
+
val workManager = WorkManager.getInstance(context)
|
|
96
|
+
|
|
97
|
+
// Enqueue the work
|
|
98
|
+
return try {
|
|
99
|
+
workManager.enqueueUniquePeriodicWork(
|
|
100
|
+
WORKER_IDENTIFIER,
|
|
101
|
+
ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE,
|
|
102
|
+
workRequest
|
|
103
|
+
).await()
|
|
104
|
+
|
|
105
|
+
true
|
|
106
|
+
} catch (e: Exception) {
|
|
107
|
+
Log.e(TAG, "Worker failed to start with error " + e.message)
|
|
108
|
+
false
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Cancels the worker task
|
|
114
|
+
*/
|
|
115
|
+
suspend fun stopWorker(context: Context): Boolean {
|
|
116
|
+
if (!isWorkerRunning(context)) {
|
|
117
|
+
return false
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
Log.d(TAG, "Cancelling worker with identifier $WORKER_IDENTIFIER")
|
|
121
|
+
|
|
122
|
+
// Stop our main worker
|
|
123
|
+
val workManager = WorkManager.getInstance(context)
|
|
124
|
+
return try {
|
|
125
|
+
workManager.cancelUniqueWork(WORKER_IDENTIFIER).await()
|
|
126
|
+
} catch (e: Exception) {
|
|
127
|
+
Log.d(TAG, "Stopping worker failed with error " + e.message)
|
|
128
|
+
false
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Returns true if the worker task is pending
|
|
134
|
+
*/
|
|
135
|
+
private suspend fun isWorkerRunning(context: Context): Boolean {
|
|
136
|
+
val workInfo = getWorkerInfo(context)
|
|
137
|
+
return workInfo?.state == WorkInfo.State.RUNNING ||
|
|
138
|
+
workInfo?.state == WorkInfo.State.ENQUEUED
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Runs tasks with the given appScopeKey
|
|
143
|
+
*/
|
|
144
|
+
suspend fun runTasks(context: Context, appScopeKey: String): Boolean {
|
|
145
|
+
// Get task service
|
|
146
|
+
val taskService = TaskServiceProviderHelper.getTaskServiceImpl(context)
|
|
147
|
+
?: throw MissingTaskServiceException()
|
|
148
|
+
|
|
149
|
+
Log.d(TAG, "runTasks: $appScopeKey")
|
|
150
|
+
|
|
151
|
+
// Get all task consumers
|
|
152
|
+
val consumers = taskService.getTaskConsumers(appScopeKey)
|
|
153
|
+
Log.d(TAG, "runTasks: number of consumers ${consumers.size}")
|
|
154
|
+
|
|
155
|
+
if (consumers.size == 0) {
|
|
156
|
+
return false
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
val tasks = consumers.filterIsInstance<BackgroundTaskConsumer>()
|
|
160
|
+
.map { bgTaskConsumer ->
|
|
161
|
+
Log.d(TAG, "runTasks: executing tasks for consumer of type ${bgTaskConsumer.taskType()}")
|
|
162
|
+
val taskCompletion = CompletableDeferred<Unit>()
|
|
163
|
+
bgTaskConsumer.executeTask {
|
|
164
|
+
Log.d(TAG, "Task successfully finished")
|
|
165
|
+
taskCompletion.complete(Unit)
|
|
166
|
+
}
|
|
167
|
+
taskCompletion
|
|
168
|
+
}
|
|
169
|
+
// Await all tasks to complete
|
|
170
|
+
tasks.awaitAll()
|
|
171
|
+
return true
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Returns the worker info object from the WorkManager if the worker has been
|
|
176
|
+
* registered, otherwise returns null
|
|
177
|
+
*/
|
|
178
|
+
private suspend fun getWorkerInfo(context: Context): WorkInfo? {
|
|
179
|
+
// Get work manager
|
|
180
|
+
val workManager = WorkManager.getInstance(context)
|
|
181
|
+
|
|
182
|
+
return try {
|
|
183
|
+
val workInfos = workManager.getWorkInfosForUniqueWork(WORKER_IDENTIFIER).await()
|
|
184
|
+
return workInfos.firstOrNull()
|
|
185
|
+
} catch (e: Exception) {
|
|
186
|
+
Log.d(TAG, "Calling getWorkInfosForUniqueWork failed with error " + e.message)
|
|
187
|
+
null
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Helper function for calling functions returning an Operation
|
|
193
|
+
*/
|
|
194
|
+
private suspend fun Operation.await(): Boolean = withContext(Dispatchers.IO) {
|
|
195
|
+
result.get()
|
|
196
|
+
true
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Helper function for calling functions returning a ListenableFuture
|
|
201
|
+
*/
|
|
202
|
+
private suspend fun <T> ListenableFuture<T>.await(): T = withContext(Dispatchers.IO) {
|
|
203
|
+
try {
|
|
204
|
+
get()
|
|
205
|
+
} catch (e: CancellationException) {
|
|
206
|
+
cancel(true)
|
|
207
|
+
throw e
|
|
208
|
+
} catch (e: Exception) {
|
|
209
|
+
throw e
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
package expo.modules.backgroundtask
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.util.Log
|
|
5
|
+
import androidx.work.CoroutineWorker
|
|
6
|
+
import androidx.work.Data
|
|
7
|
+
import androidx.work.WorkerParameters
|
|
8
|
+
|
|
9
|
+
class BackgroundTaskWork(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
|
|
10
|
+
companion object {
|
|
11
|
+
private val TAG = BackgroundTaskWork::class.java.simpleName
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The doWork function is called by the Android WorkManager to execute scheduled work.
|
|
16
|
+
*/
|
|
17
|
+
override suspend fun doWork(): Result {
|
|
18
|
+
Log.i(TAG, "doWork: Running worker")
|
|
19
|
+
|
|
20
|
+
// Get the app scope key
|
|
21
|
+
val appScopeKey = inputData.getString("appScopeKey") ?: throw MissingAppScopeKey()
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
BackgroundTaskScheduler.runTasks(applicationContext, appScopeKey)
|
|
25
|
+
} catch (e: Exception) {
|
|
26
|
+
// Wrap exception in Data:
|
|
27
|
+
val outputData = Data.Builder()
|
|
28
|
+
.putString("error", e.message)
|
|
29
|
+
.putString("stackTrace", e.stackTraceToString())
|
|
30
|
+
.build()
|
|
31
|
+
|
|
32
|
+
return Result.failure(outputData)
|
|
33
|
+
}
|
|
34
|
+
return Result.success()
|
|
35
|
+
}
|
|
36
|
+
}
|
package/app.plugin.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./plugin/build/withBackgroundTask');
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { BackgroundTaskOptions, BackgroundTaskStatus } from './BackgroundTask.types';
|
|
2
|
+
/**
|
|
3
|
+
* Returns the status for the Background Task API. On web, it always returns `BackgroundTaskStatus.Restricted`,
|
|
4
|
+
* while on native platforms it returns `BackgroundTaskStatus.Available`.
|
|
5
|
+
*
|
|
6
|
+
* @returns A BackgroundTaskStatus enum value or `null` if not available.
|
|
7
|
+
*/
|
|
8
|
+
export declare const getStatusAsync: () => Promise<BackgroundTaskStatus>;
|
|
9
|
+
/**
|
|
10
|
+
* Registers a background task with the given name. Registered tasks are saved in persistent storage and restored once the app is initialized.
|
|
11
|
+
* @param taskName Name of the task to register. The task needs to be defined first - see [`TaskManager.defineTask`](task-manager/#taskmanagerdefinetasktaskname-taskexecutor)
|
|
12
|
+
* for more details.
|
|
13
|
+
* @param options An object containing the background task options.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* import * as TaskManager from 'expo-task-manager';
|
|
18
|
+
*
|
|
19
|
+
* // Register the task outside of the component
|
|
20
|
+
* TaskManager.defineTask(BACKGROUND_TASK_IDENTIFIER, () => {
|
|
21
|
+
* try {
|
|
22
|
+
* await AsyncStorage.setItem(LAST_TASK_DATE_KEY, Date.now().toString());
|
|
23
|
+
* } catch (error) {
|
|
24
|
+
* console.error('Failed to save the last fetch date', error);
|
|
25
|
+
* return BackgroundTaskResult.Failed;
|
|
26
|
+
* }
|
|
27
|
+
* return BackgroundTaskResult.Success;
|
|
28
|
+
* });
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* You can now use the `registerTaskAsync` function to register the task:
|
|
32
|
+
*
|
|
33
|
+
* ```ts
|
|
34
|
+
* BackgroundTask.registerTaskAsync(BACKGROUND_TASK_IDENTIFIER, {});
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare function registerTaskAsync(taskName: string, options?: BackgroundTaskOptions): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Unregisters a background task, so the application will no longer be executing this task.
|
|
40
|
+
* @param taskName Name of the task to unregister.
|
|
41
|
+
* @return A promise which fulfils when the task is fully unregistered.
|
|
42
|
+
*/
|
|
43
|
+
export declare function unregisterTaskAsync(taskName: string): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* When in debug mode this function will trigger running the background tasks.
|
|
46
|
+
* This function will only work for apps built in debug mode.
|
|
47
|
+
* @todo(chrfalch): When we have a usable devtools plugin we can enable this function.
|
|
48
|
+
* @returns A promise which fulfils when the task is triggered.
|
|
49
|
+
*/
|
|
50
|
+
export { BackgroundTaskStatus, BackgroundTaskResult, BackgroundTaskOptions, } from './BackgroundTask.types';
|
|
51
|
+
//# sourceMappingURL=BackgroundTask.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BackgroundTask.d.ts","sourceRoot":"","sources":["../src/BackgroundTask.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAIrF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,QAAa,QAAQ,oBAAoB,CAMnE,CAAC;AAGF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,IAAI,CAAC,CAWf;AAGD;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMzE;AAGD;;;;;GAKG;AAcH,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { UnavailabilityError } from 'expo-modules-core';
|
|
2
|
+
import * as TaskManager from 'expo-task-manager';
|
|
3
|
+
import ExpoBackgroundTaskModule from './ExpoBackgroundTaskModule';
|
|
4
|
+
// @needsAudit
|
|
5
|
+
/**
|
|
6
|
+
* Returns the status for the Background Task API. On web, it always returns `BackgroundTaskStatus.Restricted`,
|
|
7
|
+
* while on native platforms it returns `BackgroundTaskStatus.Available`.
|
|
8
|
+
*
|
|
9
|
+
* @returns A BackgroundTaskStatus enum value or `null` if not available.
|
|
10
|
+
*/
|
|
11
|
+
export const getStatusAsync = async () => {
|
|
12
|
+
if (!ExpoBackgroundTaskModule.getStatusAsync) {
|
|
13
|
+
throw new UnavailabilityError('BackgroundTask', 'getStatusAsync');
|
|
14
|
+
}
|
|
15
|
+
return ExpoBackgroundTaskModule.getStatusAsync();
|
|
16
|
+
};
|
|
17
|
+
// @needsAudit
|
|
18
|
+
/**
|
|
19
|
+
* Registers a background task with the given name. Registered tasks are saved in persistent storage and restored once the app is initialized.
|
|
20
|
+
* @param taskName Name of the task to register. The task needs to be defined first - see [`TaskManager.defineTask`](task-manager/#taskmanagerdefinetasktaskname-taskexecutor)
|
|
21
|
+
* for more details.
|
|
22
|
+
* @param options An object containing the background task options.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* import * as TaskManager from 'expo-task-manager';
|
|
27
|
+
*
|
|
28
|
+
* // Register the task outside of the component
|
|
29
|
+
* TaskManager.defineTask(BACKGROUND_TASK_IDENTIFIER, () => {
|
|
30
|
+
* try {
|
|
31
|
+
* await AsyncStorage.setItem(LAST_TASK_DATE_KEY, Date.now().toString());
|
|
32
|
+
* } catch (error) {
|
|
33
|
+
* console.error('Failed to save the last fetch date', error);
|
|
34
|
+
* return BackgroundTaskResult.Failed;
|
|
35
|
+
* }
|
|
36
|
+
* return BackgroundTaskResult.Success;
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* You can now use the `registerTaskAsync` function to register the task:
|
|
41
|
+
*
|
|
42
|
+
* ```ts
|
|
43
|
+
* BackgroundTask.registerTaskAsync(BACKGROUND_TASK_IDENTIFIER, {});
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export async function registerTaskAsync(taskName, options = {}) {
|
|
47
|
+
if (!ExpoBackgroundTaskModule.registerTaskAsync) {
|
|
48
|
+
throw new UnavailabilityError('BackgroundTask', 'registerTaskAsync');
|
|
49
|
+
}
|
|
50
|
+
if (!TaskManager.isTaskDefined(taskName)) {
|
|
51
|
+
throw new Error(`Task '${taskName}' is not defined. You must define a task using TaskManager.defineTask before registering.`);
|
|
52
|
+
}
|
|
53
|
+
console.log('Calling ExpoBackgroundTaskModule.registerTaskAsync', { taskName, options });
|
|
54
|
+
await ExpoBackgroundTaskModule.registerTaskAsync(taskName, options);
|
|
55
|
+
}
|
|
56
|
+
// @needsAudit
|
|
57
|
+
/**
|
|
58
|
+
* Unregisters a background task, so the application will no longer be executing this task.
|
|
59
|
+
* @param taskName Name of the task to unregister.
|
|
60
|
+
* @return A promise which fulfils when the task is fully unregistered.
|
|
61
|
+
*/
|
|
62
|
+
export async function unregisterTaskAsync(taskName) {
|
|
63
|
+
if (!ExpoBackgroundTaskModule.unregisterTaskAsync) {
|
|
64
|
+
throw new UnavailabilityError('BackgroundTask', 'unregisterTaskAsync');
|
|
65
|
+
}
|
|
66
|
+
console.log('Calling ExpoBackgroundTaskModule.unregisterTaskAsync', taskName);
|
|
67
|
+
await ExpoBackgroundTaskModule.unregisterTaskAsync(taskName);
|
|
68
|
+
}
|
|
69
|
+
// @needsAudit
|
|
70
|
+
/**
|
|
71
|
+
* When in debug mode this function will trigger running the background tasks.
|
|
72
|
+
* This function will only work for apps built in debug mode.
|
|
73
|
+
* @todo(chrfalch): When we have a usable devtools plugin we can enable this function.
|
|
74
|
+
* @returns A promise which fulfils when the task is triggered.
|
|
75
|
+
*/
|
|
76
|
+
// export async function triggerTaskWorkerForTestingAsync(): Promise<boolean> {
|
|
77
|
+
// if (__DEV__) {
|
|
78
|
+
// if (!ExpoBackgroundTaskModule.triggerTaskWorkerForTestingAsync) {
|
|
79
|
+
// throw new UnavailabilityError('BackgroundTask', 'triggerTaskWorkerForTestingAsync');
|
|
80
|
+
// }
|
|
81
|
+
// console.log('Calling triggerTaskWorkerForTestingAsync');
|
|
82
|
+
// return await ExpoBackgroundTaskModule.triggerTaskWorkerForTestingAsync();
|
|
83
|
+
// } else {
|
|
84
|
+
// return Promise.resolve(false);
|
|
85
|
+
// }
|
|
86
|
+
// }
|
|
87
|
+
// Export types
|
|
88
|
+
export { BackgroundTaskStatus, BackgroundTaskResult, } from './BackgroundTask.types';
|
|
89
|
+
//# sourceMappingURL=BackgroundTask.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BackgroundTask.js","sourceRoot":"","sources":["../src/BackgroundTask.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AAGjD,OAAO,wBAAwB,MAAM,4BAA4B,CAAC;AAElE,cAAc;AACd;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,IAAmC,EAAE;IACtE,IAAI,CAAC,wBAAwB,CAAC,cAAc,EAAE;QAC5C,MAAM,IAAI,mBAAmB,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,CAAC;KACnE;IAED,OAAO,wBAAwB,CAAC,cAAc,EAAE,CAAC;AACnD,CAAC,CAAC;AAEF,cAAc;AACd;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,QAAgB,EAChB,UAAiC,EAAE;IAEnC,IAAI,CAAC,wBAAwB,CAAC,iBAAiB,EAAE;QAC/C,MAAM,IAAI,mBAAmB,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,CAAC;KACtE;IACD,IAAI,CAAC,WAAW,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE;QACxC,MAAM,IAAI,KAAK,CACb,SAAS,QAAQ,2FAA2F,CAC7G,CAAC;KACH;IACD,OAAO,CAAC,GAAG,CAAC,oDAAoD,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;IACzF,MAAM,wBAAwB,CAAC,iBAAiB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;AACtE,CAAC;AAED,cAAc;AACd;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,QAAgB;IACxD,IAAI,CAAC,wBAAwB,CAAC,mBAAmB,EAAE;QACjD,MAAM,IAAI,mBAAmB,CAAC,gBAAgB,EAAE,qBAAqB,CAAC,CAAC;KACxE;IACD,OAAO,CAAC,GAAG,CAAC,sDAAsD,EAAE,QAAQ,CAAC,CAAC;IAC9E,MAAM,wBAAwB,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC;AAC/D,CAAC;AAED,cAAc;AACd;;;;;GAKG;AACH,+EAA+E;AAC/E,mBAAmB;AACnB,wEAAwE;AACxE,6FAA6F;AAC7F,QAAQ;AACR,+DAA+D;AAC/D,gFAAgF;AAChF,aAAa;AACb,qCAAqC;AACrC,MAAM;AACN,IAAI;AAEJ,eAAe;AACf,OAAO,EACL,oBAAoB,EACpB,oBAAoB,GAErB,MAAM,wBAAwB,CAAC","sourcesContent":["import { UnavailabilityError } from 'expo-modules-core';\nimport * as TaskManager from 'expo-task-manager';\n\nimport { BackgroundTaskOptions, BackgroundTaskStatus } from './BackgroundTask.types';\nimport ExpoBackgroundTaskModule from './ExpoBackgroundTaskModule';\n\n// @needsAudit\n/**\n * Returns the status for the Background Task API. On web, it always returns `BackgroundTaskStatus.Restricted`,\n * while on native platforms it returns `BackgroundTaskStatus.Available`.\n *\n * @returns A BackgroundTaskStatus enum value or `null` if not available.\n */\nexport const getStatusAsync = async (): Promise<BackgroundTaskStatus> => {\n if (!ExpoBackgroundTaskModule.getStatusAsync) {\n throw new UnavailabilityError('BackgroundTask', 'getStatusAsync');\n }\n\n return ExpoBackgroundTaskModule.getStatusAsync();\n};\n\n// @needsAudit\n/**\n * Registers a background task with the given name. Registered tasks are saved in persistent storage and restored once the app is initialized.\n * @param taskName Name of the task to register. The task needs to be defined first - see [`TaskManager.defineTask`](task-manager/#taskmanagerdefinetasktaskname-taskexecutor)\n * for more details.\n * @param options An object containing the background task options.\n *\n * @example\n * ```ts\n * import * as TaskManager from 'expo-task-manager';\n *\n * // Register the task outside of the component\n * TaskManager.defineTask(BACKGROUND_TASK_IDENTIFIER, () => {\n * try {\n * await AsyncStorage.setItem(LAST_TASK_DATE_KEY, Date.now().toString());\n * } catch (error) {\n * console.error('Failed to save the last fetch date', error);\n * return BackgroundTaskResult.Failed;\n * }\n * return BackgroundTaskResult.Success;\n * });\n * ```\n *\n * You can now use the `registerTaskAsync` function to register the task:\n *\n * ```ts\n * BackgroundTask.registerTaskAsync(BACKGROUND_TASK_IDENTIFIER, {});\n * ```\n */\nexport async function registerTaskAsync(\n taskName: string,\n options: BackgroundTaskOptions = {}\n): Promise<void> {\n if (!ExpoBackgroundTaskModule.registerTaskAsync) {\n throw new UnavailabilityError('BackgroundTask', 'registerTaskAsync');\n }\n if (!TaskManager.isTaskDefined(taskName)) {\n throw new Error(\n `Task '${taskName}' is not defined. You must define a task using TaskManager.defineTask before registering.`\n );\n }\n console.log('Calling ExpoBackgroundTaskModule.registerTaskAsync', { taskName, options });\n await ExpoBackgroundTaskModule.registerTaskAsync(taskName, options);\n}\n\n// @needsAudit\n/**\n * Unregisters a background task, so the application will no longer be executing this task.\n * @param taskName Name of the task to unregister.\n * @return A promise which fulfils when the task is fully unregistered.\n */\nexport async function unregisterTaskAsync(taskName: string): Promise<void> {\n if (!ExpoBackgroundTaskModule.unregisterTaskAsync) {\n throw new UnavailabilityError('BackgroundTask', 'unregisterTaskAsync');\n }\n console.log('Calling ExpoBackgroundTaskModule.unregisterTaskAsync', taskName);\n await ExpoBackgroundTaskModule.unregisterTaskAsync(taskName);\n}\n\n// @needsAudit\n/**\n * When in debug mode this function will trigger running the background tasks.\n * This function will only work for apps built in debug mode.\n * @todo(chrfalch): When we have a usable devtools plugin we can enable this function.\n * @returns A promise which fulfils when the task is triggered.\n */\n// export async function triggerTaskWorkerForTestingAsync(): Promise<boolean> {\n// if (__DEV__) {\n// if (!ExpoBackgroundTaskModule.triggerTaskWorkerForTestingAsync) {\n// throw new UnavailabilityError('BackgroundTask', 'triggerTaskWorkerForTestingAsync');\n// }\n// console.log('Calling triggerTaskWorkerForTestingAsync');\n// return await ExpoBackgroundTaskModule.triggerTaskWorkerForTestingAsync();\n// } else {\n// return Promise.resolve(false);\n// }\n// }\n\n// Export types\nexport {\n BackgroundTaskStatus,\n BackgroundTaskResult,\n BackgroundTaskOptions,\n} from './BackgroundTask.types';\n"]}
|