Skip to content

Commit

Permalink
add mobile implementations of connectivity
Browse files Browse the repository at this point in the history
  • Loading branch information
jordond committed Jun 7, 2024
1 parent 4542201 commit 338751d
Show file tree
Hide file tree
Showing 21 changed files with 250 additions and 3 deletions.
23 changes: 23 additions & 0 deletions connectivity-android/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import dev.jordond.connectivity.convention.Platform
import dev.jordond.connectivity.convention.configureMultiplatform

plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.multiplatform)
alias(libs.plugins.poko)
alias(libs.plugins.dokka)
alias(libs.plugins.publish)
alias(libs.plugins.convention.multiplatform)
}

configureMultiplatform(Platform.Android)

kotlin {
sourceSets {
androidMain.dependencies {
api(projects.connectivityCore)
implementation(projects.connectivityToolsAndroid)
implementation(libs.androidx.core)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package dev.jordond.connectivity.android

import dev.jordond.connectivity.Connectivity
import dev.jordond.connectivity.ConnectivityOptions
import dev.jordond.connectivity.android.internal.AndroidConnectivityProvider
import dev.jordond.connectivity.tools.ContextProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers

public fun Connectivity(
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
options: ConnectivityOptions.Builder.() -> Unit = {},
): Connectivity {
val context = ContextProvider.getInstance().context
val provider = AndroidConnectivityProvider(context)
return Connectivity(provider, scope, options)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package dev.jordond.connectivity.android.internal

import android.annotation.SuppressLint
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.NetworkRequest
import androidx.core.content.getSystemService
import dev.jordond.connectivity.android.Connectivity
import dev.jordond.connectivity.ConnectivityProvider
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flowOf

internal class AndroidConnectivityProvider(
private val context: Context,
) : ConnectivityProvider {

@SuppressLint("MissingPermission")
override fun monitor(): Flow<Connectivity.Status> {
val connectivityManager = context.getSystemService<ConnectivityManager>()
?: return flowOf(Connectivity.Status.Disconnected)

return callbackFlow {
val networkRequest = NetworkRequest.Builder().build()

val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
val capability = connectivityManager.getNetworkCapabilities(network)
val isWifi = capability?.hasTransport(TRANSPORT_WIFI) ?: false
val isCellular = capability?.hasTransport(TRANSPORT_CELLULAR) ?: false
val isMetered =
!isWifi || isCellular || connectivityManager.isActiveNetworkMetered

val status = Connectivity.Status.Connected(isMetered)
trySend(status)
}

override fun onLost(network: Network) {
trySend(Connectivity.Status.Disconnected)
}
}

connectivityManager.registerNetworkCallback(networkRequest, networkCallback)

awaitClose { connectivityManager.unregisterNetworkCallback(networkCallback) }
}
}
}
File renamed without changes.
21 changes: 21 additions & 0 deletions connectivity-ios/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import dev.jordond.connectivity.convention.Platform
import dev.jordond.connectivity.convention.configureMultiplatform

plugins {
alias(libs.plugins.multiplatform)
alias(libs.plugins.poko)
alias(libs.plugins.dokka)
alias(libs.plugins.publish)
alias(libs.plugins.convention.multiplatform)
}

configureMultiplatform(Platform.Ios)

kotlin {
sourceSets {
iosMain.dependencies {
api(projects.connectivityCore)
implementation(libs.kotlinx.coroutines.core)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package dev.jordond.connectivity.ios

import dev.jordond.connectivity.Connectivity
import dev.jordond.connectivity.ConnectivityOptions
import dev.jordond.connectivity.ios.internal.IosConnectivityProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers

public fun Connectivity(
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
options: ConnectivityOptions.Builder.() -> Unit = {},
): Connectivity {
val provider = IosConnectivityProvider()
return Connectivity(provider, scope, options)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package dev.jordond.connectivity.ios.internal

import dev.jordond.connectivity.ios.Connectivity
import dev.jordond.connectivity.ConnectivityProvider
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import platform.Network.nw_interface_type_wifi
import platform.Network.nw_path_monitor_cancel
import platform.Network.nw_path_monitor_create
import platform.Network.nw_path_monitor_set_queue
import platform.Network.nw_path_monitor_set_update_handler
import platform.Network.nw_path_monitor_start
import platform.Network.nw_path_status_satisfied
import platform.Network.nw_path_uses_interface_type
import platform.NetworkExtension.NWPath
import platform.NetworkExtension.NWPathStatus
import platform.darwin.DISPATCH_QUEUE_PRIORITY_DEFAULT
import platform.darwin.dispatch_get_global_queue

internal class IosConnectivityProvider : ConnectivityProvider {

override fun monitor(): Flow<Connectivity.Status> {
val monitor = nw_path_monitor_create()
val queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.toLong(), flags = 0uL)

return callbackFlow {
nw_path_monitor_set_update_handler(monitor) { path ->
val nwPath: NWPath? = path as? NWPath
val status: NWPathStatus? = nwPath?.status()
when {
status != null && status.toLong() == nw_path_status_satisfied.toLong() -> {
val isWifi = nw_path_uses_interface_type(path, nw_interface_type_wifi)
val isMetered = !isWifi && (path.isExpensive() || path.isConstrained())

trySend(Connectivity.Status.Connected(isMetered))
}
else -> Connectivity.Status.Disconnected
}
}

nw_path_monitor_set_queue(monitor, queue)
nw_path_monitor_start(monitor)

awaitClose {
nw_path_monitor_cancel(monitor)
}
}
}
}
29 changes: 29 additions & 0 deletions connectivity-mobile/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import dev.jordond.connectivity.convention.Platforms
import dev.jordond.connectivity.convention.configureMultiplatform

plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.multiplatform)
alias(libs.plugins.poko)
alias(libs.plugins.dokka)
alias(libs.plugins.publish)
alias(libs.plugins.convention.multiplatform)
}

configureMultiplatform(Platforms.Mobile)

kotlin {
sourceSets {
commonMain.dependencies {
api(projects.connectivityCore)
}

androidMain.dependencies {
api(projects.connectivityAndroid)
}

iosMain.dependencies {
api(projects.connectivityIos)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package dev.jordond.connectivity

import dev.jordond.connectivity.android.Connectivity
import kotlinx.coroutines.CoroutineScope

public actual fun Connectivity(
scope: CoroutineScope,
options: ConnectivityOptions.Builder.() -> Unit,
): Connectivity = Connectivity(scope, options)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package dev.jordond.connectivity

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers

public expect fun Connectivity(
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
options: ConnectivityOptions.Builder.() -> Unit = {},
): Connectivity

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package dev.jordond.connectivity

import dev.jordond.connectivity.ios.Connectivity
import kotlinx.coroutines.CoroutineScope

public actual fun Connectivity(
scope: CoroutineScope,
options: ConnectivityOptions.Builder.() -> Unit,
): Connectivity = Connectivity(scope, options)
2 changes: 1 addition & 1 deletion connectivity-tools-android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ configureMultiplatform(Platform.Android)
kotlin {
sourceSets {
androidMain.dependencies {
implementation(projects.connectivity)
implementation(projects.connectivityCore)
implementation(libs.androidx.startup)
implementation(libs.androidx.activity)
}
Expand Down
2 changes: 1 addition & 1 deletion demo/composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ kotlin {

sourceSets {
commonMain.dependencies {
implementation(projects.connectivity)
implementation(projects.connectivityCore)

implementation(libs.kotlinx.coroutines.core)
implementation(compose.runtime)
Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ kotlinx-atomicfu = "0.24.0"
compose = "1.6.7"
compose-multiplatform = "1.6.11"
androidx-activity = "1.9.0"
androidx-core = "1.13.1"
androidx-fragment = "1.7.1"
androidx-startup = "1.1.1"
androidx-activityCompose = "1.9.0"
Expand All @@ -32,6 +33,7 @@ androidx-activity-compose = { module = "androidx.activity:activity-compose", ver
androidx-activity = { module = "androidx.activity:activity-ktx", version.ref = "androidx-activity" }
androidx-fragment = { module = "androidx.fragment:fragment-ktx", version.ref = "androidx-fragment" }
androidx-startup = { module = "androidx.startup:startup-runtime", version.ref = "androidx-startup" }
androidx-core = { group = "androidx.core", name = "core-ktx", version.ref = "androidx-core" }
kotlinx-atomicfu = { module = "org.jetbrains.kotlinx:atomicfu", version.ref = "kotlinx-atomicfu" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
Expand Down
5 changes: 4 additions & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ develocity {
rootProject.name = "connectivity"

include(
":connectivity",
":connectivity-core",
":connectivity-android",
":connectivity-ios",
":connectivity-mobile",
":connectivity-tools-android",
)

Expand Down

0 comments on commit 338751d

Please sign in to comment.