Skip to content

Commit

Permalink
[runtime] remove androidx.startup dependency (#5761)
Browse files Browse the repository at this point in the history
* remove androidx.startup dependency

* initialize the ConnectivityManager in a background thread

* focus the RetryInterceptor on Network errors, remove ApolloClient.Builder.retryOnErrorInterceptor

* Add ensureUniqueUuids

* apiDump

* private

* always call the callback after the listener is set
  • Loading branch information
martinbonnin authored Mar 25, 2024
1 parent 2f66d39 commit bdaae07
Show file tree
Hide file tree
Showing 23 changed files with 246 additions and 247 deletions.
8 changes: 8 additions & 0 deletions build-logic/src/main/kotlin/Android.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@

import com.android.build.api.dsl.CommonExtension
import com.android.build.api.dsl.LibraryExtension
import com.android.build.api.dsl.LibraryVariantDimension
import org.gradle.api.Project

fun Project.configureAndroid(
namespace: String,
androidOptions: AndroidOptions
) {
plugins.apply("com.android.library")

extensions.findByName("android")?.apply {
this as CommonExtension<*, *, *, *, *>

Expand All @@ -21,6 +23,10 @@ fun Project.configureAndroid(
getCatalogVersion("android.sdkversion.min").toInt()
}
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

if (this is LibraryVariantDimension) {
multiDexEnabled = true
}
}

if (this is LibraryExtension) {
Expand All @@ -38,4 +44,6 @@ fun Project.configureAndroid(
}
}
}

dependencies.add("implementation", getCatalogLib("androidx.multidex"))
}
6 changes: 3 additions & 3 deletions build-logic/src/main/kotlin/api.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ fun Project.apolloLibrary(
"com.apollographql.apollo3.annotations.ApolloInternal"
)

configureTesting()

if (publish) {
configurePublishing()
}
Expand All @@ -62,6 +60,8 @@ fun Project.apolloLibrary(
)
}

configureTesting()

