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

Add an Android Client Builder following Coil, Koin patterns #7846

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
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
10 changes: 5 additions & 5 deletions android-test-app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,25 @@ plugins {
}

android {
compileSdk = 34
compileSdk = 35

namespace = "okhttp.android.testapp"

testBuildType = "release"

defaultConfig {
minSdk = 21
targetSdk = 34
targetSdk = 35
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

compileOptions {
targetCompatibility(JavaVersion.VERSION_11)
sourceCompatibility(JavaVersion.VERSION_11)
targetCompatibility(JavaVersion.VERSION_17)
sourceCompatibility(JavaVersion.VERSION_17)
}

kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
jvmTarget = JavaVersion.VERSION_17.toString()
}

buildTypes {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2023 Block, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp.android.testapp

import androidx.test.core.app.ApplicationProvider
import assertk.assertThat
import assertk.assertions.isEqualTo
import okhttp3.android.OkHttpClientContext.okHttpClient
import org.junit.Test

/**
* Run with "./gradlew :android-test-app:connectedCheck -PandroidBuild=true" and make sure ANDROID_SDK_ROOT is set.
*/
class OkHttpClientFactoryTest {
@Test
fun testUsesCorrectFactory() {
val application = ApplicationProvider.getApplicationContext<TestApplication>()

val client = application.okHttpClient
assertThat(client.cache?.maxSize()).isEqualTo(5_000_000)
assertThat(client.cache?.directory?.name).isEqualTo("test-app-cache")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp3.android
package okhttp.android.testapp

import assertk.assertThat
import assertk.assertions.isEqualTo
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,29 @@ import androidx.activity.ComponentActivity
import okhttp3.Call
import okhttp3.Callback
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.android.OkHttpClientContext
import okhttp3.android.OkHttpClientContext.okHttpClient
import okhttp3.internal.platform.AndroidPlatform
import okio.IOException

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val client = OkHttpClient()

// Ensure we are compiling against the right variant
println(AndroidPlatform.isSupported)

val url = "https://github.com/square/okhttp".toHttpUrl()
println(url.topPrivateDomain())

client.newCall(Request(url)).enqueue(
// avoid these being stripped by r8, when we want them for tests
println(OkHttpClientContext)
println(okHttpClient.cache?.maxSize())
println(okHttpClient.cache?.directory)

okHttpClient.newCall(Request(url)).enqueue(
object : Callback {
override fun onFailure(
call: Call,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,17 @@
package okhttp.android.testapp

import android.app.Application
import okhttp3.OkHttpClient
import okhttp3.android.AndroidOkHttpClientBuilder.androidBuilder
import okhttp3.android.OkHttpClientFactory

class TestApplication : Application()
class TestApplication : Application(), OkHttpClientFactory {
override fun newOkHttpClient(): OkHttpClient {
return OkHttpClient.androidBuilder(
context = this,
cacheDir = { cacheDir.resolve("test-app-cache") },
cacheSize = 5_000_000,
)
.build()
}
}
15 changes: 15 additions & 0 deletions okhttp/api/android/okhttp.api
Original file line number Diff line number Diff line change
Expand Up @@ -1304,3 +1304,18 @@ public final class okhttp3/android/AndroidAsyncDns$Companion {
public final fun getIPv6 ()Lokhttp3/android/AndroidAsyncDns;
}

public final class okhttp3/android/AndroidOkHttpClientBuilder {
public static final field INSTANCE Lokhttp3/android/AndroidOkHttpClientBuilder;
public final fun androidBuilder (Lokhttp3/OkHttpClient$Companion;Landroid/content/Context;Lkotlin/jvm/functions/Function0;J)Lokhttp3/OkHttpClient$Builder;
public static synthetic fun androidBuilder$default (Lokhttp3/android/AndroidOkHttpClientBuilder;Lokhttp3/OkHttpClient$Companion;Landroid/content/Context;Lkotlin/jvm/functions/Function0;JILjava/lang/Object;)Lokhttp3/OkHttpClient$Builder;
}

public final class okhttp3/android/OkHttpClientContext {
public static final field INSTANCE Lokhttp3/android/OkHttpClientContext;
public final fun getOkHttpClient (Landroid/content/Context;)Lokhttp3/OkHttpClient;
}

public abstract interface class okhttp3/android/OkHttpClientFactory {
public abstract fun newOkHttpClient ()Lokhttp3/OkHttpClient;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright (c) 2024 Block, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package okhttp3.android

import android.content.Context
import android.os.StrictMode
import java.io.File
import okhttp3.Cache
import okhttp3.OkHttpClient

/**
* [OkHttpClient.Builder] with opinionated defaults on Android.
*/
object AndroidOkHttpClientBuilder {
/**
* Create a [OkHttpClient.Builder] with opinionated defaults on Android.
*
* @param context The context for accessing resources such as file locations or android services.
* @param cacheDir lambda for providing a cache dir. Defaults to "okhttp" in [Context.getCacheDir].
* @param cacheSize the cache size. Defaults to 10MB.
*/
fun OkHttpClient.Companion.androidBuilder(
context: Context,
cacheDir: (() -> File)? = { context.cacheDir.resolve("okhttp") },
cacheSize: Long = 10_000_000L,
): OkHttpClient.Builder {
return OkHttpClient.Builder().apply {
if (cacheDir != null) {
StrictMode.allowThreadDiskWrites().resetAfter {
cache(Cache(cacheDir(), cacheSize))
}
}
}
}

private fun <R> StrictMode.ThreadPolicy.resetAfter(block: () -> R) =
try {
block()
} finally {
StrictMode.setThreadPolicy(this)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright (c) 2024 Block, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package okhttp3.android

import android.content.Context
import okhttp3.OkHttpClient
import okhttp3.android.AndroidOkHttpClientBuilder.androidBuilder
import okhttp3.internal.platform.ContextAwarePlatform
import okhttp3.internal.platform.Platform

/**
* App Singleton instance of OkHttp. Should be used as default client for general traffic. Or as a root client
* adapted via [OkHttpClient.newBuilder]. May not be appropriate for critical security requests because of shared
* interceptors/listeners.
*
* Apps can customise the instance by implementing [OkHttpClientFactory] in the [android.app.Application].
*/
object OkHttpClientContext {
private val _okHttpClient: OkHttpClient by lazy {
val context =
checkNotNull((Platform.get() as ContextAwarePlatform).applicationContext) { "OkHttp initializer not run" }
newOkHttpClient(context)
}

/**
* Accessor for the app singleton instance of OkHttp. The context here is not actually used, rather the app instance
* configured via [okhttp3.internal.platform.PlatformInitializer] is used instead.
*/
val Context.okHttpClient: OkHttpClient
get() = _okHttpClient

private fun newOkHttpClient(context: Context): OkHttpClient {
val okHttpClientFactory = context.applicationContext as? OkHttpClientFactory
return okHttpClientFactory?.newOkHttpClient() ?: OkHttpClient.androidBuilder(
context = context,
cacheDir = null,
).build()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2024 Block, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package okhttp3.android

import okhttp3.OkHttpClient

/**
* Factory of OkHttpClient instances. When implemented in the android Application,
* it will be initialised early and available as an app singleton.
*/
interface OkHttpClientFactory {
fun newOkHttpClient(): OkHttpClient
}
Loading