Skip to content

Commit 6d6ed1f

Browse files
oschwaldclaude
andcommitted
feat(sample): Add collapsible sections for device data display
Display device data in expandable/collapsible sections instead of raw JSON. Uses reflection to dynamically iterate over DeviceData properties, so new fields are automatically included without code changes. Changes: - Add collapsible section UI with tap to expand/collapse - Show summary at top, detailed JSON in collapsible sections below - Add kotlin-reflect dependency for dynamic property iteration - All sections collapsed by default 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 90e8486 commit 6d6ed1f

File tree

4 files changed

+91
-7
lines changed

4 files changed

+91
-7
lines changed

gradle/libs.versions.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ material = "1.12.0"
4242
[libraries]
4343
# Kotlin
4444
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
45+
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
4546
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
4647
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
4748
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" }

sample/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
plugins {
22
alias(libs.plugins.android.application)
33
alias(libs.plugins.kotlin.android)
4+
alias(libs.plugins.kotlin.serialization)
45
alias(libs.plugins.detekt)
56
alias(libs.plugins.ktlint)
67
}
@@ -52,8 +53,10 @@ dependencies {
5253

5354
// Kotlin
5455
implementation(libs.kotlin.stdlib)
56+
implementation(libs.kotlin.reflect)
5557
implementation(libs.kotlinx.coroutines.core)
5658
implementation(libs.kotlinx.coroutines.android)
59+
implementation(libs.kotlinx.serialization.json)
5760

5861
// AndroidX
5962
implementation(libs.androidx.core.ktx)

sample/src/main/java/com/maxmind/device/sample/MainActivity.kt

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
11
package com.maxmind.device.sample
22

3+
import android.graphics.Typeface
34
import android.os.Bundle
45
import android.util.Log
6+
import android.util.TypedValue
7+
import android.view.View
8+
import android.widget.LinearLayout
9+
import android.widget.TextView
510
import androidx.appcompat.app.AppCompatActivity
611
import androidx.lifecycle.lifecycleScope
712
import com.google.android.material.snackbar.Snackbar
813
import com.maxmind.device.DeviceTracker
914
import com.maxmind.device.config.SdkConfig
1015
import com.maxmind.device.sample.databinding.ActivityMainBinding
1116
import kotlinx.coroutines.launch
17+
import kotlinx.serialization.encodeToString
18+
import kotlinx.serialization.json.Json
19+
import kotlinx.serialization.serializer
20+
import kotlin.reflect.full.memberProperties
1221

1322
/**
1423
* Main activity demonstrating the MaxMind Device Tracker usage.
@@ -17,6 +26,7 @@ class MainActivity : AppCompatActivity() {
1726

1827
private lateinit var binding: ActivityMainBinding
1928
private var logText = StringBuilder()
29+
private val json = Json { prettyPrint = true }
2030

2131
override fun onCreate(savedInstanceState: Bundle?) {
2232
super.onCreate(savedInstanceState)
@@ -92,6 +102,25 @@ class MainActivity : AppCompatActivity() {
92102
appendLog(" SDK Version: ${deviceData.build.sdkVersion}")
93103
appendLog(" Screen: ${deviceData.display.widthPixels}x${deviceData.display.heightPixels} (${deviceData.display.densityDpi}dpi)")
94104
appendLog(" Timestamp: ${deviceData.deviceTime}")
105+
appendLog("")
106+
107+
// Dynamically add collapsible sections for each property
108+
deviceData::class.memberProperties.forEach { prop ->
109+
val value = prop.getter.call(deviceData)
110+
if (value != null) {
111+
val content = try {
112+
json.encodeToString(serializer(prop.returnType), value)
113+
} catch (e: Exception) {
114+
value.toString()
115+
}
116+
addCollapsibleSection(prop.name, content)
117+
}
118+
}
119+
120+
// Scroll to top to show summary first
121+
binding.scrollView.post {
122+
binding.scrollView.fullScroll(android.view.View.FOCUS_UP)
123+
}
95124

96125
showMessage("Device data collected")
97126
} catch (e: Exception) {
@@ -102,6 +131,44 @@ class MainActivity : AppCompatActivity() {
102131
}
103132
}
104133

134+
private fun addCollapsibleSection(title: String, content: String) {
135+
val header = TextView(this).apply {
136+
text = "$title"
137+
setTypeface(typeface, Typeface.BOLD)
138+
setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f)
139+
setPadding(0, dpToPx(8), 0, dpToPx(4))
140+
setTextColor(getColor(R.color.purple_700))
141+
}
142+
143+
val contentView = TextView(this).apply {
144+
text = content
145+
setTextSize(TypedValue.COMPLEX_UNIT_SP, 11f)
146+
typeface = Typeface.MONOSPACE
147+
setPadding(dpToPx(16), 0, 0, dpToPx(8))
148+
visibility = View.GONE
149+
}
150+
151+
header.setOnClickListener {
152+
if (contentView.visibility == View.GONE) {
153+
contentView.visibility = View.VISIBLE
154+
header.text = "$title"
155+
} else {
156+
contentView.visibility = View.GONE
157+
header.text = "$title"
158+
}
159+
}
160+
161+
binding.logContainer.addView(header)
162+
binding.logContainer.addView(contentView)
163+
}
164+
165+
private fun dpToPx(dp: Int): Int =
166+
TypedValue.applyDimension(
167+
TypedValue.COMPLEX_UNIT_DIP,
168+
dp.toFloat(),
169+
resources.displayMetrics
170+
).toInt()
171+
105172
private fun sendDeviceData() {
106173
try {
107174
val sdk = DeviceTracker.getInstance()
@@ -143,6 +210,11 @@ class MainActivity : AppCompatActivity() {
143210
private fun clearLog() {
144211
logText.clear()
145212
binding.tvLog.text = ""
213+
// Remove all views except the tvLog TextView
214+
val childCount = binding.logContainer.childCount
215+
if (childCount > 1) {
216+
binding.logContainer.removeViews(1, childCount - 1)
217+
}
146218
appendLog("Log cleared.")
147219
}
148220

sample/src/main/res/layout/activity_main.xml

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -111,15 +111,23 @@
111111
android:padding="12dp"
112112
android:fillViewport="true">
113113

114-
<TextView
115-
android:id="@+id/tvLog"
114+
<LinearLayout
115+
android:id="@+id/logContainer"
116116
android:layout_width="match_parent"
117117
android:layout_height="wrap_content"
118-
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
119-
android:fontFamily="monospace"
120-
android:textSize="12sp"
121-
android:textIsSelectable="true"
122-
tools:text="Log output will appear here..." />
118+
android:orientation="vertical">
119+
120+
<TextView
121+
android:id="@+id/tvLog"
122+
android:layout_width="match_parent"
123+
android:layout_height="wrap_content"
124+
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
125+
android:fontFamily="monospace"
126+
android:textSize="12sp"
127+
android:textIsSelectable="true"
128+
tools:text="Log output will appear here..." />
129+
130+
</LinearLayout>
123131

124132
</ScrollView>
125133

0 commit comments

Comments
 (0)