1
0
mirror of https://github.com/wulkanowy/wulkanowy.git synced 2024-09-20 04:49:08 -05:00

Migrate from moshi to kotlinx serialization (#1557)

This commit is contained in:
Mikołaj Pich 2021-10-04 17:13:31 +02:00 committed by GitHub
parent 2d84b0775a
commit 1839d7cb8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 56 additions and 132 deletions

View File

@ -1,5 +1,6 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
apply plugin: 'kotlin-parcelize' apply plugin: 'kotlin-parcelize'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin' apply plugin: 'dagger.hilt.android.plugin'
@ -162,7 +163,7 @@ ext {
room = "2.3.0" room = "2.3.0"
chucker = "3.5.2" chucker = "3.5.2"
mockk = "1.12.0" mockk = "1.12.0"
moshi = "1.12.0" coroutines = "1.5.2"
} }
dependencies { dependencies {
@ -170,7 +171,8 @@ dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2" implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
implementation "androidx.core:core-ktx:1.6.0" implementation "androidx.core:core-ktx:1.6.0"
implementation "androidx.activity:activity-ktx:1.3.1" implementation "androidx.activity:activity-ktx:1.3.1"
@ -207,10 +209,6 @@ dependencies {
implementation 'com.github.ncapdevi:FragNav:3.3.0' implementation 'com.github.ncapdevi:FragNav:3.3.0'
implementation "com.github.YarikSOffice:lingver:1.3.0" implementation "com.github.YarikSOffice:lingver:1.3.0"
implementation "com.squareup.moshi:moshi:$moshi"
implementation "com.squareup.moshi:moshi-adapters:$moshi"
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi"
implementation "com.jakewharton.timber:timber:5.0.1" implementation "com.jakewharton.timber:timber:5.0.1"
implementation "at.favre.lib:slf4j-timber:1.0.1" implementation "at.favre.lib:slf4j-timber:1.0.1"
implementation 'com.github.bastienpaulfr:Treessence:1.0.4' implementation 'com.github.bastienpaulfr:Treessence:1.0.4'
@ -237,7 +235,7 @@ dependencies {
testImplementation "junit:junit:4.13.2" testImplementation "junit:junit:4.13.2"
testImplementation "io.mockk:mockk:$mockk" testImplementation "io.mockk:mockk:$mockk"
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.2' testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines"
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
testImplementation 'org.robolectric:robolectric:4.6.1' testImplementation 'org.robolectric:robolectric:4.6.1'

View File

@ -9,7 +9,6 @@ import com.chuckerteam.chucker.api.ChuckerCollector
import com.chuckerteam.chucker.api.ChuckerInterceptor import com.chuckerteam.chucker.api.ChuckerInterceptor
import com.chuckerteam.chucker.api.RetentionManager import com.chuckerteam.chucker.api.RetentionManager
import com.fredporciuncula.flow.preferences.FlowSharedPreferences import com.fredporciuncula.flow.preferences.FlowSharedPreferences
import com.squareup.moshi.Moshi
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
@ -21,6 +20,7 @@ import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.serialization.json.Json
import timber.log.Timber import timber.log.Timber
import javax.inject.Singleton import javax.inject.Singleton
@ -88,7 +88,9 @@ internal class RepositoryModule {
@Singleton @Singleton
@Provides @Provides
fun provideMoshi() = Moshi.Builder().build() fun provideJson() = Json {
ignoreUnknownKeys = true
}
@Singleton @Singleton
@Provides @Provides

View File

@ -1,9 +1,10 @@
package io.github.wulkanowy.data.db package io.github.wulkanowy.data.db
import androidx.room.TypeConverter import androidx.room.TypeConverter
import com.squareup.moshi.Moshi import kotlinx.serialization.SerializationException
import com.squareup.moshi.Types import kotlinx.serialization.decodeFromString
import io.github.wulkanowy.data.db.adapters.PairAdapterFactory import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.time.Instant import java.time.Instant
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalDateTime import java.time.LocalDateTime
@ -13,15 +14,7 @@ import java.util.Date
class Converters { class Converters {
private val moshi by lazy { Moshi.Builder().add(PairAdapterFactory).build() } private val json = Json
private val integerListAdapter by lazy {
moshi.adapter<List<Int>>(Types.newParameterizedType(List::class.java, Integer::class.java))
}
private val stringListPairAdapter by lazy {
moshi.adapter<List<Pair<String, String>>>(Types.newParameterizedType(List::class.java, Pair::class.java, String::class.java, String::class.java))
}
@TypeConverter @TypeConverter
fun timestampToDate(value: Long?): LocalDate? = value?.run { fun timestampToDate(value: Long?): LocalDate? = value?.run {
@ -51,21 +44,25 @@ class Converters {
@TypeConverter @TypeConverter
fun intListToJson(list: List<Int>): String { fun intListToJson(list: List<Int>): String {
return integerListAdapter.toJson(list) return json.encodeToString(list)
} }
@TypeConverter @TypeConverter
fun jsonToIntList(value: String): List<Int> { fun jsonToIntList(value: String): List<Int> {
return integerListAdapter.fromJson(value).orEmpty() return json.decodeFromString(value)
} }
@TypeConverter @TypeConverter
fun stringPairListToJson(list: List<Pair<String, String>>): String { fun stringPairListToJson(list: List<Pair<String, String>>): String {
return stringListPairAdapter.toJson(list) return json.encodeToString(list)
} }
@TypeConverter @TypeConverter
fun jsonToStringPairList(value: String): List<Pair<String, String>> { fun jsonToStringPairList(value: String): List<Pair<String, String>> {
return stringListPairAdapter.fromJson(value).orEmpty() return try {
json.decodeFromString(value)
} catch (e: SerializationException) {
emptyList() // handle errors from old gson Pair serialized data
}
} }
} }

View File

@ -1,68 +0,0 @@
package io.github.wulkanowy.data.db.adapters
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 java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
object PairAdapterFactory : JsonAdapter.Factory {
override fun create(type: Type, annotations: MutableSet<out Annotation>, moshi: Moshi): JsonAdapter<*>? {
if (type !is ParameterizedType || List::class.java != type.rawType) return null
if (type.actualTypeArguments[0] != Pair::class.java) return null
val listType = Types.newParameterizedType(List::class.java, Map::class.java, String::class.java)
val listAdapter = moshi.adapter<List<Map<String, String>>>(listType)
val mapType = Types.newParameterizedType(MutableMap::class.java, String::class.java, String::class.java)
val mapAdapter = moshi.adapter<Map<String, String>>(mapType)
return PairAdapter(listAdapter, mapAdapter)
}
private class PairAdapter(
private val listAdapter: JsonAdapter<List<Map<String, String>>>,
private val mapAdapter: JsonAdapter<Map<String, String>>,
) : JsonAdapter<List<Pair<String, String>>>() {
override fun toJson(writer: JsonWriter, value: List<Pair<String, String>>?) {
writer.beginArray()
value?.forEach {
writer.beginObject()
writer.name("first").value(it.first)
writer.name("second").value(it.second)
writer.endObject()
}
writer.endArray()
}
override fun fromJson(reader: JsonReader): List<Pair<String, String>>? {
return if (reader.peek() == JsonReader.Token.BEGIN_OBJECT) deserializeMoshiMap(reader)
else deserializeGsonPair(reader)
}
// for compatibility with 0.21.0
private fun deserializeMoshiMap(reader: JsonReader): List<Pair<String, String>>? {
val map = mapAdapter.fromJson(reader) ?: return null
return map.entries.map {
it.key to it.value
}
}
private fun deserializeGsonPair(reader: JsonReader): List<Pair<String, String>>? {
val list = listAdapter.fromJson(reader) ?: return null
return list.map {
require(it.size == 2) {
"pair with more or less than two elements: $list"
}
it["first"].orEmpty() to it["second"].orEmpty()
}
}
}
}

View File

@ -3,10 +3,9 @@ package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.squareup.moshi.JsonClass
import java.io.Serializable import java.io.Serializable
@JsonClass(generateAdapter = true) @kotlinx.serialization.Serializable
@Entity(tableName = "Recipients") @Entity(tableName = "Recipients")
data class Recipient( data class Recipient(

View File

@ -1,8 +1,8 @@
package io.github.wulkanowy.data.pojos package io.github.wulkanowy.data.pojos
import com.squareup.moshi.JsonClass import kotlinx.serialization.Serializable
@JsonClass(generateAdapter = true) @Serializable
class Contributor( class Contributor(
val displayName: String, val displayName: String,
val githubUsername: String val githubUsername: String

View File

@ -1,9 +1,9 @@
package io.github.wulkanowy.data.pojos package io.github.wulkanowy.data.pojos
import com.squareup.moshi.JsonClass
import io.github.wulkanowy.ui.modules.message.send.RecipientChipItem import io.github.wulkanowy.ui.modules.message.send.RecipientChipItem
import kotlinx.serialization.Serializable
@JsonClass(generateAdapter = true) @Serializable
data class MessageDraft( data class MessageDraft(
val recipients: List<RecipientChipItem>, val recipients: List<RecipientChipItem>,
val subject: String, val subject: String,

View File

@ -1,25 +1,26 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import android.content.res.AssetManager import android.content.res.AssetManager
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import io.github.wulkanowy.data.pojos.Contributor import io.github.wulkanowy.data.pojos.Contributor
import io.github.wulkanowy.utils.DispatchersProvider import io.github.wulkanowy.utils.DispatchersProvider
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class AppCreatorRepository @Inject constructor( class AppCreatorRepository @Inject constructor(
private val assets: AssetManager, private val assets: AssetManager,
private val dispatchers: DispatchersProvider private val dispatchers: DispatchersProvider,
private val json: Json,
) { ) {
@OptIn(ExperimentalSerializationApi::class)
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
suspend fun getAppCreators() = withContext(dispatchers.backgroundThread) { suspend fun getAppCreators() = withContext(dispatchers.backgroundThread) {
val moshi = Moshi.Builder().build() val inputStream = assets.open("contributors.json").buffered()
val type = Types.newParameterizedType(List::class.java, Contributor::class.java) json.decodeFromStream<List<Contributor>>(inputStream)
val adapter = moshi.adapter<List<Contributor>>(type)
adapter.fromJson(assets.open("contributors.json").bufferedReader().use { it.readText() })
} }
} }

View File

@ -1,7 +1,6 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import android.content.Context import android.content.Context
import com.squareup.moshi.Moshi
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.Resource
@ -18,7 +17,6 @@ import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
import io.github.wulkanowy.data.mappers.mapFromEntities import io.github.wulkanowy.data.mappers.mapFromEntities
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.pojos.MessageDraft import io.github.wulkanowy.data.pojos.MessageDraft
import io.github.wulkanowy.data.pojos.MessageDraftJsonAdapter
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.pojo.Folder import io.github.wulkanowy.sdk.pojo.Folder
import io.github.wulkanowy.sdk.pojo.SentMessage import io.github.wulkanowy.sdk.pojo.SentMessage
@ -29,6 +27,9 @@ import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import timber.log.Timber import timber.log.Timber
import java.time.LocalDateTime.now import java.time.LocalDateTime.now
import javax.inject.Inject import javax.inject.Inject
@ -42,7 +43,7 @@ class MessageRepository @Inject constructor(
@ApplicationContext private val context: Context, @ApplicationContext private val context: Context,
private val refreshHelper: AutoRefreshHelper, private val refreshHelper: AutoRefreshHelper,
private val sharedPrefProvider: SharedPrefProvider, private val sharedPrefProvider: SharedPrefProvider,
private val moshi: Moshi, private val json: Json,
) { ) {
private val saveFetchResultMutex = Mutex() private val saveFetchResultMutex = Mutex()
@ -168,9 +169,9 @@ class MessageRepository @Inject constructor(
var draftMessage: MessageDraft? var draftMessage: MessageDraft?
get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_send_draft)) get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_send_draft))
?.let { MessageDraftJsonAdapter(moshi).fromJson(it) } ?.let { json.decodeFromString(it) }
set(value) = sharedPrefProvider.putString( set(value) = sharedPrefProvider.putString(
context.getString(R.string.pref_key_message_send_draft), context.getString(R.string.pref_key_message_send_draft),
value?.let { MessageDraftJsonAdapter(moshi).toJson(it) } value?.let { json.encodeToString(it) }
) )
} }

View File

@ -5,9 +5,6 @@ import android.content.SharedPreferences
import androidx.core.content.edit import androidx.core.content.edit
import com.fredporciuncula.flow.preferences.FlowSharedPreferences import com.fredporciuncula.flow.preferences.FlowSharedPreferences
import com.fredporciuncula.flow.preferences.Preference import com.fredporciuncula.flow.preferences.Preference
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.adapter
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.sdk.toLocalDate import io.github.wulkanowy.sdk.toLocalDate
@ -19,6 +16,9 @@ import io.github.wulkanowy.utils.toTimestamp
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalDateTime import java.time.LocalDateTime
import javax.inject.Inject import javax.inject.Inject
@ -27,16 +27,12 @@ import javax.inject.Singleton
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
@Singleton @Singleton
class PreferencesRepository @Inject constructor( class PreferencesRepository @Inject constructor(
@ApplicationContext val context: Context,
private val sharedPref: SharedPreferences, private val sharedPref: SharedPreferences,
private val flowSharedPref: FlowSharedPreferences, private val flowSharedPref: FlowSharedPreferences,
@ApplicationContext val context: Context, private val json: Json,
moshi: Moshi
) { ) {
@OptIn(ExperimentalStdlibApi::class)
private val dashboardItemsPositionAdapter: JsonAdapter<Map<DashboardItem.Type, Int>> =
moshi.adapter()
val startMenuIndex: Int val startMenuIndex: Int
get() = getString(R.string.pref_key_start_menu, R.string.pref_default_startup).toInt() get() = getString(R.string.pref_key_start_menu, R.string.pref_default_startup).toInt()
@ -197,14 +193,14 @@ class PreferencesRepository @Inject constructor(
var dashboardItemsPosition: Map<DashboardItem.Type, Int>? var dashboardItemsPosition: Map<DashboardItem.Type, Int>?
get() { get() {
val json = sharedPref.getString(PREF_KEY_DASHBOARD_ITEMS_POSITION, null) ?: return null val value = sharedPref.getString(PREF_KEY_DASHBOARD_ITEMS_POSITION, null) ?: return null
return dashboardItemsPositionAdapter.fromJson(json) return json.decodeFromString(value)
} }
set(value) = sharedPref.edit { set(value) = sharedPref.edit {
putString( putString(
PREF_KEY_DASHBOARD_ITEMS_POSITION, PREF_KEY_DASHBOARD_ITEMS_POSITION,
dashboardItemsPositionAdapter.toJson(value) json.encodeToString(value)
) )
} }

View File

@ -1,10 +1,10 @@
package io.github.wulkanowy.ui.modules.message.send package io.github.wulkanowy.ui.modules.message.send
import com.squareup.moshi.JsonClass
import io.github.wulkanowy.data.db.entities.Recipient import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.materialchipsinput.ChipItem import io.github.wulkanowy.materialchipsinput.ChipItem
import kotlinx.serialization.Serializable
@JsonClass(generateAdapter = true) @Serializable
data class RecipientChipItem( data class RecipientChipItem(
override val title: String, override val title: String,

View File

@ -23,8 +23,8 @@ class ConvertersTest {
@Test @Test
fun jsonToStringPairList_0210() { fun jsonToStringPairList_0210() {
assertEquals(Converters().jsonToStringPairList("{\"aaa\":\"bbb\",\"ccc\":\"ddd\"}"), listOf("aaa" to "bbb", "ccc" to "ddd")) assertEquals(Converters().jsonToStringPairList("{\"aaa\":\"bbb\",\"ccc\":\"ddd\"}"), listOf<Pair<String, String>>())
assertEquals(Converters().jsonToStringPairList("{\"aaa\":\"bbb\"}"), listOf("aaa" to "bbb")) assertEquals(Converters().jsonToStringPairList("{\"aaa\":\"bbb\"}"), listOf<Pair<String, String>>())
assertEquals(Converters().jsonToStringPairList("{}"), listOf<Pair<String, String>>()) assertEquals(Converters().jsonToStringPairList("{}"), listOf<Pair<String, String>>())
} }
} }

View File

@ -1,7 +1,6 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import android.content.Context import android.content.Context
import com.squareup.moshi.Moshi
import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.db.dao.MessageAttachmentDao import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
@ -30,6 +29,7 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.toList import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.Json
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@ -63,9 +63,6 @@ class MessageRepositoryTest {
private lateinit var repository: MessageRepository private lateinit var repository: MessageRepository
@MockK
private lateinit var moshi: Moshi
@Before @Before
fun setUp() { fun setUp() {
MockKAnnotations.init(this) MockKAnnotations.init(this)
@ -78,7 +75,7 @@ class MessageRepositoryTest {
context = context, context = context,
refreshHelper = refreshHelper, refreshHelper = refreshHelper,
sharedPrefProvider = sharedPrefProvider, sharedPrefProvider = sharedPrefProvider,
moshi = moshi, json = Json,
) )
} }

View File

@ -12,6 +12,7 @@ buildscript {
} }
dependencies { dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath 'com.android.tools.build:gradle:7.0.2' classpath 'com.android.tools.build:gradle:7.0.2'
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
classpath 'com.google.gms:google-services:4.3.10' classpath 'com.google.gms:google-services:4.3.10'