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

[runtime] remove androidx.startup dependency #5761

Merged
merged 7 commits into from
Mar 25, 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
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
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The test APK grew over 64k methods 🤷 .

Screenshot 2024-03-25 at 13 30 53 Screenshot 2024-03-25 at 13 31 06

Interestingly, it's not the case on those 2 screenshots (I think? but somehow two dex files are still required 🤷 )
In all cases, it's close enough that it may happen again. Plus AGP enables it by default if minSdk > 21 anyways.

No place for degrowth in dex-land 🌱 🙈

}
}

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
Loading