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

Feat: Network Rule #7

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0055332
add konnection library
canonall Mar 20, 2023
308600d
implement NetworkListener using Konnection
canonall Mar 20, 2023
ca34720
add NetworkListener to JobQueue
canonall Mar 20, 2023
4cf22f8
implement NetworkRule
canonall Mar 20, 2023
bd96487
update tests
canonall Mar 20, 2023
65b1a20
move network check inside willRun
canonall Mar 20, 2023
8efa999
move withTimeout to JobQueue
canonall Mar 20, 2023
2635030
update network rule test
canonall Mar 20, 2023
d206da3
remove Konnection library
canonall Mar 20, 2023
4932108
implement NetworkManager without Konnection and update NetworkListener
canonall Mar 20, 2023
a9c940f
code clean-up
canonall Mar 20, 2023
a9c7358
fix flaky tests
canonall Mar 20, 2023
51f2b87
add missing actual keyword for ios NetworkManager
canonall Mar 21, 2023
e1d8a13
@SuppressLint("MissingPermission") - AndroidManifest and permissions …
canonall Mar 21, 2023
208effc
use abstract NetworkListener
canonall Mar 21, 2023
0bb1b79
observe network changes in the queue
canonall Mar 21, 2023
bac0add
update NetworkListener:
canonall Mar 22, 2023
95f862d
add new JobEvents
canonall Mar 22, 2023
185bed6
refactor NetworkException to NetworkRuleTimeoutException
canonall Mar 22, 2023
6ea1857
wait for network rule to be satisfied 15 seconds, otherwise cancel it
canonall Mar 22, 2023
ea76420
update test
canonall Mar 22, 2023
d92cc9d
chore: rename timeout to jobTimeout and allow user to define a networ…
canonall Mar 22, 2023
4b5b720
chore: fix tests
canonall Mar 22, 2023
164ce28
refactor: use named arguments and rename some variables
canonall Mar 23, 2023
5377d9d
call observeNetworkState() before running the job
canonall Mar 23, 2023
56801aa
refactor NetworkListeners and android network manager
canonall Mar 23, 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
6 changes: 6 additions & 0 deletions src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.liftric.job.queue">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>
12 changes: 8 additions & 4 deletions src/androidMain/kotlin/com/liftric/job/queue/JobQueue.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ actual class JobQueue(
context: Context,
serializers: SerializersModule = SerializersModule {},
configuration: Queue.Configuration = Queue.DefaultConfiguration,
store: JsonStorage = SettingsStorage(SharedPreferencesSettings.Factory(context).create("com.liftric.persisted.queue"))
networkListener: NetworkListener,
store: JsonStorage = SettingsStorage(
SharedPreferencesSettings.Factory(context).create("com.liftric.persisted.queue")
)
) : AbstractJobQueue(
serializers,
configuration,
store
serializers = serializers,
Copy link
Member

Choose a reason for hiding this comment

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

I like the use of named arguments.

networkListener = networkListener,
configuration = configuration,
store = store
)
37 changes: 37 additions & 0 deletions src/androidMain/kotlin/com/liftric/job/queue/NetworkListener.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.liftric.job.queue

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch

actual class NetworkListener(
networkManager: NetworkManager,
scope: CoroutineScope = CoroutineScope(context = Dispatchers.Default)
) : AbstractNetworkListener(
networkManager = networkManager,
scope = scope
) {
private val _currentNetworkState = MutableStateFlow(NetworkState.NONE)
override val currentNetworkState: StateFlow<NetworkState>
get() = _currentNetworkState.asStateFlow()

private var job: kotlinx.coroutines.Job? = null

override fun observeNetworkState() {
job = scope.launch {
networkManager
.network
.collectLatest { currentNetworkState ->
_currentNetworkState.emit(currentNetworkState ?: NetworkState.NONE)
}
}
}

override fun stopMonitoring() {
job?.cancel()
}
}
54 changes: 54 additions & 0 deletions src/androidMain/kotlin/com/liftric/job/queue/NetworkManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.liftric.job.queue

import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.Build
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow

@SuppressLint("MissingPermission")
actual class NetworkManager(context: Context) {
private var connectivityManager: ConnectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

private val connectionPublisher = MutableStateFlow(getCurrentNetworkConnection())
val network: Flow<NetworkState?> = connectionPublisher

private fun getCurrentNetworkConnection(): NetworkState? =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
postAndroidMNetworkConnection(connectivityManager)
} else {
preAndroidMNetworkConnection(connectivityManager)
}

@TargetApi(Build.VERSION_CODES.M)
Copy link
Member

Choose a reason for hiding this comment

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

Lovely to have it backwards compatible. We should internally discuss which API level makes sense for Flowify. I would rather start with Android 6 or 7 to get rid of some old chunks. But nice that you've implemented it with old versions in mind! Just leave it as is at the moment, we may remove it later on!

private fun postAndroidMNetworkConnection(connectivityManager: ConnectivityManager): NetworkState? {
val network = connectivityManager.activeNetwork
val capabilities = connectivityManager.getNetworkCapabilities(network)
return getNetworkConnection(capabilities)
}

@Suppress("DEPRECATION")
private fun preAndroidMNetworkConnection(connectivityManager: ConnectivityManager): NetworkState? =
when (connectivityManager.activeNetworkInfo?.type) {
null -> null
ConnectivityManager.TYPE_WIFI -> NetworkState.WIFI
else -> NetworkState.MOBILE
}

