Update Kotlin, Serialization and Coroutines

* Kotlin [1.3.72] -> [1.4.10]
* Serialization [0.20.0] -> [1.0.0]
* Coroutines [1.3.4] -> [1.4.0-M1]

CP-994
This commit is contained in:
Davide Farella 2020-10-15 15:50:03 +02:00
parent c5ee8ecb66
commit 4e0d9d3dd5
13 changed files with 113 additions and 86 deletions

View File

@ -30,20 +30,19 @@ plugins {
id("me.proton.kotlin")
id("me.proton.publish-libraries")
id("me.proton.tests")
val kotlinVersion = "1.4.10" // Sep 09, 2020
kotlin("jvm") version kotlinVersion apply false
kotlin("plugin.serialization") version kotlinVersion apply false
}
buildscript {
repositories.google()
dependencies {
val kotlinVersion = "1.4.10" // Sep 09, 2020
val agpVersion = "4.2.0-alpha13"
val hiltVersion = "2.29.1-alpha" // Sep 10, 2020
classpath(kotlin("gradle-plugin", kotlinVersion))
classpath(kotlin("serialization", kotlinVersion))
classpath("org.jetbrains.dokka:dokka-gradle-plugin:$kotlinVersion")
classpath("com.android.tools.build:gradle:$agpVersion")
classpath("com.google.dagger:hilt-android-gradle-plugin:$hiltVersion")
}
@ -55,7 +54,8 @@ kotlinCompilerArgs(
// Enables inline classes
"-XXLanguage:+InlineClasses",
// Enables experimental Coroutines from coroutines-test artifact, like `runBlockingTest`
"-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi"
"-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-Xopt-in=kotlinx.serialization.ExperimentalSerializationApi"
)
// setupDokka()

View File

@ -40,7 +40,7 @@ repositories {
}
dependencies {
val easyGradle = "2.6" // Oct 15, 2020
val easyGradle = "2.7" // Oct 15, 2020
val agpVersion = "4.2.0-alpha13" // Oct 01, 2020
implementation(gradleApi())

View File

@ -22,14 +22,12 @@ import studio.forface.easygradle.dsl.android.*
internal fun initVersions() {
// region Kotlin
`kotlin version` = "1.3.72" // Released: Apr 14, 2020
`coroutines version` = "1.3.5" // Released: Mar 17, 2020
`serialization version` = "0.20.0" // Released: Mar 04, 2020
`kotlin version` = "1.4.10" // Released: Sep 09, 2020
`coroutines version` = "1.4.0-M1" // Released: Oct 13, 2020
`serialization version` = "1.0.0" // Released: Oct 08, 2020
// endregion
// region Android
`android-gradle-plugin version` = "4.0.0-beta03" // Released: Mar 19, 2020
`activity version` = "1.2.0-alpha03" // Released: Apr 05, 2020
`android-annotation version` = "1.1.0" // Released: Jun 05, 2019
`appcompat version` = "1.1.0" // Released: Sep 06, 2019
@ -44,7 +42,7 @@ internal fun initVersions() {
`android-work version` = "2.2.0" // Released: Aug 16, 2019
`android-test version` = "1.2.0" // Released: May 31, 2019
`robolectric version` = "4.3.1" // Released: Oct 11, 2019
`robolectric version` = "4.4" // Released: Aug 24, 2020
// endregion
// region Others
@ -54,15 +52,12 @@ internal fun initVersions() {
`hilt-androidx version` = "1.0.0-alpha01" // Released: Jun 12, 2020
`mockK version` = "1.10.0" // Released: Apr 19, 2020
`retrofit version` = "2.9.0" // Released: May 20, 2020
`retrofit-kotlin-serialization version` = "0.4.0" // Released: Apr 12, 2019
`retrofit-kotlin-serialization version` = "0.8.0" // Released: Oct 09, 2020
`timber version` = "4.7.1" // Released:
`viewStateStore version` = "1.4-beta-4" // Released: Oct 03, 2019
// endregion
}
// region Kotlin
// endregion
// region Android
const val `android-tools version` = "26.6.3" // Updated: Apr 17, 2020
const val `androidUi version` = "0.1.0-dev08" // Released: Apr 03, 2020
@ -74,11 +69,3 @@ const val `miniDsn version` = "1.0.0" // Released: Jul 18,
const val `okHttp version` = "4.8.0" // Released: Jul 11, 2020
const val `trustKit version` = "1.1.3" // Released: Apr 30, 2020
// endregion
// region Gradle
const val `easyGradle version` = "1.5-beta-10" // Released: Jun 27, 2020
// endregion
// region Plugins
const val `dokka-plugin version` = "0.10.1" // Released: Feb 04, 2020
// endregion

View File

@ -17,12 +17,11 @@
*/
package me.proton.core.network.data.protonApi
import kotlinx.serialization.Decoder
import kotlinx.serialization.Encoder
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialDescriptor
import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.JsonInput
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
/**
* Kotlin Boolean serializer that can deserialize Boolean from both bool and int json values
@ -31,20 +30,17 @@ import kotlinx.serialization.json.JsonInput
*/
class IntToBoolSerializer : KSerializer<Boolean> {
override fun deserialize(decoder: Decoder): Boolean {
val json = (decoder as? JsonInput)?.decodeJson()
val bool = json?.primitive?.booleanOrNull
if (bool != null)
return bool
val int = json?.primitive?.intOrNull
?: throw SerializationException("boolean or int required")
return int != 0
}
override val descriptor =
PrimitiveSerialDescriptor(IntToBoolSerializer::class.qualifiedName!!, PrimitiveKind.STRING)
override val descriptor: SerialDescriptor
get() = SerialDescriptor(IntToBoolSerializer::class.qualifiedName!!)
override fun deserialize(decoder: Decoder): Boolean =
decoder.decodeString().toBooleanFromInt()
override fun serialize(encoder: Encoder, value: Boolean) {
encoder.encodeBoolean(value)
}
private fun String.toBooleanFromInt() =
toBoolean() || toIntOrNull()?.let { it > 0 } ?: false
}

View File

@ -240,7 +240,7 @@ internal class ProtonApiBackendTests {
}
@Test
fun `test deserialize bool from int`() = runBlocking {
fun `can deserialize false from 0`() = runBlocking {
webServer.prepareResponse(
HttpURLConnection.HTTP_OK,
"""{ "Number": 5, "String": "foo", Bool: 0 }"""
@ -250,6 +250,26 @@ internal class ProtonApiBackendTests {
assertEquals(false, result.valueOrNull?.bool)
}
@Test
fun `can deserialize true from 1`() = runBlocking {
webServer.prepareResponse(
HttpURLConnection.HTTP_OK,
"""{ "Number": 5, "String": "foo", Bool: 1 }""")
val result = backend(ApiManager.Call(0) { test() })
assertEquals(true, result.valueOrNull?.bool)
}
@Test
fun `can deserialize true from 5`() = runBlocking {
webServer.prepareResponse(
HttpURLConnection.HTTP_OK,
"""{ "Number": 5, "String": "foo", Bool: 5 }""")
val result = backend(ApiManager.Call(0) { test() })
assertEquals(true, result.valueOrNull?.bool)
}
@Test
fun `test pinning error`() = runBlocking {
val badBackend = createBackend {

View File

@ -37,7 +37,6 @@ pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
maven("https://dl.bintray.com/kotlin/kotlin-eap")
maven("https://plugins.gradle.org/m2/")
}
}

View File

@ -1,12 +1,30 @@
/*
* Copyright (c) 2020 Proton Technologies AG
* This file is part of Proton Technologies AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonCore is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.util.android.sharedpreferences
import me.proton.core.test.android.mocks.mockSharedPreferences
import me.proton.core.util.android.sharedpreferences.internal.SerializableTestChild
import me.proton.core.util.android.sharedpreferences.internal.SerializableTestClass
import me.proton.core.util.kotlin.startsWith
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFails
import me.proton.core.util.kotlin.startsWith
/**
* Test suite for serializable items within SharedPreferences
@ -41,14 +59,15 @@ internal class SerializableTest {
fun `proper message is displayed if given class is not serializable`() {
// GIVEN
val ns =
NonSerializableTestClass()
val ns = NonSerializableTestClass()
// WHEN
val block = { p["key2"] = ns }
// THEN
val message = assertFails(block).localizedMessage
assert(message startsWith "Can't locate argument-less serializer for class NonSerializableTestClass.")
assert(message startsWith "Serializer for class 'NonSerializableTestClass' is not found.") {
message
}
}
}

View File

@ -26,7 +26,6 @@ import androidx.work.ListenableWorker
import androidx.work.WorkInfo
import androidx.work.workDataOf
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.ImplicitReflectionSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationStrategy
import me.proton.core.util.kotlin.deserializeOrNull
@ -45,8 +44,7 @@ internal const val SERIALIZED_DATA_KEY = "serialized_data_key"
* @return [T] deserialized from receiver [WorkInfo.mOutputData]
*
* @param T must be a class annotate with [Serializable]
* @param deserializer optional [DeserializationStrategy] of [T], if no value is passed, the
* [ImplicitReflectionSerializer] will be used
* @param deserializer optional [DeserializationStrategy] of [T]
*
* @throws KotlinNullPointerException if no serialized data in present in [Data]
*/
@ -58,8 +56,7 @@ inline fun <reified T : Any> WorkInfo.outputData(
* @return [T] deserialized from receiver [Data]
*
* @param T must be a class annotate with [Serializable]
* @param deserializer optional [DeserializationStrategy] of [T], if no value is passed, the
* [ImplicitReflectionSerializer] will be used
* @param deserializer optional [DeserializationStrategy] of [T]
*
* @throws KotlinNullPointerException if no serialized data in present in [Data]
*/
@ -78,8 +75,7 @@ inline fun <reified T : Any> Data.deserialize(
* @return [Data] created by serializing receiver [T]
*
* @param T must be a class annotate with [Serializable]
* @param serializer optional [SerializationStrategy] of [T], if no value is passed, the
* [ImplicitReflectionSerializer] will be used
* @param serializer optional [SerializationStrategy] of [T]
*/
inline fun <reified T : Any> T.toWorkData(
serializer: SerializationStrategy<T>? = null
@ -89,8 +85,7 @@ inline fun <reified T : Any> T.toWorkData(
* @return [T] deserialized from [ListenableWorker.getInputData]
*
* @param T must be a class annotate with [Serializable]
* @param deserializer optional [DeserializationStrategy] of [T], if no value is passed, the
* [ImplicitReflectionSerializer] will be used
* @param deserializer optional [DeserializationStrategy] of [T]
*
* @throws KotlinNullPointerException if no serialized data in present in [Data]
*/

View File

@ -52,5 +52,5 @@ repositories {
dependencies {
implementation(gradleApi())
implementation("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.14.1")
implementation("studio.forface.easygradle:dsl:2.1")
implementation("studio.forface.easygradle:dsl:2.7")
}

View File

@ -18,7 +18,7 @@
plugins {
val kotlinVersion = "1.4.10"
val easyGradle = "0.1"
val easyGradle = "2.7"
kotlin("jvm") version kotlinVersion
id("studio.forface.easygradle") version easyGradle

View File

@ -72,5 +72,5 @@ dependencies {
implementation(gradleApi())
implementation("org.jetbrains.dokka:dokka-gradle-plugin:1.4.10")
implementation("com.gradle.publish:plugin-publish-plugin:0.12.0")
implementation("studio.forface.easygradle:dsl:2.1")
implementation("studio.forface.easygradle:dsl:2.7")
}

View File

@ -20,7 +20,6 @@ package me.proton.core.util.kotlin
import kotlinx.serialization.StringFormat
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
/**
* Configuration for Proton Core library
@ -29,9 +28,8 @@ import kotlinx.serialization.json.JsonConfiguration
object ProtonCoreConfig : Invokable {
/** Default [StringFormat] for serialize and deserialize JSON strings */
var defaultJsonStringFormat: StringFormat = Json(
JsonConfiguration.Stable.copy(
ignoreUnknownKeys = true, isLenient = true
)
)
var defaultJsonStringFormat: StringFormat = Json {
ignoreUnknownKeys = true
isLenient = true
}
}

View File

@ -1,25 +1,35 @@
@file:Suppress(
"EXPERIMENTAL_API_USAGE" // Explicit serializer
)
@file:OptIn(ImplicitReflectionSerializer::class)
/*
* Copyright (c) 2020 Proton Technologies AG
* This file is part of Proton Technologies AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonCore is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.util.kotlin
import kotlinx.serialization.Decoder
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.Encoder
import kotlinx.serialization.ImplicitReflectionSerializer
import kotlinx.serialization.KSerializer
import kotlinx.serialization.PrimitiveDescriptor
import kotlinx.serialization.PrimitiveKind
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.Serializer
import kotlinx.serialization.parse
import kotlinx.serialization.parseList
import kotlinx.serialization.parseMap
import kotlinx.serialization.stringify
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.encodeToString
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import me.proton.core.util.kotlin.ProtonCoreConfig.defaultJsonStringFormat
/*
@ -49,7 +59,10 @@ annotation class NeedSerializable
@NeedSerializable
inline fun <reified T : Any> String.deserialize(
deserializer: DeserializationStrategy<T>? = null
): T = deserializer?.let { Serializer.parse(deserializer, this) } ?: Serializer.parse(this)
): T =
deserializer
?.let { Serializer.decodeFromString(deserializer, this) }
?: Serializer.decodeFromString(this)
/**
* @return [T] object from the receiver [String] or null if receiver can't be deserialized to [T].
@ -71,7 +84,7 @@ inline fun <reified T : Any> String.deserializeOrNull(
* This uses reflection: TODO improve for avoid it
*/
@NeedSerializable
inline fun <reified T : Any> String.deserializeList(): List<T> = Serializer.parseList(this)
inline fun <reified T : Any> String.deserializeList(): List<T> = Serializer.decodeFromString(this)
/**
* @return [Map] of [T], [V] object from the receiver [String]
@ -79,7 +92,7 @@ inline fun <reified T : Any> String.deserializeList(): List<T> = Serializer.pars
*/
@NeedSerializable
inline fun <reified T : Any, reified V : Any> String.deserializeMap(): Map<T, V> =
Serializer.parseMap(this)
Serializer.decodeFromString(this)
/**
@ -91,21 +104,21 @@ inline fun <reified T : Any, reified V : Any> String.deserializeMap(): Map<T, V>
@NeedSerializable
inline fun <reified T : Any> T.serialize(
serializer: SerializationStrategy<T>? = null
) = serializer?.let { Serializer.stringify(serializer, this) } ?: Serializer.stringify(this)
) = serializer?.let { Serializer.encodeToString(serializer, this) } ?: Serializer.encodeToString(this)
/**
* @return [String] from the receiver [List] of [T] object
* This uses reflection: TODO improve for avoid it
*/
@NeedSerializable
inline fun <reified T : Any> List<T>.serialize() = Serializer.stringify(this)
inline fun <reified T : Any> List<T>.serialize() = Serializer.encodeToString(this)
/**
* @return [String] from the receiver [Map] of [T] and [V] object
* This uses reflection: TODO improve for avoid it
*/
@NeedSerializable
inline fun <reified T : Any, reified V : Any> Map<T, V>.serialize() = Serializer.stringify(this)
inline fun <reified T : Any, reified V : Any> Map<T, V>.serialize() = Serializer.encodeToString(this)
@PublishedApi
@ -126,7 +139,7 @@ internal sealed class SerializableTestSealedClass(val value: Int) {
class Two(value: Int) : SerializableTestSealedClass(value)
companion object {
fun build(value: Int) : SerializableTestSealedClass {
fun build(value: Int): SerializableTestSealedClass {
return when (value) {
1 -> One(value)
2 -> Two(value)
@ -137,7 +150,7 @@ internal sealed class SerializableTestSealedClass(val value: Int) {
@Suppress("ClassName", "ClassNaming") // Test class
@Serializer(forClass = SerializableTestSealedClass::class)
object _Serializer : KSerializer<SerializableTestSealedClass> {
override val descriptor = PrimitiveDescriptor("TestCustomSerializer", PrimitiveKind.STRING)
override val descriptor = PrimitiveSerialDescriptor("TestCustomSerializer", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: SerializableTestSealedClass) {
val raw = Raw(value.value)