Replace gson with kotlinx-serialization

This commit is contained in:
Maksim Karasev 2024-10-03 16:38:23 +03:00
parent fa9d259a21
commit 6a5f405211
7 changed files with 50 additions and 53 deletions

3
.gitignore vendored
View file

@ -67,3 +67,6 @@ fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
# kotlin
.kotlin/

2
.idea/kotlinc.xml generated
View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.9.10" />
<option name="version" value="2.0.20" />
</component>
</project>

View file

@ -2,6 +2,7 @@ plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.devtools.ksp'
id 'org.jetbrains.kotlin.plugin.serialization'
}
android {
@ -51,11 +52,12 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'androidx.activity:activity-ktx:1.9.2'
implementation 'androidx.fragment:fragment-ktx:1.8.3'
implementation 'androidx.fragment:fragment-ktx:1.8.4'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.google.guava:guava:33.1.0-android'
implementation 'com.google.code.gson:gson:2.11.0'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3'
def shizuku_version = '13.1.5'
implementation "dev.rikka.shizuku:api:$shizuku_version"
@ -71,7 +73,7 @@ dependencies {
androidTestImplementation "androidx.room:room-testing:$roomVersion"
// Lifecycle components
def lifecycleVersion = '2.8.5'
def lifecycleVersion = '2.8.6'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"

View file

@ -3,19 +3,21 @@ package ru.karasevm.privatednstoggle.model
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.google.gson.annotations.SerializedName
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
// All fields must have default values for proper deserialization
@Serializable
@Entity(tableName = "dns_servers")
data class DnsServer(
@SerializedName("id")
@SerialName("id")
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
@SerializedName("server")
@SerialName("server")
val server: String = "",
@SerializedName("label")
@SerialName("label")
val label: String = "",
@SerializedName("enabled")
@SerialName("enabled")
@ColumnInfo(defaultValue = "1")
val enabled: Boolean = true,
val sortOrder: Int? = null

View file

@ -27,12 +27,9 @@ import androidx.recyclerview.widget.ItemTouchHelper.DOWN
import androidx.recyclerview.widget.ItemTouchHelper.UP
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonSyntaxException
import com.google.gson.ToNumberPolicy
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.launch
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.lsposed.hiddenapibypass.HiddenApiBypass
import rikka.shizuku.Shizuku
import rikka.shizuku.ShizukuBinderWrapper
@ -58,7 +55,6 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
private lateinit var sharedPrefs: SharedPreferences
private lateinit var adapter: ServerListRecyclerAdapter
private lateinit var clipboard: ClipboardManager
private lateinit var gson: Gson
private val dnsServerViewModel: DnsServerViewModel by viewModels { DnsServerViewModelFactory((application as PrivateDNSApp).repository) }
private val itemTouchHelper by lazy {
@ -126,7 +122,7 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
private fun importSettings(json: String) {
runCatching {
val data: BackupUtils.Backup = gson.fromJson(json, BackupUtils.Backup::class.java)
val data: BackupUtils.Backup = Json.decodeFromString<BackupUtils.Backup>(json)
BackupUtils.import(data, dnsServerViewModel, sharedPrefs)
}.onSuccess {
Toast.makeText(
@ -134,28 +130,18 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
).show()
}.onFailure { exception ->
runCatching {
val objectType = object : TypeToken<Map<String, Any>>() {}.type
val data: Map<String, Any> = gson.fromJson(json, objectType)
Log.e("IMPORT", "Malformed json, falling back to legacy", exception)
val data = Json.decodeFromString<BackupUtils.LegacyBackup>(json)
BackupUtils.importLegacy(data, dnsServerViewModel, sharedPrefs)
}.onSuccess {
Toast.makeText(
this, getString(R.string.import_success), Toast.LENGTH_SHORT
).show()
}.onFailure {
}.onFailure { exception ->
Log.e("IMPORT", "Import failed", exception)
when (exception) {
is JsonSyntaxException -> {
Toast.makeText(
this, getString(R.string.import_failure_json), Toast.LENGTH_SHORT
).show()
}
else -> {
Toast.makeText(
this, getString(R.string.import_failure), Toast.LENGTH_SHORT
).show()
}
}
Toast.makeText(
this, getString(R.string.import_failure), Toast.LENGTH_SHORT
).show()
}
}
}
@ -193,7 +179,6 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
sharedPrefs = PreferenceHelper.defaultPreference(this)
clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
gson = GsonBuilder().setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE).create()
migrateServerList()
@ -249,7 +234,7 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
R.id.export_settings_clipboard -> {
dnsServerViewModel.viewModelScope.launch {
val data = BackupUtils.export(dnsServerViewModel, sharedPrefs)
val jsonData = gson.toJson(data)
val jsonData = Json.encodeToString(data)
clipboard.setPrimaryClip(ClipData.newPlainText("", jsonData))
// Only show a toast for Android 12 and lower.
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) Toast.makeText(
@ -263,7 +248,7 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
val activityContext = this
dnsServerViewModel.viewModelScope.launch {
val data = BackupUtils.export(dnsServerViewModel, sharedPrefs)
val jsonData = gson.toJson(data)
val jsonData = Json.encodeToString(data)
ShareCompat.IntentBuilder(activityContext).setText(jsonData)
.setType("text/plain")
.startChooser()
@ -319,7 +304,7 @@ class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogLi
if (result.resultCode == RESULT_OK) {
val data: Intent? = result.data
data?.data?.also { uri ->
val jsonData = gson.toJson(BackupUtils.export(dnsServerViewModel, sharedPrefs))
val jsonData = Json.encodeToString(BackupUtils.export(dnsServerViewModel, sharedPrefs))
val contentResolver = applicationContext.contentResolver
runCatching {
contentResolver.openOutputStream(uri)?.use { outputStream ->

View file

@ -1,20 +1,26 @@
package ru.karasevm.privatednstoggle.util
import android.content.SharedPreferences
import com.google.gson.annotations.SerializedName
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import ru.karasevm.privatednstoggle.data.DnsServerViewModel
import ru.karasevm.privatednstoggle.model.DnsServer
import ru.karasevm.privatednstoggle.util.PreferenceHelper.AUTO_MODE
import ru.karasevm.privatednstoggle.util.PreferenceHelper.DNS_SERVERS
import ru.karasevm.privatednstoggle.util.PreferenceHelper.REQUIRE_UNLOCK
import ru.karasevm.privatednstoggle.util.PreferenceHelper.autoMode
import ru.karasevm.privatednstoggle.util.PreferenceHelper.requireUnlock
object BackupUtils {
@Serializable
data class Backup(
@SerializedName("dns_servers") val dnsServers: List<DnsServer>,
@SerializedName("auto_mode") val autoMode: Int?,
@SerializedName("require_unlock") val requireUnlock: Boolean?,
@SerialName("dns_servers") val dnsServers: List<DnsServer>,
@SerialName("auto_mode") val autoMode: Int?,
@SerialName("require_unlock") val requireUnlock: Boolean?,
)
@Serializable
data class LegacyBackup(
@SerialName("dns_servers") val dnsServers: String,
@SerialName("auto_mode") val autoMode: Int?,
@SerialName("require_unlock") val requireUnlock: Boolean?,
)
/**
@ -47,17 +53,16 @@ object BackupUtils {
/**
* Imports old server list
* @param map Deserialized backup
* @param legacyBackup Deserialized backup
* @param viewModel View model
* @param sharedPreferences Shared preferences
*/
fun importLegacy(
map: Map<String, Any>,
legacyBackup: LegacyBackup,
viewModel: DnsServerViewModel,
sharedPreferences: SharedPreferences
) {
map[DNS_SERVERS]?.let { servers ->
if (servers is String) {
legacyBackup.dnsServers.let { servers ->
val serverList = servers.split(",")
serverList.forEach { server ->
val parts = server.split(" : ")
@ -67,9 +72,8 @@ object BackupUtils {
viewModel.insert(DnsServer(0, server, ""))
}
}
}
}
sharedPreferences.autoMode = map[AUTO_MODE] as? Int ?: 0
sharedPreferences.requireUnlock = map[REQUIRE_UNLOCK] as? Boolean ?: false
sharedPreferences.autoMode = legacyBackup.autoMode?: 0
sharedPreferences.requireUnlock = legacyBackup.requireUnlock?: false
}
}

View file

@ -6,15 +6,16 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:8.5.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.10"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.20"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
plugins {
id 'org.jetbrains.kotlin.android' version '1.9.10' apply false
id "com.google.devtools.ksp" version "1.9.10-1.0.13" apply false
id 'org.jetbrains.kotlin.android' version '2.0.20' apply false
id 'org.jetbrains.kotlin.plugin.serialization' version '2.0.20'
id "com.google.devtools.ksp" version "2.0.20-1.0.25" apply false
}
tasks.register('clean', Delete) {