Skip to content
This repository has been archived by the owner on Oct 14, 2023. It is now read-only.

Feature: Persist jobs as JSON #4

Merged
merged 30 commits into from
Jan 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
c706983
feat(job): support serialization
Dec 16, 2022
576e14a
fix(tests): serialize only if not void
Dec 16, 2022
b7aae68
feat(queue): persistence
Dec 16, 2022
502cb75
refactor(persistence): use userdefaults
Dec 16, 2022
22a2d4e
fix(tests): not running under ios and android
Dec 16, 2022
e567245
fix(queue): remove job after run
Dec 16, 2022
70d02b5
refactor(job): remove data type
Dec 16, 2022
b901ed6
fix(queue): serialization
Dec 16, 2022
ae3b54b
Merge pull request #5 from Liftric/feat/persistence
Dec 19, 2022
5a5ad37
refactor(queue): life cycle
Dec 19, 2022
8454e0a
fix(queue): coroutines
Dec 19, 2022
bf362bb
refactor(queue): final adjustements
Dec 20, 2022
0c965a6
fix(queue): ensure start only if not running
Dec 20, 2022
8b9d44e
Merge branch 'main' into feat/serializer
benjohnde Dec 27, 2022
a08d359
chore(gradle): bump to Kotlin 1.8
Jan 9, 2023
728cea9
chore(gradle): set java release 11
Jan 9, 2023
046daa4
chore(tests): try to fix flaky test
Jan 9, 2023
02dcd9e
refactor(queue): suspend with lock
Jan 10, 2023
0dde537
refactor(job): ignore cancellation exception
Jan 11, 2023
b77fdd2
refactor(job): simplify wiring and optimize cancellation
Jan 11, 2023
835b1a2
fix(queue): obj-c class not supported as hashmap key
Jan 11, 2023
01804a6
Revert "fix(queue): obj-c class not supported as hashmap key"
Jan 11, 2023
705aec7
fix(serializer): contextual uuid serializer not supported on ios
Jan 11, 2023
3ca1bf1
chore(android): bump target sdk
Jan 11, 2023
7ff1447
fix(android): namespace
Jan 11, 2023
27fce1f
feat(github): support publishing to Github packages!
Jan 12, 2023
7bbc406
refactor(README): update
Jan 12, 2023
54fea53
chore(README): update headline
Jan 12, 2023
df2d97f
refactor(queue): restrict access
Jan 12, 2023
fc0d1af
fix(tests): flakiness
Jan 12, 2023
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
11 changes: 5 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
name: Build & test

on:
pull_request:
types: [ opened, reopened, synchronize ]
Expand All @@ -8,19 +7,19 @@ on:
push:
branches:
- main

jobs:
test:
runs-on: macOS-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: set up JDK 11
uses: actions/setup-java@v1
uses: actions/setup-java@v3
with:
java-version: 11
java-version: '11'
distribution: 'adopt'
- name: Build and test
run: region=${{ secrets.region }} clientId=${{ secrets.clientid }} ./gradlew build test
run: ./gradlew build test
- name: Upload test result
if: ${{ always() }}
uses: actions/upload-artifact@v2
Expand Down
25 changes: 12 additions & 13 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
name: Publish to OSSRH
name: Publish to Github
on:
release:
types: [published]

jobs:
publish:
runs-on: macOS-latest

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v1
uses: actions/setup-java@v3
with:
java-version: 11
java-version: '11'
distribution: 'adopt'
- name: Validate Gradle wrapper
uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b
- name: Grant Permission to Execute
run: chmod +x gradlew
- name: New version
run: ./gradlew versionDisplay
- name: Publish Library
- name: Publish package
uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
with:
arguments: publish
env:
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.OSSRH_GPG_SECRET_KEY }}
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }}
ORG_GRADLE_PROJECT_ossrhUsername: ${{ secrets.OSSRH_USERNAME }}
ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.OSSRH_PASSWORD }}
ORG_GRADLE_PROJECT_npmAccessKey: ${{ secrets.NPMJS_ACCESS_KEY }}
run: region=${{ secrets.region }} clientId=${{ secrets.clientid }} ./gradlew publishAllPublicationsToSonatypeRepository
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
43 changes: 29 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Persisted-queue
# kmm-job-queue

Coroutine job scheduler inspired by `Android Work Manager` and `android-priority-jobqueue` for Kotlin Multiplatform projects. Run & repeat tasks. Rebuild them from disk. Fine tune execution with rules.
Coroutine job scheduler for Kotlin Multiplatform projects. Run & repeat tasks. Rebuild them from disk. Fine tune execution with rules.

The library depends on `kotlinx-serialization` for the persistence of the jobs.