tasks.withType(Jar::class.java).configureEach {
manifest {
attributes(mapOf("Automatic-Module-Name" to namespace))
Expand All @@ -81,7 +81,6 @@ fun Project.apolloTest(
"com.apollographql.apollo3.annotations.ApolloExperimental",
"com.apollographql.apollo3.annotations.ApolloInternal",
)
configureTesting()

if (extensions.findByName("kotlin") is KotlinMultiplatformExtension) {
configureMpp(
Expand All @@ -94,6 +93,7 @@ fun Project.apolloTest(
withWasm = false
)
}
configureTesting()
}

fun Project.apolloRoot(ciBuild: TaskProvider<Task>) {
Expand Down
1 change: 1 addition & 0 deletions gradle/libraries.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ androidx-core = "androidx.core:core-ktx:1.12.0"
androidx-espresso-idlingresource = { group = "androidx.test.espresso", name = "espresso-idling-resource", version = "3.5.1" }
androidx-lint-rules = "androidx.lint:lint-gradle:1.0.0-alpha01"
androidx-lint-gradle-plugin = { module = "com.android.lint:com.android.lint.gradle.plugin", version.ref = "android-plugin" }
androidx-multidex = "androidx.multidex:multidex:2.0.1"
androidx-paging-compose = "androidx.paging:paging-compose:1.0.0-alpha18"
androidx-profileinstaller = "androidx.profileinstaller:profileinstaller:1.3.1"
androidx-sqlite = { group = "androidx.sqlite", name = "sqlite", version.ref = "androidx-sqlite" }
Expand Down
11 changes: 0 additions & 11 deletions libraries/apollo-runtime/api/android/apollo-runtime.api
Original file line number Diff line number Diff line change
Expand Up @@ -136,17 +136,6 @@ public final class com/apollographql/apollo3/ApolloClient$Companion {
public final fun builder ()Lcom/apollographql/apollo3/ApolloClient$Builder;
}

public final class com/apollographql/apollo3/ApolloInitializer : androidx/startup/Initializer {
public static final field Companion Lcom/apollographql/apollo3/ApolloInitializer$Companion;
public fun <init> ()V
public synthetic fun create (Landroid/content/Context;)Ljava/lang/Object;
public fun create (Landroid/content/Context;)V
public fun dependencies ()Ljava/util/List;
}

public final class com/apollographql/apollo3/ApolloInitializer$Companion {
}

public final class com/apollographql/apollo3/AutoPersistedQueryInfo : com/apollographql/apollo3/api/ExecutionContext$Element {
public static final field Key Lcom/apollographql/apollo3/AutoPersistedQueryInfo$Key;
public fun <init> (Z)V
Expand Down
3 changes: 0 additions & 3 deletions libraries/apollo-runtime/api/jvm/apollo-runtime.api
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,6 @@ public final class com/apollographql/apollo3/interceptor/AutoPersistedQueryInter
public final class com/apollographql/apollo3/interceptor/AutoPersistedQueryInterceptor$Companion {
}

public final class com/apollographql/apollo3/network/NetworkMonitorKt {
}

public abstract interface class com/apollographql/apollo3/network/NetworkTransport {
public abstract fun dispose ()V
public abstract fun execute (Lcom/apollographql/apollo3/api/ApolloRequest;)Lkotlinx/coroutines/flow/Flow;
Expand Down
1 change: 0 additions & 1 deletion libraries/apollo-runtime/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ kotlin {

findByName("androidMain")?.apply {
dependencies {
implementation(libs.androidx.startup.runtime)
implementation(libs.androidx.annotation)
implementation(libs.androidx.core)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,54 @@
package instrumented

import com.apollographql.apollo3.mockserver.MockResponse
import androidx.test.platform.app.InstrumentationRegistry
import com.apollographql.apollo3.api.http.HttpRequest
import com.apollographql.apollo3.api.http.HttpResponse
import com.apollographql.apollo3.exception.ApolloNetworkException
import com.apollographql.apollo3.mockserver.assertNoRequest
import com.apollographql.apollo3.mockserver.enqueueString
import com.apollographql.apollo3.network.NetworkMonitor
import com.apollographql.apollo3.network.http.DefaultHttpEngine
import com.apollographql.apollo3.network.http.HttpEngine
import com.apollographql.apollo3.testing.FooQuery
import com.apollographql.apollo3.testing.mockServerTest
import org.junit.Test
import kotlin.test.Test
import kotlin.test.assertEquals

class NetworkMonitorTest {
/**
* A test that runs on a real device to test the network monitor.
* Start it with Airplane mode on and the test should terminate when you disable Airplane mode.
*/
class FaultyHttpEngine: HttpEngine {
private var first = true
var received = 0
val delegate = DefaultHttpEngine()
override suspend fun execute(request: HttpRequest): HttpResponse {
received++
if (first) {
first = false
throw ApolloNetworkException("Ooopsie")
} else {
return delegate.execute(request)
}
}
}

@Test
fun test() = mockServerTest(
skipDelays = false,
clientBuilder = {
retryOnError(true)
networkMonitor(NetworkMonitor(InstrumentationRegistry.getInstrumentation().context))
retryOnError { true }
httpEngine(FaultyHttpEngine())
}
) {
mockServer.enqueue(MockResponse.Builder().statusCode(500).build())
mockServer.enqueueString(FooQuery.successResponse)

val response = apolloClient.query(FooQuery()).execute()

assertEquals(42, response.data?.foo)

mockServer.takeRequest()
mockServer.takeRequest()
mockServer.assertNoRequest()
}
Expand Down
11 changes: 0 additions & 11 deletions libraries/apollo-runtime/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,14 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application>
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<!-- This entry makes ApolloInitializer discoverable. -->
<meta-data android:name="com.apollographql.apollo3.ApolloInitializer"
android:value="androidx.startup" />
</provider>
</application>
</manifest>

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ import com.apollographql.apollo3.annotations.ApolloExperimental
/**
* Returns a new [NetworkMonitor] for the given [Context]
*
* Use this function in contexts where androidx.startup is not available
* In order to work correctly, this requires:
* - minSdk >= 23
* - declaring the [ACCESS_NETWORK_STATE](https://developer.android.com/reference/android/Manifest.permission#ACCESS_NETWORK_STATE) permission in your Manifest
*
* If one of these conditions is not satisfied, the returned [NetworkMonitor] will behave as if the device were always online.
*/
@ApolloExperimental
fun NetworkMonitor(context: Context): NetworkMonitor? = platformConnectivityManager(context)?.let { DefaultNetworkMonitor(it) }
fun NetworkMonitor(context: Context): NetworkMonitor = DefaultNetworkMonitor { networkObserver(context) }
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,38 @@ import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES.M
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import com.apollographql.apollo3.ApolloInitializer
import java.lang.ref.WeakReference

/**
* isPermissionGranted, WeakReferences and other things here inspired by Coil [NetworkObserver](https://github.com/coil-kt/coil/blob/24375db1775fb46f0e184501646cd9e150185608/coil-core/src/androidMain/kotlin/coil3/util/NetworkObserver.kt)
*/
internal fun Context.isPermissionGranted(permission: String): Boolean {
return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
}


@RequiresApi(M)
@SuppressLint("MissingPermission")
internal class AndroidPlatformConnectivityManager(private val connectivityManager: ConnectivityManager) : PlatformConnectivityManager {
private var listener: WeakReference<PlatformConnectivityManager.Listener>? = null
internal class AndroidNetworkObserver(private val connectivityManager: ConnectivityManager) : NetworkObserver {
private var listener: WeakReference<NetworkObserver.Listener>? = null

/**
* Not locked because I'm assuming the [NetworkCallback] is always called on the same thread
* and the thread safety comes from [DefaultNetworkMonitor._isOnline]
*/
private var onlineNetworks = mutableSetOf<Long>()

private val networkCallback: NetworkCallback = object : NetworkCallback() {
override fun onAvailable(network: Network) = onConnectivityChange(true)
override fun onLost(network: Network) = onConnectivityChange(false)
override fun onAvailable(network: Network) {
onlineNetworks.add(network.networkHandle)
onConnectivityChange(onlineNetworks.isNotEmpty())
}

override fun onLost(network: Network) {
onlineNetworks.remove(network.networkHandle)
onConnectivityChange(onlineNetworks.isNotEmpty())
}
}

private fun onConnectivityChange(isOnline: Boolean) {
Expand All @@ -43,7 +54,7 @@ internal class AndroidPlatformConnectivityManager(private val connectivityManage
}
}

override fun setListener(listener: PlatformConnectivityManager.Listener) {
override fun setListener(listener: NetworkObserver.Listener) {
check(this.listener == null) {
"There can be only one listener"
}
Expand All @@ -61,26 +72,23 @@ internal class AndroidPlatformConnectivityManager(private val connectivityManage
}
}

internal fun platformConnectivityManager(context: Context): AndroidPlatformConnectivityManager? {
return if (VERSION.SDK_INT >= M) {
val connectivityManager = context.getSystemService(ConnectivityManager::class.java)
val hasPermission = context.isPermissionGranted(Manifest.permission.ACCESS_NETWORK_STATE)
if (connectivityManager == null || !hasPermission) {
println("Cannot get ConnectivityManager")
return null
}
private val TAG = "Apollo"

AndroidPlatformConnectivityManager(connectivityManager)
} else {
null
internal fun networkObserver(context: Context): NetworkObserver {
if (VERSION.SDK_INT < M) {
Log.w(TAG, "network monitoring requires minSdk of 23 or more")
return NoOpNetworkObserver
}
}

internal actual fun platformConnectivityManager(): PlatformConnectivityManager? {
val context = ApolloInitializer.context
if (context == null) {
return null
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE)
if (connectivityManager !is ConnectivityManager) {
Log.w(TAG, "Cannot get ConnectivityManager")
return NoOpNetworkObserver
}
val hasPermission = context.isPermissionGranted(Manifest.permission.ACCESS_NETWORK_STATE)
if (!hasPermission) {
Log.w(TAG, "No ACCESS_NETWORK_STATE")
return NoOpNetworkObserver
}

return platformConnectivityManager(context)
}
return AndroidNetworkObserver(connectivityManager)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.apollographql.apollo3.network

import com.apollographql.apollo3.annotations.ApolloExperimental

@ApolloExperimental
fun NetworkMonitor(): NetworkMonitor = DefaultNetworkMonitor { AppleNetworkObserver() }
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,19 @@ import platform.Network.nw_path_status_satisfied
import platform.Network.nw_path_t
import platform.darwin.dispatch_queue_create

internal actual fun platformConnectivityManager(): PlatformConnectivityManager? {
return ApplePlatformMonitor()
}

private class ApplePlatformMonitor: PlatformConnectivityManager, nw_path_monitor_update_handler_t {
internal class AppleNetworkObserver: NetworkObserver, nw_path_monitor_update_handler_t {
var monitor: nw_path_monitor_t = null
var listener: PlatformConnectivityManager.Listener? = null
var listener: NetworkObserver.Listener? = null

override fun close() {
if (monitor != null) {
nw_path_monitor_cancel(monitor)
}
}

override fun setListener(listener: PlatformConnectivityManager.Listener) {
override fun setListener(listener: NetworkObserver.Listener) {
check(monitor == null) {
"Apollo: there can be only one monitor"
"Apollo: there can be only one listener"
}
monitor = nw_path_monitor_create()
this.listener = listener
Expand Down
Loading

0 comments on commit bdaae07

Please sign in to comment.