Skip to content

Commit 6508209

Browse files
RUM-9511: Rick and Morty Api requests implementation
1 parent faae92a commit 6508209

File tree

11 files changed

+637
-0
lines changed

11 files changed

+637
-0
lines changed

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ buildscript {
3434
classpath(libs.unmockGradlePlugin)
3535
classpath(libs.sqlDelightGradlePlugin)
3636
classpath(libs.binaryCompatibilityGradlePlugin)
37+
classpath(libs.kotlinxSerializationPlugin)
3738
}
3839
}
3940

gradle/libs.versions.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ kotlinSP = "1.9.24-1.0.20"
55
gson = "2.10.1"
66
okHttp = "4.12.0"
77
kronosNTP = "0.0.1-alpha11"
8+
kotlinxSerialization = "1.6.3"
89

910
# Android
1011
androidDesugaringSdk = "2.0.4"
@@ -90,6 +91,7 @@ timber = "5.0.1"
9091
coroutines = "1.4.2"
9192

9293
# Local Server
94+
ktorClient = "2.3.13"
9395
ktor = "1.6.8"
9496
ktorServer = "3.0.0-rc-1"
9597

@@ -115,6 +117,7 @@ sqlDelightGradlePlugin = { module = "com.squareup.sqldelight:gradle-plugin", ver
115117
binaryCompatibilityGradlePlugin = { module = "org.jetbrains.kotlinx:binary-compatibility-validator", version.ref = "binaryCompatibility" }
116118
dependencyLicenseGradlePlugin = { module = "com.datadoghq:dependency-license", version.ref = "dependencyLicense" }
117119
versionsGradlePlugin = { module = "com.github.ben-manes:gradle-versions-plugin", version.ref = "versionsGradlePlugin" }
120+
kotlinxSerializationPlugin = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin"}
118121

119122
# Annotation processors
120123
glideCompiler = { module = "com.github.bumptech.glide:compiler", version.ref = "glide" }
@@ -123,6 +126,7 @@ roomCompiler = { module = "androidx.room:room-compiler", version.ref = "room" }
123126
# Common
124127

125128
kotlin = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
129+
kotlinxSerializationJson = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
126130

127131
okHttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okHttp" }
128132
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
@@ -329,6 +333,12 @@ ktorServer = [
329333
"ktorServerNetty",
330334
"ktorServerSSE"
331335
]
336+
ktorClient = [
337+
"ktorClientCore",
338+
"ktorClientOkHttp",
339+
"ktorContentNegotiation",
340+
"ktorSerializationKotlinxJson"
341+
]
332342

333343
traceCore = [
334344
"jctools",

sample/benchmark/build.gradle.kts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ plugins {
1010
id("com.android.application")
1111
kotlin("android")
1212
kotlin("kapt")
13+
kotlin("plugin.serialization")
14+
id("kotlin-parcelize")
1315
alias(libs.plugins.datadogGradlePlugin)
1416
}
1517

@@ -98,6 +100,8 @@ dependencies {
98100
implementation(libs.daggerLib)
99101
kapt(libs.daggerCompiler)
100102
implementation(libs.coroutinesCore)
103+
implementation(libs.bundles.ktorClient)
104+
implementation(libs.kotlinxSerializationJson)
101105
implementation(project(":features:dd-sdk-android-logs"))
102106
implementation(project(":features:dd-sdk-android-rum"))
103107
implementation(project(":features:dd-sdk-android-trace"))
@@ -108,11 +112,14 @@ dependencies {
108112
implementation(project(":features:dd-sdk-android-session-replay-material"))
109113
implementation(project(":features:dd-sdk-android-session-replay-compose"))
110114
implementation(project(":integrations:dd-sdk-android-compose"))
115+
implementation(project(":integrations:dd-sdk-android-glide"))
116+
implementation(project(":integrations:dd-sdk-android-okhttp"))
111117
implementation(project(":tools:benchmark"))
112118

113119
testImplementation(libs.bundles.jUnit5)
114120
testImplementation(libs.bundles.testTools)
115121
testImplementation(libs.systemStubsJupiter)
122+
testImplementation(libs.ktorClientMock)
116123
}
117124

118125
kotlinConfig()
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2016-Present Datadog, Inc.
5+
*/
6+
7+
package com.datadog.benchmark.sample.di.app
8+
9+
import android.content.Context
10+
import com.datadog.android.api.SdkCore
11+
import com.datadog.android.okhttp.DatadogInterceptor
12+
import com.datadog.benchmark.sample.config.BenchmarkConfig
13+
import com.datadog.benchmark.sample.config.SyntheticsRun
14+
import com.datadog.benchmark.sample.config.SyntheticsScenario
15+
import com.datadog.benchmark.sample.network.rickandmorty.RickAndMortyNetworkService
16+
import com.datadog.benchmark.sample.network.rickandmorty.RickAndMortyNetworkServiceImpl
17+
import dagger.Binds
18+
import dagger.Module
19+
import dagger.Provides
20+
import io.ktor.client.HttpClient
21+
import io.ktor.client.engine.okhttp.OkHttp
22+
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
23+
import io.ktor.serialization.kotlinx.json.json
24+
import kotlinx.serialization.json.Json
25+
import okhttp3.Cache
26+
import okhttp3.OkHttpClient
27+
import java.io.File
28+
import javax.inject.Singleton
29+
30+
@Module
31+
internal interface NetworkModule {
32+
33+
@Binds
34+
@Singleton
35+
fun bindRickAndMortyNetworkService(impl: RickAndMortyNetworkServiceImpl): RickAndMortyNetworkService
36+
37+
companion object {
38+
@Provides
39+
@Singleton
40+
fun provideOkHttpClient(
41+
context: Context,
42+
config: BenchmarkConfig,
43+
sdkCore: dagger.Lazy<SdkCore>
44+
): OkHttpClient {
45+
return OkHttpClient.Builder().apply {
46+
cache(Cache(File(context.cacheDir, "okhttp-cache"), OKHTTP_CACHE_SIZE_BYTES))
47+
48+
if (config.scenario == SyntheticsScenario.RumAuto && config.run == SyntheticsRun.Instrumented) {
49+
val interceptor = DatadogInterceptor.Builder(emptyMap()).apply {
50+
setSdkInstanceName(sdkCore.get().name)
51+
}.build()
52+
53+
addInterceptor(interceptor)
54+
}
55+
}.build()
56+
}
57+
58+
@Provides
59+
@Singleton
60+
fun provideKtorHttpClient(
61+
okHttpClient: OkHttpClient
62+
): HttpClient {
63+
return HttpClient(OkHttp) {
64+
engine {
65+
preconfigured = okHttpClient
66+
}
67+
install(ContentNegotiation) {
68+
json(Json { ignoreUnknownKeys = true })
69+
}
70+
}
71+
}
72+
}
73+
}
74+
75+
private const val OKHTTP_CACHE_SIZE_BYTES: Long = 10 * 1024 * 1024 // 10mb
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2016-Present Datadog, Inc.
5+
*/
6+
7+
package com.datadog.benchmark.sample.network
8+
9+
import io.ktor.client.HttpClient
10+
import io.ktor.client.call.body
11+
import io.ktor.client.request.get
12+
import io.ktor.http.HttpStatusCode
13+
import io.ktor.http.Url
14+
import io.ktor.utils.io.errors.IOException
15+
16+
internal sealed interface KtorHttpResponse<out T : Any> {
17+
data class Success<T : Any>(val result: T) : KtorHttpResponse<T>
18+
data class IOError(val exception: IOException) : KtorHttpResponse<Nothing>
19+
data class UnknownException(val exception: Exception) : KtorHttpResponse<Nothing>
20+
data class ServerError(val code: HttpStatusCode) : KtorHttpResponse<Nothing>
21+
data class ClientError(val code: HttpStatusCode) : KtorHttpResponse<Nothing>
22+
23+
val optionalResult: T? get() = (this as? Success)?.result
24+
}
25+
26+
@Suppress("TooGenericExceptionCaught", "MagicNumber")
27+
internal suspend inline fun <reified T : Any> HttpClient.safeGet(url: Url): KtorHttpResponse<T> {
28+
return try {
29+
val response = get(url)
30+
val statusCode = response.status
31+
when (statusCode.value) {
32+
in 500..599 -> KtorHttpResponse.ServerError(statusCode)
33+
in 400..499 -> KtorHttpResponse.ClientError(statusCode)
34+
else -> KtorHttpResponse.Success(response.body())
35+
}
36+
} catch (e: IOException) {
37+
KtorHttpResponse.IOError(e)
38+
} catch (e: Exception) {
39+
KtorHttpResponse.UnknownException(e)
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2016-Present Datadog, Inc.
5+
*/
6+
7+
package com.datadog.benchmark.sample.network.rickandmorty
8+
9+
import com.datadog.benchmark.sample.network.KtorHttpResponse
10+
import com.datadog.benchmark.sample.network.rickandmorty.models.Character
11+
import com.datadog.benchmark.sample.network.rickandmorty.models.CharacterResponse
12+
import com.datadog.benchmark.sample.network.rickandmorty.models.Episode
13+
import com.datadog.benchmark.sample.network.rickandmorty.models.EpisodeResponse
14+
import com.datadog.benchmark.sample.network.rickandmorty.models.Location
15+
import com.datadog.benchmark.sample.network.rickandmorty.models.LocationResponse
16+
17+
internal interface RickAndMortyNetworkService {
18+
suspend fun getCharacter(id: Int): KtorHttpResponse<Character>
19+
suspend fun getCharacters(nextPageUrl: String?): KtorHttpResponse<CharacterResponse>
20+
suspend fun getCharacters(ids: List<String>): KtorHttpResponse<List<Character>>
21+
22+
suspend fun getLocation(id: Int): KtorHttpResponse<Location>
23+
suspend fun getLocations(nextPageUrl: String?): KtorHttpResponse<LocationResponse>
24+
25+
suspend fun getEpisode(id: Int): KtorHttpResponse<Episode>
26+
suspend fun getEpisodes(ids: List<String>): KtorHttpResponse<List<Episode>>
27+
suspend fun getEpisodes(nextPageUrl: String?): KtorHttpResponse<EpisodeResponse>
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2016-Present Datadog, Inc.
5+
*/
6+
7+
package com.datadog.benchmark.sample.network.rickandmorty
8+
9+
import com.datadog.benchmark.sample.network.KtorHttpResponse
10+
import com.datadog.benchmark.sample.network.rickandmorty.models.Character
11+
import com.datadog.benchmark.sample.network.rickandmorty.models.CharacterResponse
12+
import com.datadog.benchmark.sample.network.rickandmorty.models.Episode
13+
import com.datadog.benchmark.sample.network.rickandmorty.models.EpisodeResponse
14+
import com.datadog.benchmark.sample.network.rickandmorty.models.Location
15+
import com.datadog.benchmark.sample.network.rickandmorty.models.LocationResponse
16+
import com.datadog.benchmark.sample.network.safeGet
17+
import io.ktor.client.HttpClient
18+
import io.ktor.http.URLBuilder
19+
import io.ktor.http.Url
20+
import io.ktor.http.appendPathSegments
21+
import javax.inject.Inject
22+
23+
internal class RickAndMortyNetworkServiceImpl @Inject constructor(
24+
private val httpClient: HttpClient
25+
) : RickAndMortyNetworkService {
26+
27+
override suspend fun getCharacter(id: Int): KtorHttpResponse<Character> {
28+
val url = URLBuilder(BASE_URL).apply {
29+
appendPathSegments(CHARACTER_PATH, id.toString())
30+
}.build()
31+
32+
return httpClient.safeGet(url)
33+
}
34+
35+
override suspend fun getLocation(id: Int): KtorHttpResponse<Location> {
36+
val url = URLBuilder(BASE_URL).apply {
37+
appendPathSegments(LOCATION_PATH, id.toString())
38+
}.build()
39+
40+
return httpClient.safeGet(url)
41+
}
42+
43+
override suspend fun getLocations(nextPageUrl: String?): KtorHttpResponse<LocationResponse> {
44+
val url = if (nextPageUrl != null) {
45+
Url(nextPageUrl)
46+
} else {
47+
URLBuilder(BASE_URL).apply {
48+
appendPathSegments(LOCATION_PATH)
49+
}.build()
50+
}
51+
52+
return httpClient.safeGet(url)
53+
}
54+
55+
override suspend fun getEpisode(id: Int): KtorHttpResponse<Episode> {
56+
val url = URLBuilder(BASE_URL).apply {
57+
appendPathSegments(EPISODE_PATH, id.toString())
58+
}.build()
59+
60+
return httpClient.safeGet(url)
61+
}
62+
63+
override suspend fun getCharacters(nextPageUrl: String?): KtorHttpResponse<CharacterResponse> {
64+
val url = if (nextPageUrl != null) {
65+
Url(nextPageUrl)
66+
} else {
67+
URLBuilder(BASE_URL).apply {
68+
appendPathSegments(CHARACTER_PATH)
69+
}.build()
70+
}
71+
72+
return httpClient.safeGet(url)
73+
}
74+
75+
override suspend fun getCharacters(ids: List<String>): KtorHttpResponse<List<Character>> {
76+
val url = URLBuilder(BASE_URL).apply {
77+
appendPathSegments(CHARACTER_PATH, ids.joinToString(","))
78+
}.build()
79+
80+
return httpClient.safeGet(url)
81+
}
82+
83+
override suspend fun getEpisodes(ids: List<String>): KtorHttpResponse<List<Episode>> {
84+
val url = URLBuilder(BASE_URL).apply {
85+
appendPathSegments(EPISODE_PATH, "[${ids.joinToString(",")}]")
86+
}.build()
87+
88+
return httpClient.safeGet(url)
89+
}
90+
91+
override suspend fun getEpisodes(nextPageUrl: String?): KtorHttpResponse<EpisodeResponse> {
92+
val url = if (nextPageUrl != null) {
93+
Url(nextPageUrl)
94+
} else {
95+
URLBuilder(BASE_URL).apply {
96+
appendPathSegments(EPISODE_PATH)
97+
}.build()
98+
}
99+
100+
return httpClient.safeGet(url)
101+
}
102+
}
103+
104+
private const val BASE_URL = "https://rickandmortyapi.com/api"
105+
private const val CHARACTER_PATH = "character"
106+
private const val LOCATION_PATH = "location"
107+
private const val EPISODE_PATH = "episode"

0 commit comments

Comments
 (0)