⚠️ The project is still work in progress and shouldn't be used in a production project.

## Rules

Expand All @@ -9,15 +13,17 @@ Coroutine job scheduler inspired by `Android Work Manager` and `android-priority
- [x] Retry
- [x] Periodic
- [x] Unique
- [ ] Internet
- [ ] Network

## Capabilities

- [x] Cancellation (all, by id, by tag)
- [x] Cancellation (all, by id)
- [x] Start & stop scheduling
- [x] Restore from disk (after start)

## Example

Define a `DataTask<*>` or a `Task` (`DataTask<Unit>`), customize its body and limit when it should repeat.
Define a `Task` (or `DataTask<T>`), customize its body and limit when it should repeat.

⚠️ Make sure the data you pass into the task is serializable.

Expand All @@ -27,25 +33,31 @@ data class UploadData(val id: String)

class UploadTask(data: UploadData): DataTask<UploadData>(data) {
override suspend fun body() { /* Do something */ }
override suspend fun onRepeat(cause: Throwable): Boolean { cause is NetworkException }
override suspend fun onRepeat(cause: Throwable): Boolean { cause is NetworkException } // Won't retry if false
}
```

Create a single instance of the scheduler on app start. To start enqueuing jobs run `queue.start()`.
Create a single instance of the job queue on app start. To start enqueuing jobs run `jobQueue.start()`.

⚠️ You have to provide the polymorphic serializer of your custom task **if you want to persist it**.

You can pass a `Queue.Configuration` or a custom `JobSerializer` to the scheduler.
You can pass a custom `Queue.Configuration` or `JsonStorage` to the job queue.

```kotlin
val scheduler = JobScheduler()
scheduler.queue.start()
val jobQueue = JobQueue(serializers = SerializersModule {
polymorphic(Task::class) {
subclass(UploadTask::class, UploadTask.serializer())
}
})
jobQueue.start()
```

You can customize the jobs life cycle during schedule by defining rules.

