Skip to content

Commit

Permalink
chore: migrate to moshi to reduce bundle size (#71)
Browse files Browse the repository at this point in the history
* chore: migrate to moshi to reduce bundle size

* Use proguard for moshi
  • Loading branch information
gastonfournier authored Jul 25, 2024
1 parent f1589ce commit f0cfe13
Show file tree
Hide file tree
Showing 13 changed files with 100 additions and 51 deletions.
8 changes: 3 additions & 5 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ androidxTestExt = "1.2.1"
espressoCore = "3.6.1"
appcompat = "1.7.0"
material = "1.12.0"
moshi = "1.15.1"
work = "2.9.0"
okhttp = "4.12.0"
jackson = "2.17.2"
assertj = "3.26.3"
lifecycleProcess = "2.8.3"
kotlinxCoroutinesTest = "1.8.1"
Expand All @@ -30,15 +30,13 @@ androidx-work-ktx = { group = "androidx.work", name = "work-runtime-ktx", versio
androidx-work-testing = { group = "androidx.work", name = "work-testing", version.ref = "work" }
androidx-lifecycle-process = { group = "androidx.lifecycle", name = "lifecycle-process", version.ref = "lifecycleProcess" }
kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutinesTest" }
moshi-kotlin = { group = "com.squareup.moshi", name="moshi-kotlin", version.ref = "moshi" }
moshi-adapters = { group = "com.squareup.moshi", name="moshi-adapters", version.ref = "moshi" }
robolectric-test = { group = "org.robolectric", name = "robolectric", version = "4.13" }
awaitility = { group = "org.awaitility", name = "awaitility", version = "4.2.1" }

okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
okhttp-mockserver = { group = "com.squareup.okhttp3", name = "mockwebserver", version.ref = "okhttp" }
jackson-databind = { group = "com.fasterxml.jackson.core", name = "jackson-databind", version.ref = "jackson" }
jackson-core = { group = "com.fasterxml.jackson.core", name = "jackson-core", version.ref = "jackson" }
jackson-module-kotlin = { group = "com.fasterxml.jackson.module", name = "jackson-module-kotlin", version.ref = "jackson" }
jackson-datatype-jsr310 = { group = "com.fasterxml.jackson.datatype", name = "jackson-datatype-jsr310", version.ref = "jackson" }
assertj = { group = "org.assertj", name = "assertj-core", version.ref = "assertj" }
jsonunit = { group = "net.javacrumbs.json-unit", name = "json-unit-assertj", version.ref = "jsonunit" }

Expand Down
6 changes: 2 additions & 4 deletions unleashandroidsdk/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,8 @@ dependencies {

implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.process)
implementation(libs.jackson.databind)
implementation(libs.jackson.core)
implementation(libs.jackson.module.kotlin)
implementation(libs.jackson.datatype.jsr310)
implementation(libs.moshi.kotlin)
implementation(libs.moshi.adapters)
api(libs.okhttp)

testImplementation(libs.junit)
Expand Down
4 changes: 3 additions & 1 deletion unleashandroidsdk/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-keep public class io.getunleash.** { *; }
-keep class com.fasterxml.** { *; }
-keepclasseswithmembers class * {
@com.squareup.moshi.* methods;
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ class DefaultUnleash(
lifecycle.addObserver(taskManager)
if (bootstrapFile != null && bootstrapFile.exists()) {
Log.i(TAG, "Using provided bootstrap file")
Parser.jackson.readValue(bootstrapFile, ProxyResponse::class.java)?.let { state ->
Parser.proxyResponseAdapter.fromJson(bootstrapFile.readText())?.let { state ->
val toggles = state.toggles.groupBy { it.name }
.mapValues { (_, v) -> v.first() }
cache.write(UnleashState(unleashContextState.value, toggles))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package io.getunleash.android.backup

import android.util.Log
import com.fasterxml.jackson.module.kotlin.readValue
import io.getunleash.android.data.Parser
import com.squareup.moshi.JsonAdapter
import io.getunleash.android.data.Parser.moshi
import io.getunleash.android.data.Toggle
import io.getunleash.android.data.UnleashContext
import io.getunleash.android.data.UnleashState
Expand All @@ -13,7 +13,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File

private data class BackupState(val contextId: String, val toggles: Map<String, Toggle>)
data class BackupState(val contextId: String, val toggles: Map<String, Toggle>)

/**
* Local backup of the last state of the Unleash SDK.
Expand All @@ -29,6 +29,7 @@ class LocalBackup(
private const val TAG = "LocalBackup"
internal const val STATE_BACKUP_FILE = "unleash_state.json"
}
private val backupAdapter: JsonAdapter<BackupState> = moshi.adapter(BackupState::class.java)

fun subscribeTo(state: Flow<UnleashState>) {
unleashScope.launch {
Expand All @@ -49,7 +50,14 @@ class LocalBackup(
try {
// write only the last state
val contextBackup = File(localDir.absolutePath, STATE_BACKUP_FILE)
contextBackup.writeBytes(Parser.jackson.writeValueAsBytes(BackupState(id(state.context), state.toggles)))
contextBackup.writeBytes(
backupAdapter.toJson(
BackupState(
id(state.context),
state.toggles
)
).toByteArray(Charsets.UTF_8)
)
Log.d(TAG, "Written state to ${contextBackup.absolutePath}")
} catch (e: Exception) {
Log.i(TAG, "Error writing to disc", e)
Expand All @@ -60,7 +68,8 @@ class LocalBackup(
val stateBackup = File(localDir.absolutePath, STATE_BACKUP_FILE)
try {
if (stateBackup.exists()) {
val backupState = Parser.jackson.readValue<BackupState>(stateBackup.readBytes())
val backupState =
backupAdapter.fromJson(stateBackup.readText(Charsets.UTF_8)) ?: return null
if (backupState.contextId != id(context)) {
Log.i(TAG, "Context id mismatch, ignoring backup for context id ${backupState.contextId}")
return null
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,66 @@
package io.getunleash.android.data

import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.databind.util.StdDateFormat
import com.fasterxml.jackson.module.kotlin.KotlinFeature
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import com.squareup.moshi.adapters.Rfc3339DateJsonAdapter
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import io.getunleash.android.metrics.MetricsPayload
import io.getunleash.android.polling.ProxyResponse
import java.lang.reflect.Type
import java.util.Date

object Parser {
val jackson: ObjectMapper =
/*val jackson: ObjectMapper =
jacksonObjectMapper {
enable(KotlinFeature.NullIsSameAsDefault)
}.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.setDateFormat(
StdDateFormat().withColonInTimeZone(true)
).disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
).disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)*/



val moshi: Moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory()) // for Kotlin support
.add(Date::class.java, Rfc3339DateJsonAdapter().nullSafe()) // for date format
// .add(DefaultOnNullAdapterFactory())
.build()
val proxyResponseAdapter: JsonAdapter<ProxyResponse> = moshi.adapter(ProxyResponse::class.java)
val metricsBodyAdapter: JsonAdapter<MetricsPayload> = moshi.adapter(MetricsPayload::class.java)

// Define an adapter to handle null values as default
class DefaultOnNullAdapterFactory : JsonAdapter.Factory {
override fun create(
type: Type,
annotations: MutableSet<out Annotation>,
moshi: Moshi
): JsonAdapter<*>? {
val rawType = Types.getRawType(type)
if (rawType.isAnnotationPresent(DefaultOnNull::class.java)) {
val delegate = moshi.nextAdapter<Any>(this, type, annotations)
return object : JsonAdapter<Any>() {
override fun fromJson(reader: JsonReader): Any? {
if (reader.peek() == JsonReader.Token.NULL) {
reader.nextNull<Unit>()
return rawType.getDeclaredConstructor().newInstance()
}
return delegate.fromJson(reader)
}

override fun toJson(writer: JsonWriter, value: Any?) {
delegate.toJson(writer, value)
}
}
}
return null
}
}

// Custom annotation to handle default values
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class DefaultOnNull

}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package io.getunleash.android.data

import com.fasterxml.jackson.annotation.JsonProperty
import com.squareup.moshi.Json

data class Variant(
val name: String,
val enabled: Boolean = true,
@JsonProperty("feature_enabled") val featureEnabled: Boolean = false,
@Json(name = "feature_enabled") val featureEnabled: Boolean = false,
val payload: Payload? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import java.util.concurrent.atomic.AtomicInteger
data class EvaluationCount(
var yes: Int,
var no: Int,
val variants: ConcurrentHashMap<String, Int> = ConcurrentHashMap()
val variants: MutableMap<String, Int> = mutableMapOf()
)

data class Bucket(
val start: Date,
val stop: Date,
val toggles: ConcurrentHashMap<String, EvaluationCount> = ConcurrentHashMap()
val toggles: MutableMap<String, EvaluationCount> = mutableMapOf()
)

interface UnleashMetricsBucket {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package io.getunleash.android.metrics

import android.util.Log
import io.getunleash.android.UnleashConfig
import io.getunleash.android.data.Parser
import io.getunleash.android.data.Parser.metricsBodyAdapter
import io.getunleash.android.data.Variant
import io.getunleash.android.http.Throttler
import okhttp3.Call
Expand Down Expand Up @@ -51,7 +51,7 @@ class MetricsSender(
val request = Request.Builder()
.headers(applicationHeaders.toHeaders())
.url(metricsUrl).post(
Parser.jackson.writeValueAsString(payload)
metricsBodyAdapter.toJson(payload)
.toRequestBody("application/json".toMediaType())
).build()
httpClient.newCall(request).enqueue(object : Callback {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package io.getunleash.android.polling

import android.util.Log
import com.fasterxml.jackson.module.kotlin.readValue
import io.getunleash.android.UnleashConfig
import io.getunleash.android.data.Parser
import io.getunleash.android.data.Parser.proxyResponseAdapter
import io.getunleash.android.data.UnleashContext
import io.getunleash.android.data.UnleashState
import io.getunleash.android.errors.NoBodyException
Expand Down Expand Up @@ -157,7 +156,7 @@ open class UnleashFetcher(
res.body?.use { b ->
try {
val proxyResponse: ProxyResponse =
Parser.jackson.readValue(b.string())
proxyResponseAdapter.fromJson(b.string())!!
FetchResponse(Status.SUCCESS, proxyResponse)
} catch (e: Exception) {
// If we fail to parse, just keep data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ abstract class BaseTest {
ShadowLog.setTimeSupplier {
df.format(Date())
}

System.setProperty("json-unit.libraries", "moshi")
}
}

Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package io.getunleash.android.data

import com.fasterxml.jackson.module.kotlin.readValue
import io.getunleash.android.polling.ProxyResponse
import com.squareup.moshi.JsonAdapter
import io.getunleash.android.data.Parser.moshi
import io.getunleash.android.data.Parser.proxyResponseAdapter
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test

Expand All @@ -16,51 +17,52 @@ private const val stringPayloadVariant = "{\n" +

class PayloadTest {

val variantsAdapter: JsonAdapter<Variant> = moshi.adapter(Variant::class.java)
@Test
fun testGetValueAsString() {
val variant = Parser.jackson.readValue(stringPayloadVariant, Variant::class.java)
val variant = variantsAdapter.fromJson(stringPayloadVariant)!!
assertThat(variant.payload?.getValueAsString()).isEqualTo("11")
}

@Test
fun testGetValueAsNumber() {
val variant = Parser.jackson.readValue(stringPayloadVariant, Variant::class.java)
val variant = variantsAdapter.fromJson(stringPayloadVariant)!!
assertThat(variant.payload?.getValueAsInt()).isEqualTo(11)
}

@Test
fun testGetValueAsBool() {
val variant = Parser.jackson.readValue(stringPayloadVariant, Variant::class.java)
val variant = variantsAdapter.fromJson(stringPayloadVariant)!!
assertThat(variant.payload?.getValueAsBoolean()).isFalse()
}

@Test
fun `Able to parse payload value as string`() {
val response: ProxyResponse = Parser.jackson.readValue(TestResponses.threeToggles)
val response = proxyResponseAdapter.fromJson(TestResponses.threeToggles)!!
val map = response.toggles.groupBy { it.name }.mapValues { (_, v) -> v.first() }
val toggle = map["variantToggle"]!!
assertThat(toggle.variant.payload!!.getValueAsString()).isEqualTo("some-text")
}

@Test
fun `Able to parse payload value as integer`() {
val response: ProxyResponse = Parser.jackson.readValue(TestResponses.complicatedVariants)
val response = proxyResponseAdapter.fromJson(TestResponses.complicatedVariants)!!
val map = response.toggles.groupBy { it.name }.mapValues { (_, v) -> v.first() }
val toggle = map["variantToggle"]!!
assertThat(toggle.variant.payload!!.getValueAsInt()).isEqualTo(54)
}

@Test
fun `Able to parse payload value as boolean`() {
val response: ProxyResponse = Parser.jackson.readValue(TestResponses.complicatedVariants)
val response = proxyResponseAdapter.fromJson(TestResponses.complicatedVariants)!!
val map = response.toggles.groupBy { it.name }.mapValues { (_, v) -> v.first() }
val toggle = map["booleanVariant"]!!
assertThat(toggle.variant.payload!!.getValueAsBoolean()).isTrue
}

@Test
fun `Able to parse payload value as double`() {
val response: ProxyResponse = Parser.jackson.readValue(TestResponses.complicatedVariants)
val response = proxyResponseAdapter.fromJson(TestResponses.complicatedVariants)!!
val map = response.toggles.groupBy { it.name }.mapValues { (_, v) -> v.first() }
val toggle = map["doubleVariant"]!!
assertThat(toggle.variant.payload!!.getValueAsDouble()).isEqualTo(42.0)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package io.getunleash.android.data

import com.fasterxml.jackson.module.kotlin.readValue
import io.getunleash.android.polling.ProxyResponse

object TestResponses {
val threeToggles = """
{
Expand Down Expand Up @@ -86,8 +83,4 @@ object TestResponses {
]
}""".trimIndent()

fun String.toToggleMap(): Map<String, Toggle> {
val response: ProxyResponse = Parser.jackson.readValue(this)
return response.toggles.groupBy { it.name }.mapValues { (_, v) -> v.first() }
}
}

0 comments on commit f0cfe13

Please sign in to comment.