Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Add Compose Multiplatform support #11

Merged
merged 12 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
130 changes: 114 additions & 16 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ view the generated KDoc at [connectivity.jordond.dev](https://connectivity.jordo
- [Usage](#usage)
- [Options](#options)
- [HTTP monitoring](#http-monitoring)
- [Compose](#compose)
- [Multiple Targets](#multiple-targets)
- [Demo](#demo)
- [Contributing](#contributing)
- [License](#license)
Expand All @@ -39,13 +41,16 @@ Monitor network connectivity:

This library is written for Kotlin Multiplatform, and can be used on the following platforms:

| Artifact | Android | iOS | macOS | tvOS | Desktop | Browser (js/wasm) |
|------------------------|:-------:|:---:|:-----:|------|:-------:|:-----------------:|
| `connectivity-core` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `connectivity-device` | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| `connectivity-android` | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| `connectivity-apple` | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
| `connectivity-http` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Artifact | Android | iOS | macOS | tvOS | Desktop (JVM) | Browser (js/wasm) |
|-------------------------------|:-------:|:---:|:-----:|------|::|:-----------------:|
| `connectivity-core` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `connectivity-device` | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| `connectivity-android` | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| `connectivity-apple` | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
| `connectivity-http` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `connectivity-compose` | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ |
| `connectivity-compose-device` | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ |
| `connectivity-compose-http` | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ |

## Setup

Expand All @@ -61,6 +66,9 @@ connectivity-device = { module = "dev.jordond.connectivity:connectivity-device",
connectivity-android = { module = "dev.jordond.connectivity:connectivity-android", version.ref = "connectivity" }
connectivity-apple = { module = "dev.jordond.connectivity:connectivity-apple", version.ref = "connectivity" }
connectivity-http = { module = "dev.jordond.connectivity:connectivity-http", version.ref = "connectivity" }
connectivity-compose = { module = "dev.jordond.connectivity:connectivity-compose", version.ref = "connectivity" }
connectivity-compose-device = { module = "dev.jordond.connectivity:connectivity-compose-device", version.ref = "connectivity" }
connectivity-compose-http = { module = "dev.jordond.connectivity:connectivity-compose-http", version.ref = "connectivity" }
```

### Single Platform
Expand All @@ -69,8 +77,11 @@ Here is an example of how to add the dependencies to a single platform project t

```kotlin
dependencies {
implementation(libs.connectivity.core)
implementation(libs.connectivity.android)
implementation(libs.connectivity.core)
implementation(libs.connectivity.android)

// For compose support
implementation(libs.connectivity.compose.device)
}
```

Expand All @@ -83,8 +94,11 @@ Android and Apple devices:
kotlin {
sourceSets {
commonMain.dependencies {
implementation(libs.connectivity.core)
implementation(libs.connectivity.device)
implementation(libs.connectivity.core)
implementation(libs.connectivity.device)

// For compose support
implementation(libs.connectivity.compose.device)
}
}
}
Expand All @@ -100,15 +114,21 @@ It uses the `connectivity-device` for mobile targets, and `connectivity-http` fo
kotlin {
sourceSets {
commonMain.dependencies {
implementation(libs.connectivity.core)
implementation(libs.connectivity.core)

// For compose support
implementation(libs.connectivity.compose)
}

val deviceMain by creating {
dependsOn(commonMain.get())
androidMain.get().dependsOn(this)
appleMain.get().dependsOn(this)
dependencies {
implementation(libs.connectivity.device)
implementation(libs.connectivity.device)

// For compose support
implementation(libs.connectivity.compose.device)
}
}

Expand All @@ -118,7 +138,10 @@ kotlin {
jsMain.get().dependsOn(this)
wasmJsMain.get().dependsOn(this)
dependencies {
implementation(libs.connectivity.http)
implementation(libs.connectivity.http)

// For compose support
implementation(libs.connectivity.compose.http)
}
}
}
Expand Down Expand Up @@ -167,7 +190,7 @@ val connectivity = Connectivity()
connectivity.start()
coroutineScope.launch {
connectivity.updates.collect { update ->
println("Monitoring is active: ${update.isActive}")
println("Monitoring is active: ${update.isMonitoring}")

when (update.status) {
is NetworkStatus.Connected -> println("Connected to network")
Expand All @@ -179,7 +202,7 @@ coroutineScope.launch {

**Note:** Because the backing field for `Connectivity.updates` is a `StateFlow`, it requires a
initial value to be emitted. This means that the first value emitted will be the *real* current
network status. This is why the `isActive` property is available on the `Connectivity.Update`.
network status. This is why the `isMonitoring` property is available on the `Connectivity.Update`.

By default when you construct a `Connectivity` object, it will not automatically start monitoring
network connectivity. You can enable this by passing in `ConnectivityOptions`():
Expand Down Expand Up @@ -244,6 +267,81 @@ val connectivity = Connectivity {
}
```

## Compose

Connectivity also provides support for Compose Multiplatform. To use it you will have to make sure
you add the dependencies for the `connectivity-compose-x` modules.

Then you can use it like so:

**Note:** This composable is provided by either `connectivity-compose-device`
or `connectivity-compose-http` artifact.

```kotlin
@Composable
fun MyApp() {
val state = rememberConnectivityState {
// Optional configurator for ConnectivityOptions
autoStart = true
}

when (state.status) {
is Connectivity.Status.Connected -> Text("Connected to network")
is Connectivity.Status.Disconnected -> Text("Disconnected from network")
else -> {}
}
}
```

### Multiple Targets

If you need to support both Device and HTTP monitoring in the same project, you will have to do
something similar to [this](#all-supported-platforms).

Example:

```kotlin
// commonMain/Platform.kt
expect fun createConnectivity(): Connectivity
```

Then define the `actual` functions:

```kotlin
// deviceMain/Platform.device.kt
actual fun createConnectivityState(): Connectivity {
return Connectivity {
autoStart = true
}
}

// httpMain/Platform.http.kt
actual fun createConnectivityState(): Connectivity {
return Connectivity {
autoStart = true
urls("cloudflare.com", "my-own-domain.com")
port = 80
pollingIntervalMs = 10.minutes
timeoutMs = 5.seconds
}
}
```

Then it can be used like so:

```kotlin
@Composable
fun MyApp() {
val state = createConnectivityState()

when (state.status) {
is Connectivity.Status.Connected -> Text("Connected to network")
is Connectivity.Status.Disconnected -> Text("Disconnected from network")
else -> {}
}
}
```

## Demo

A demo app is available in the `demo` directory. It is a Compose Multiplatform app that runs on
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
public final class dev/jordond/connectivity/compose/DeviceConnectivityStateKt {
public static final fun rememberConnectivityState (Ldev/jordond/connectivity/ConnectivityOptions;Lkotlinx/coroutines/CoroutineScope;Landroidx/compose/runtime/Composer;II)Ldev/jordond/connectivity/compose/ConnectivityState;
public static final fun rememberConnectivityState (Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)Ldev/jordond/connectivity/compose/ConnectivityState;
}

29 changes: 29 additions & 0 deletions connectivity-compose-device/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.compose)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.poko)
alias(libs.plugins.dokka)
alias(libs.plugins.publish)
alias(libs.plugins.convention.multiplatform)
}

configureMultiplatform(Platforms.Mobile)

kotlin {
sourceSets {
commonMain.dependencies {
implementation(projects.connectivityCore)
api(projects.connectivityCompose)
api(projects.connectivityDevice)

implementation(compose.runtime)
implementation(compose.ui)
implementation(libs.kotlinx.coroutines.core)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package dev.jordond.connectivity.compose

import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import dev.jordond.connectivity.Connectivity
import dev.jordond.connectivity.ConnectivityOptions
import kotlinx.coroutines.CoroutineScope

/**
* Create and remember a [ConnectivityState] instance for Android and iOS platforms.
*
* @param options The [ConnectivityOptions] to use for configuring the network status monitoring.
* @param scope The [CoroutineScope] in which to launch the network status monitoring coroutine.
* @return A [ConnectivityState] instance.
*/
@Composable
public fun rememberConnectivityState(
options: ConnectivityOptions = ConnectivityOptions(),
scope: CoroutineScope = rememberCoroutineScope(),
): ConnectivityState {
val connectivity = remember(options, scope) {
Connectivity(options, scope)
}

return remember(options, scope, connectivity) {
ConnectivityState(connectivity, scope)
}
}

/**
* Create and remember a [ConnectivityState] instance for Android and iOS platforms.
*
* @param scope The [CoroutineScope] in which to launch the network status monitoring coroutine.
* @param block A lambda function to configure the [ConnectivityOptions] for the network status monitoring.
* @return A [ConnectivityState] instance.
*/
@Composable
public fun rememberConnectivityState(
scope: CoroutineScope = rememberCoroutineScope(),
block: ConnectivityOptions.Builder.() -> Unit,
): ConnectivityState {
val options = remember(block) {
ConnectivityOptions.build(block)
}

return rememberConnectivityState(options = options, scope = scope)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
public final class dev/jordond/connectivity/compose/HttpConnectivityStateKt {
public static final fun rememberConnectivityState (Ldev/jordond/connectivity/HttpConnectivityOptions;Lkotlinx/coroutines/CoroutineScope;Lio/ktor/client/HttpClient;Landroidx/compose/runtime/Composer;II)Ldev/jordond/connectivity/compose/ConnectivityState;
public static final fun rememberConnectivityState (Lkotlinx/coroutines/CoroutineScope;Lio/ktor/client/HttpClient;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)Ldev/jordond/connectivity/compose/ConnectivityState;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
public final class dev/jordond/connectivity/compose/HttpConnectivityStateKt {
public static final fun rememberConnectivityState (Ldev/jordond/connectivity/HttpConnectivityOptions;Lkotlinx/coroutines/CoroutineScope;Lio/ktor/client/HttpClient;Landroidx/compose/runtime/Composer;II)Ldev/jordond/connectivity/compose/ConnectivityState;
public static final fun rememberConnectivityState (Lkotlinx/coroutines/CoroutineScope;Lio/ktor/client/HttpClient;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)Ldev/jordond/connectivity/compose/ConnectivityState;
}

29 changes: 29 additions & 0 deletions connectivity-compose-http/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.compose)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.poko)
alias(libs.plugins.dokka)
alias(libs.plugins.publish)
alias(libs.plugins.convention.multiplatform)
}

configureMultiplatform(Platforms.Compose)

kotlin {
sourceSets {
commonMain.dependencies {
implementation(projects.connectivityCore)
api(projects.connectivityCompose)
api(projects.connectivityHttp)

implementation(compose.runtime)
implementation(compose.ui)
implementation(libs.kotlinx.coroutines.core)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package dev.jordond.connectivity.compose

import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import dev.jordond.connectivity.Connectivity
import dev.jordond.connectivity.ConnectivityOptions
import dev.jordond.connectivity.HttpConnectivityOptions
import io.ktor.client.HttpClient
import kotlinx.coroutines.CoroutineScope

/**
* Create and remember a [ConnectivityState] instance for Android and iOS platforms.
*
* @param options The [ConnectivityOptions] to use for configuring the network status monitoring.
* @param scope The [CoroutineScope] in which to launch the network status monitoring coroutine.
* @param httpClient The [HttpClient] instance to use for network requests.
* @return A [ConnectivityState] instance.
*/
@Composable
public fun rememberConnectivityState(
options: HttpConnectivityOptions = HttpConnectivityOptions(),
scope: CoroutineScope = rememberCoroutineScope(),
httpClient: HttpClient = HttpClient()
): ConnectivityState {
val connectivity = remember(options, scope, httpClient) {
Connectivity(options, scope, httpClient)
}

return remember(connectivity, scope) {
ConnectivityState(connectivity, scope)
}
}

/**
* Create and remember a [ConnectivityState] instance for Android and iOS platforms.
*
* @param scope The [CoroutineScope] in which to launch the network status monitoring coroutine.
* @param httpClient The [HttpClient] instance to use for network requests.
* @param block A lambda function to configure the [HttpConnectivityOptions] instance.
* @return A [ConnectivityState] instance.
*/
@Composable
public fun rememberConnectivityState(
scope: CoroutineScope = rememberCoroutineScope(),
httpClient: HttpClient = HttpClient(),
block: HttpConnectivityOptions.Builder.() -> Unit,
): ConnectivityState {
val options = remember(block) {
HttpConnectivityOptions.build(block)
}

return rememberConnectivityState(options = options, scope = scope, httpClient = httpClient)
}
Loading