Skip to content

Commit 0f19a20

Browse files
authored
feat(demo-app): OkHttp build-time instrumentation and demo screen (#1688)
* feat(demo-app): OkHttp build-time instrumentation and demo screen Wire ByteBuddy and okhttp3 library/agent in the demo app (composite dependency substitutions). Add OkHttpDemoActivity with sample HTTP calls for manual trace verification. - Gradle: ByteBuddy plugin, okhttp3-library + okhttp3-agent, okhttp dependency - OkHttpDemoScreen: GET 200/404, connection failure, POST JSON, slow GET (httpbin) - MainActivity: enableEdgeToEdge, safeDrawingPadding, entry button to OkHttp screen; onBackground for title in dark theme - Theme: transparent status/navigation bars; light/dark system bar icons from background luminance Fixes #419 Made-with: Cursor * refactor(demo-app): replace OutlinedButton with OkHttpDemoActionButton in OkHttpDemoScreen Updated the OkHttpDemoScreen to use a new composable, OkHttpDemoActionButton, for better code reuse and readability. This change simplifies the button implementation for various HTTP actions while maintaining the same functionality.
1 parent c3312a3 commit 0f19a20

8 files changed

Lines changed: 277 additions & 6 deletions

File tree

demo-app/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
33
plugins {
44
alias(rootLibs.plugins.androidApp)
55
alias(libs.plugins.compose.compiler)
6+
alias(libs.plugins.byteBuddy)
67
}
78

89
android {
@@ -69,6 +70,9 @@ dependencies {
6970
implementation("io.opentelemetry.android:android-agent") //parent dir
7071
implementation("io.opentelemetry.android.instrumentation:compose-click")
7172
implementation("io.opentelemetry.android.instrumentation:sessions")
73+
implementation("io.opentelemetry.android.instrumentation:okhttp3-library")
74+
byteBuddy("io.opentelemetry.android.instrumentation:okhttp3-agent")
75+
implementation(libs.okhttp)
7276
implementation(libs.androidx.core.ktx)
7377
implementation(libs.androidx.lifecycle.runtime.ktx)
7478
implementation(libs.androidx.activity.compose)

demo-app/gradle/libs.versions.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
[versions]
2+
byteBuddy = "1.18.8"
3+
okhttp = "5.3.2"
24
opentelemetry = "1.61.0"
35
opentelemetry-alpha = "1.61.0-alpha"
46
junit = "6.0.3"
@@ -11,6 +13,7 @@ androidx-appcompat = "androidx.appcompat:appcompat:1.7.1"
1113
opentelemetry-exporter-otlp = { module = "io.opentelemetry:opentelemetry-exporter-otlp", version.ref = "opentelemetry" }
1214
opentelemetry-api-incubator = { module = "io.opentelemetry:opentelemetry-api-incubator", version.ref = "opentelemetry-alpha" }
1315
gson = "com.google.code.gson:gson:2.13.2"
16+
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
1417

1518
#Test tools
1619
androidx-junit = "androidx.test.ext:junit:1.3.0"
@@ -47,3 +50,4 @@ junit = ["junit-jupiter-api", "junit-jupiter-engine", "junit-vintage-engine"]
4750
[plugins]
4851
spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
4952
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
53+
byteBuddy = { id = "net.bytebuddy.byte-buddy-gradle-plugin", version.ref = "byteBuddy" }

demo-app/settings.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,9 @@ includeBuild("..") {
2828
.using(project(":instrumentation:compose:click"))
2929
substitute(module("io.opentelemetry.android.instrumentation:sessions"))
3030
.using(project(":instrumentation:sessions"))
31+
substitute(module("io.opentelemetry.android.instrumentation:okhttp3-library"))
32+
.using(project(":instrumentation:okhttp3:library"))
33+
substitute(module("io.opentelemetry.android.instrumentation:okhttp3-agent"))
34+
.using(project(":instrumentation:okhttp3:agent"))
3135
}
3236
}

demo-app/src/main/AndroidManifest.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@
2929
android:exported="true"
3030
android:label="About OpenTelemetry Android"
3131
/>
32+
<activity
33+
android:name=".OkHttpDemoActivity"
34+
android:exported="false"
35+
android:label="OkHttp instrumentation"
36+
android:theme="@style/Theme.DemoApp"
37+
/>
3238
<activity
3339
android:name=".MainActivity"
3440
android:exported="true"

demo-app/src/main/java/io/opentelemetry/android/demo/MainActivity.kt

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,16 @@ import android.os.Bundle
1212
import android.util.Log
1313
import androidx.activity.ComponentActivity
1414
import androidx.activity.compose.setContent
15+
import androidx.activity.enableEdgeToEdge
1516
import androidx.activity.viewModels
1617
import androidx.compose.foundation.layout.Arrangement
1718
import androidx.compose.foundation.layout.Column
1819
import androidx.compose.foundation.layout.Row
1920
import androidx.compose.foundation.layout.fillMaxSize
2021
import androidx.compose.foundation.layout.padding
22+
import androidx.compose.foundation.layout.safeDrawingPadding
23+
import androidx.compose.foundation.rememberScrollState
24+
import androidx.compose.foundation.verticalScroll
2125
import androidx.compose.material3.MaterialTheme
2226
import androidx.compose.material3.Surface
2327
import androidx.compose.ui.Alignment
@@ -41,21 +45,28 @@ class MainActivity : ComponentActivity() {
4145

4246
override fun onCreate(savedInstanceState: Bundle?) {
4347
super.onCreate(savedInstanceState)
48+
enableEdgeToEdge()
4449
setContent {
4550
DemoAppTheme {
4651
// A surface container using the 'background' color from the theme
4752
Surface(
48-
modifier = Modifier.fillMaxSize(),
53+
modifier = Modifier.fillMaxSize().safeDrawingPadding(),
4954
color = MaterialTheme.colorScheme.background,
5055
) {
5156
Column(
57+
modifier =
58+
Modifier
59+
.fillMaxSize()
60+
.verticalScroll(rememberScrollState())
61+
.padding(horizontal = 20.dp, vertical = 16.dp),
5262
horizontalAlignment = Alignment.CenterHorizontally,
53-
verticalArrangement = Arrangement.SpaceEvenly,
63+
verticalArrangement = Arrangement.spacedBy(16.dp),
5464
) {
5565
Row(
56-
Modifier.padding(all = 20.dp),
66+
Modifier.padding(vertical = 4.dp),
5767
horizontalArrangement = Arrangement.Center,
5868
) {
69+
val onBackground = MaterialTheme.colorScheme.onBackground
5970
CenterText(
6071
fontSize = 40.sp,
6172
text =
@@ -66,7 +77,7 @@ class MainActivity : ComponentActivity() {
6677
withStyle(style = SpanStyle(color = Color(0xFF425CC7))) {
6778
append("Telemetry")
6879
}
69-
withStyle(style = SpanStyle(color = Color.Black)) {
80+
withStyle(style = SpanStyle(color = onBackground)) {
7081
append(" Android Demo")
7182
}
7283
toAnnotatedString()
@@ -78,6 +89,9 @@ class MainActivity : ComponentActivity() {
7889
painterResource(id = R.drawable.otel_icon),
7990
)
8091
val context = LocalContext.current
92+
LauncherButton(text = "OkHttp instrumentation", onClick = {
93+
context.startActivity(Intent(this@MainActivity, OkHttpDemoActivity::class.java))
94+
})
8195
LauncherButton(text = "Go shopping", onClick = {
8296
context.startActivity(Intent(this@MainActivity, AstronomyShopActivity::class.java))
8397
})
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.android.demo
7+
8+
import android.os.Bundle
9+
import androidx.activity.ComponentActivity
10+
import androidx.activity.compose.setContent
11+
import androidx.activity.enableEdgeToEdge
12+
import io.opentelemetry.android.demo.theme.DemoAppTheme
13+
14+
class OkHttpDemoActivity : ComponentActivity() {
15+
16+
override fun onCreate(savedInstanceState: Bundle?) {
17+
super.onCreate(savedInstanceState)
18+
enableEdgeToEdge()
19+
setContent {
20+
DemoAppTheme {
21+
OkHttpDemoScreen(onBack = { finish() })
22+
}
23+
}
24+
}
25+
}
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.android.demo
7+
8+
import android.widget.Toast
9+
import androidx.compose.foundation.BorderStroke
10+
import androidx.compose.foundation.layout.Arrangement
11+
import androidx.compose.foundation.layout.Column
12+
import androidx.compose.foundation.layout.Spacer
13+
import androidx.compose.foundation.layout.fillMaxSize
14+
import androidx.compose.foundation.layout.fillMaxWidth
15+
import androidx.compose.foundation.layout.height
16+
import androidx.compose.foundation.layout.padding
17+
import androidx.compose.foundation.rememberScrollState
18+
import androidx.compose.foundation.verticalScroll
19+
import androidx.compose.material.icons.Icons
20+
import androidx.compose.material.icons.filled.ArrowBack
21+
import androidx.compose.material3.ButtonDefaults
22+
import androidx.compose.material3.ExperimentalMaterial3Api
23+
import androidx.compose.material3.Icon
24+
import androidx.compose.material3.IconButton
25+
import androidx.compose.material3.MaterialTheme
26+
import androidx.compose.material3.OutlinedButton
27+
import androidx.compose.material3.Scaffold
28+
import androidx.compose.material3.Text
29+
import androidx.compose.material3.TopAppBar
30+
import androidx.compose.runtime.Composable
31+
import androidx.compose.runtime.remember
32+
import androidx.compose.runtime.rememberCoroutineScope
33+
import androidx.compose.ui.Modifier
34+
import androidx.compose.ui.graphics.Color
35+
import androidx.compose.ui.platform.LocalContext
36+
import androidx.compose.ui.unit.dp
37+
import kotlinx.coroutines.Dispatchers
38+
import kotlinx.coroutines.launch
39+
import kotlinx.coroutines.withContext
40+
import okhttp3.MediaType.Companion.toMediaType
41+
import okhttp3.OkHttpClient
42+
import okhttp3.Request
43+
import okhttp3.RequestBody.Companion.toRequestBody
44+
45+
private val DemoAccentBlue = Color(0xFF425CC7)
46+
47+
/**
48+
* Full-screen OkHttp instrumentation demo (issue #419). Sample HTTP calls produce traced client spans.
49+
*/
50+
@OptIn(ExperimentalMaterial3Api::class)
51+
@Composable
52+
fun OkHttpDemoScreen(onBack: () -> Unit) {
53+
val context = LocalContext.current
54+
val scope = rememberCoroutineScope()
55+
val client = remember { OkHttpClient() }
56+
57+
Scaffold(
58+
modifier = Modifier.fillMaxSize(),
59+
topBar = {
60+
TopAppBar(
61+
title = { Text("Instrumentation testing") },
62+
navigationIcon = {
63+
IconButton(onClick = onBack) {
64+
Icon(
65+
imageVector = Icons.Filled.ArrowBack,
66+
contentDescription = "Back",
67+
)
68+
}
69+
},
70+
)
71+
},
72+
) { innerPadding ->
73+
Column(
74+
modifier =
75+
Modifier
76+
.fillMaxSize()
77+
.padding(innerPadding)
78+
.verticalScroll(rememberScrollState())
79+
.padding(horizontal = 20.dp, vertical = 16.dp),
80+
verticalArrangement = Arrangement.spacedBy(16.dp),
81+
) {
82+
Text(
83+
text = "OkHttp sample calls (GET, POST, delayed GET); each should produce a traced client span in your collector.",
84+
style = MaterialTheme.typography.bodyMedium,
85+
color = MaterialTheme.colorScheme.onSurfaceVariant,
86+
)
87+
Spacer(modifier = Modifier.height(8.dp))
88+
OkHttpDemoActionButton(
89+
onClick = {
90+
scope.launch {
91+
val msg =
92+
runCatching {
93+
withContext(Dispatchers.IO) {
94+
val request =
95+
Request.Builder().url("https://httpbin.org/status/200").get().build()
96+
val code = client.newCall(request).execute().use { it.code }
97+
"OkHttp: HTTP $code"
98+
}
99+
}.getOrElse { e -> "OkHttp 200 demo: ${e.message}" }
100+
withContext(Dispatchers.Main) {
101+
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
102+
}
103+
}
104+
},
105+
text = "GET known 200",
106+
)
107+
OkHttpDemoActionButton(
108+
onClick = {
109+
scope.launch {
110+
val msg =
111+
runCatching {
112+
withContext(Dispatchers.IO) {
113+
val request =
114+
Request.Builder().url("https://httpbin.org/status/404").get().build()
115+
val code = client.newCall(request).execute().use { it.code }
116+
"OkHttp: HTTP $code"
117+
}
118+
}.getOrElse { e -> "OkHttp 404 demo: ${e.message}" }
119+
withContext(Dispatchers.Main) {
120+
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
121+
}
122+
}
123+
},
124+
text = "GET known 404",
125+
)
126+
OkHttpDemoActionButton(
127+
onClick = {
128+
scope.launch {
129+
val msg =
130+
runCatching {
131+
withContext(Dispatchers.IO) {
132+
val request =
133+
Request.Builder().url("http://127.0.0.1:1/").get().build()
134+
client.newCall(request).execute().use { it.code }
135+
"OkHttp: unexpected success"
136+
}
137+
}.getOrElse { e -> "OkHttp failure demo: ${e.javaClass.simpleName}" }
138+
withContext(Dispatchers.Main) {
139+
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
140+
}
141+
}
142+
},
143+
text = "GET connection failure",
144+
)
145+
OkHttpDemoActionButton(
146+
onClick = {
147+
scope.launch {
148+
val msg =
149+
runCatching {
150+
withContext(Dispatchers.IO) {
151+
val json = """{"event":"otel-android-demo","source":"okhttp"}"""
152+
val body =
153+
json.toRequestBody("application/json; charset=utf-8".toMediaType())
154+
val request =
155+
Request.Builder().url("https://httpbin.org/post").post(body).build()
156+
val code = client.newCall(request).execute().use { it.code }
157+
"OkHttp POST: HTTP $code"
158+
}
159+
}.getOrElse { e -> "OkHttp POST demo: ${e.message}" }
160+
withContext(Dispatchers.Main) {
161+
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
162+
}
163+
}
164+
},
165+
text = "POST JSON payload",
166+
)
167+
OkHttpDemoActionButton(
168+
onClick = {
169+
scope.launch {
170+
val msg =
171+
runCatching {
172+
withContext(Dispatchers.IO) {
173+
val request =
174+
Request.Builder().url("https://httpbin.org/delay/3").get().build()
175+
val code = client.newCall(request).execute().use { it.code }
176+
"OkHttp slow GET: HTTP $code (~3s)"
177+
}
178+
}.getOrElse { e -> "OkHttp slow demo: ${e.message}" }
179+
withContext(Dispatchers.Main) {
180+
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
181+
}
182+
}
183+
},
184+
text = "GET slow response (3s)",
185+
)
186+
}
187+
}
188+
}
189+
190+
@Composable
191+
private fun OkHttpDemoActionButton(
192+
text: String,
193+
onClick: () -> Unit,
194+
) {
195+
OutlinedButton(
196+
onClick = onClick,
197+
modifier = Modifier.fillMaxWidth().height(56.dp),
198+
border = BorderStroke(1.dp, DemoAccentBlue),
199+
colors =
200+
ButtonDefaults.outlinedButtonColors(
201+
containerColor = Color.Transparent,
202+
contentColor = DemoAccentBlue,
203+
),
204+
) {
205+
Text(text)
206+
}
207+
}

demo-app/src/main/java/io/opentelemetry/android/demo/theme/Theme.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ import androidx.compose.material3.dynamicLightColorScheme
1515
import androidx.compose.material3.lightColorScheme
1616
import androidx.compose.runtime.Composable
1717
import androidx.compose.runtime.SideEffect
18+
import androidx.compose.ui.graphics.Color
1819
import androidx.compose.ui.graphics.toArgb
1920
import androidx.compose.ui.platform.LocalContext
2021
import androidx.compose.ui.platform.LocalView
22+
import androidx.core.graphics.ColorUtils
2123
import androidx.core.view.WindowCompat
2224

2325
private val DarkColorScheme =
@@ -64,8 +66,13 @@ fun DemoAppTheme(
6466
if (!view.isInEditMode) {
6567
SideEffect {
6668
val window = (view.context as Activity).window
67-
window.statusBarColor = colorScheme.primary.toArgb()
68-
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
69+
val insetsController = WindowCompat.getInsetsController(window, view)
70+
val backgroundArgb = colorScheme.background.toArgb()
71+
window.statusBarColor = Color.Transparent.toArgb()
72+
window.navigationBarColor = Color.Transparent.toArgb()
73+
val useDarkSystemIcons = ColorUtils.calculateLuminance(backgroundArgb) > 0.5
74+
insetsController.isAppearanceLightStatusBars = useDarkSystemIcons
75+
insetsController.isAppearanceLightNavigationBars = useDarkSystemIcons
6976
}
7077
}
7178

0 commit comments

Comments
 (0)