```kotlin
val data = UploadData(id = ...)
val data = UploadData(id = "123456")

scheduler.schedule(UploadTask(data)) {
jobQueue.schedule(UploadTask(data)) {
unique(data.id)
retry(RetryLimit.Limited(3), delay = 30.seconds)
persist()
Expand All @@ -55,7 +67,10 @@ scheduler.schedule(UploadTask(data)) {
You can subscribe to life cycle events (e.g. for logging).

```kotlin
scheduler.onEvent.collect { event ->
Logger.info(event)
jobQueue.listener.collect { event ->
when (event) {
is JobEvent.DidFail -> Logger.error(event.error)
else -> Logger.info(event)
}
}
```
76 changes: 43 additions & 33 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeSimulatorTest

plugins {
id("com.android.library") version libs.versions.android.tools.gradle
kotlin("multiplatform") version libs.versions.kotlin
Expand All @@ -7,22 +9,13 @@ plugins {
id("signing")
}

repositories {
mavenCentral()
google()
gradlePluginPortal()
}

java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
group = "com.liftric"
version = with(versioning.info) {
if (branch == "HEAD" && dirty.not()) tag else full
}

kotlin {
ios {
binaries.framework()
}

ios()
iosSimulatorArm64()

android {
Expand All @@ -44,6 +37,8 @@ kotlin {
implementation(kotlin("test"))
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
implementation(libs.multiplatform.settings.test)
implementation(libs.kotlinx.coroutines.test)
}
}
val androidMain by getting
Expand All @@ -53,6 +48,9 @@ kotlin {
implementation(kotlin("test"))
implementation(kotlin("test-junit"))
implementation(libs.androidx.test.core)
implementation(libs.androidx.test.runner)
implementation(libs.androidx.test.ext)

}
}
val iosMain by getting
Expand All @@ -73,34 +71,40 @@ kotlin {
}

android {
compileSdk = 30
compileSdk = 33

namespace = "com.liftric.job.queue"

defaultConfig {
minSdk = 21
targetSdk = 30
targetSdk = 33
testInstrumentationRunner = "androidx.test.runner"
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}

testOptions {
unitTests.apply {
unitTests {
isReturnDefaultValues = true
}
}
publishing {
multipleVariants {
withSourcesJar()
withJavadocJar()
allVariants()
}
}
}

group = "com.liftric"
version = with(versioning.info) {
if (branch == "HEAD" && dirty.not()) tag else full
}

afterEvaluate {
project.publishing.publications.withType(MavenPublication::class.java).forEach {
it.groupId = group.toString()
tasks {
withType<KotlinNativeSimulatorTest> {
deviceId = "iPhone 14"
}
withType(JavaCompile::class) {
options.release.set(11)
}
}

Expand All @@ -114,11 +118,11 @@ val javadocJar by tasks.registering(Jar::class) {
publishing {
repositories {
maven {
name = "sonatype"
setUrl("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/")
name = "GitHubPackages"
setUrl("https://maven.pkg.github.com/Liftric/kmm-job-queue")
credentials {
username = ossrhUsername
password = ossrhPassword
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
}
}
}
Expand All @@ -128,13 +132,13 @@ publishing {

pom {
name.set(project.name)
description.set("Kotlin Multiplatform persisted queue library.")
url.set("https://github.com/liftric/cognito-idp")
description.set("Persistable coroutine job queue for Kotlin Multiplatform projects.")
url.set("https://github.com/Liftric/kmm-job-queue")

licenses {
license {
name.set("MIT")
url.set("https://github.com/liftric/cognito-idp/blob/master/LICENSE")
url.set("https://github.com/Liftric/kmm-job-queue/blob/master/LICENSE")
}
}
developers {
Expand All @@ -145,12 +149,18 @@ publishing {
}
}
scm {
url.set("https://github.com/liftric/persisted-queue")
url.set("https://github.com/Liftric/kmm-job-queue")
}
}
}
}

afterEvaluate {
project.publishing.publications.withType(MavenPublication::class.java).forEach {
it.groupId = group.toString()
}
}

signing {
val signingKey: String? by project
val signingPassword: String? by project
Expand Down
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
android.useAndroidX=true
android.disableAutomaticComponentCreation=true
org.gradle.parallel=true
org.gradle.jvmargs=-Xmx4096m
org.gradle.vfs.watch=true
kotlin.native.enableDependencyPropagation=false
kotlin.mpp.enableGranularSourceSetsMetadata=true
kotlin.mpp.enableCompatibilityMetadataVariant=true
kotlin.mpp.androidSourceSetLayoutVersion1.nowarn=true
kotlin.incremental=true
kotlin.incremental.multiplatform=true
kotlin.caching.enabled=true
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
14 changes: 9 additions & 5 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
rootProject.name = "persisted-queue"
rootProject.name = "job-queue"

pluginManagement {
repositories {
Expand All @@ -16,15 +16,19 @@ dependencyResolutionManagement {

versionCatalogs {
create("libs") {
version("android-tools-gradle", "7.2.2")
version("kotlin", "1.7.20")
version("android-tools-gradle", "7.3.0")
version("kotlin", "1.8.0")
library("kotlinx-coroutines", "org.jetbrains.kotlinx", "kotlinx-coroutines-core").version("1.6.4")
library("kotlinx-serialization", "org.jetbrains.kotlinx", "kotlinx-serialization-json").version("1.4.0")
library("kotlinx-atomicfu", "org.jetbrains.kotlinx", "atomicfu").version("0.18.5")
library("kotlinx-coroutines-test", "org.jetbrains.kotlinx", "kotlinx-coroutines-test").version("1.6.4")
library("kotlinx-serialization", "org.jetbrains.kotlinx", "kotlinx-serialization-json").version("1.4.1")
library("kotlinx-atomicfu", "org.jetbrains.kotlinx", "atomicfu").version("0.19.0")
library("kotlinx-datetime", "org.jetbrains.kotlinx", "kotlinx-datetime").version("0.4.0")
library("androidx-test-core", "androidx.test", "core").version("1.4.0")
library("androidx-test-runner", "androidx.test", "runner").version("1.4.0")
library("androidx-test-ext", "androidx.test.ext", "junit").version("1.1.3")
library("roboelectric", "org.robolectric", "robolectric").version("4.5.1")
library("multiplatform-settings", "com.russhwolf", "multiplatform-settings").version("1.0.0-RC")
library("multiplatform-settings-test", "com.russhwolf", "multiplatform-settings-test").version("1.0.0-RC")
plugin("versioning", "net.nemerosa.versioning").version("3.0.0")
plugin("kotlin.serialization", "org.jetbrains.kotlin.plugin.serialization").versionRef("kotlin")
}
Expand Down
16 changes: 16 additions & 0 deletions src/androidMain/kotlin/com/liftric/job/queue/JobQueue.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.liftric.job.queue

import android.content.Context
import com.russhwolf.settings.SharedPreferencesSettings
import kotlinx.serialization.modules.SerializersModule

actual class JobQueue(
context: Context,
serializers: SerializersModule = SerializersModule {},
configuration: Queue.Configuration = Queue.DefaultConfiguration,
store: JsonStorage = SettingsStorage(SharedPreferencesSettings.Factory(context).create("com.liftric.persisted.queue"))
) : AbstractJobQueue(
serializers,
configuration,
store
)
Loading