private fun getNetworkConnection(capabilities: NetworkCapabilities?): NetworkState? =
when {
capabilities == null -> null
Build.VERSION.SDK_INT < Build.VERSION_CODES.M
&& !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) -> null
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
!(capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
&& capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) -> null
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> NetworkState.WIFI
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> NetworkState.MOBILE
else -> null
}
}
24 changes: 15 additions & 9 deletions src/androidTest/kotlin/com/liftric/job/queue/JobQueueTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@ import kotlinx.serialization.modules.polymorphic
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
actual class JobQueueTests: AbstractJobQueueTests(JobQueue(
context = InstrumentationRegistry.getInstrumentation().targetContext,
serializers = SerializersModule {
polymorphic(Task::class) {
subclass(TestTask::class, TestTask.serializer())
}
},
store = MapStorage()
))
actual class JobQueueTests : AbstractJobQueueTests(
JobQueue(
context = InstrumentationRegistry.getInstrumentation().targetContext,
serializers = SerializersModule {
polymorphic(Task::class) {
subclass(TestTask::class, TestTask.serializer())
}
},
store = MapStorage(),
networkListener = NetworkListener(
networkManager = NetworkManager(InstrumentationRegistry.getInstrumentation().context)
)
)
)

65 changes: 39 additions & 26 deletions src/commonMain/kotlin/com/liftric/job/queue/Job.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.liftric.job.queue

import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.withTimeout
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.serialization.Serializable
Expand All @@ -19,47 +18,61 @@ data class Job(
override val info: JobInfo,
override val task: Task,
override val startTime: Instant
): JobContext {
@Transient internal var delegate: JobDelegate? = null
) : JobContext {
@Transient
internal var delegate: JobDelegate? = null

constructor(task: Task, info: JobInfo) : this (UUIDFactory.create(), info, task, Clock.System.now())
constructor(task: Task, info: JobInfo) : this(
id = UUIDFactory.create(),
info = info,
task = task,
startTime = Clock.System.now()
)

private var canRepeat: Boolean = true

suspend fun run(): JobEvent {
return withTimeout(info.timeout) {
val event = try {
info.rules.forEach { it.willRun(this@Job) }

task.body()

JobEvent.DidSucceed(this@Job)
} catch (e: CancellationException) {
throw e
} catch (e: Throwable) {
canRepeat = task.onRepeat(e)
JobEvent.DidFail(this@Job, e)
val event = try {
info.rules.forEach {
it.willRun(jobContext = this@Job)
}
task.body()
JobEvent.DidSucceed(job = this@Job)
} catch (e: CancellationException) {
throw e
} catch (e: Throwable) {
canRepeat = task.onRepeat(e)
JobEvent.DidFail(job = this@Job, error = e)
}

try {
info.rules.forEach { it.willRemove(this@Job, event) }

event
} catch (e: CancellationException) {
throw e
} catch (e: Throwable) {
JobEvent.DidFailOnRemove(this@Job, e)
try {
info.rules.forEach { rule ->
rule.willRemove(jobContext = this@Job, result = event)
}
} catch (e: CancellationException) {
throw e
} catch (e: Throwable) {
JobEvent.DidFailOnRemove(job = this@Job, error = e)
}
return event
}

override suspend fun cancel() {
delegate?.onEvent?.emit(JobEvent.DidCancel(this@Job))
delegate?.onEvent?.emit(JobEvent.DidCancel(job = this@Job))
}

override suspend fun repeat(id: UUID, info: JobInfo, task: Task, startTime: Instant) {
if (canRepeat) {
delegate?.onEvent?.emit(JobEvent.ShouldRepeat(Job(id, info, task, startTime)))
delegate?.onEvent?.emit(
JobEvent.ShouldRepeat(
Job(
id = id,
info = info,
task = task,
startTime = startTime
)
)
)
}
}
}
9 changes: 7 additions & 2 deletions src/commonMain/kotlin/com/liftric/job/queue/JobContext.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ package com.liftric.job.queue

import kotlinx.datetime.Instant

interface JobContext: JobData {
interface JobContext : JobData {
suspend fun cancel()
suspend fun repeat(id: UUID = this.id, info: JobInfo = this.info, task: Task = this.task, startTime: Instant = this.startTime)
suspend fun repeat(
id: UUID = this.id,
info: JobInfo = this.info,
task: Task = this.task,
startTime: Instant = this.startTime,
)
}

interface JobData {
Expand Down
3 changes: 3 additions & 0 deletions src/commonMain/kotlin/com/liftric/job/queue/JobEvent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ sealed class JobEvent {
data class ShouldRepeat(val job: Job): JobEvent()
data class DidCancel(val job: JobContext): JobEvent()
data class DidFailOnRemove(val job: JobContext, val error: Throwable): JobEvent()
data class NetworkRuleSatisfied(val job: JobContext): JobEvent()
data class NetworkRuleTimeout(val job: JobContext): JobEvent()
data class JobTimeout(val job: JobContext): JobEvent()
}
6 changes: 4 additions & 2 deletions src/commonMain/kotlin/com/liftric/job/queue/JobInfo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import kotlinx.serialization.Serializable
@Serializable
data class JobInfo(
var tag: String? = null,
var timeout: Duration = Duration.INFINITE,
var jobTimeout: Duration = Duration.INFINITE,
var networkRuleTimeout: Duration = Duration.INFINITE,
var rules: MutableList<JobRule> = mutableListOf(),
var shouldPersist: Boolean = false
var shouldPersist: Boolean = false,
var minRequiredNetworkState: NetworkState = NetworkState.NONE
)
Loading