From 4b06463124467e2a2c1aea653599b30857b5ca94 Mon Sep 17 00:00:00 2001 From: Yo Date: Thu, 22 Feb 2024 08:44:20 +0100 Subject: [PATCH] feat: support for custom serializer (#179) * dev: support custom serializer * chores: revert minSdk to 21 * chores: fix version to 2.0.0 * refacto: Making change as non-breaking chores: downgrade version to 1.8.0 as change is no longer breaking + adding readme * chores: adding notice to implement kotlinx for default EmojiInitializer * chores: spotlessApply * chores: Adding example for Jackson and Gson + revert version.properties * chores: rename AEmojiInitializer to AbstractEmojiInitializer * doc: Doc for KotlinxDeserializer.kt --------- Co-authored-by: Yoobi <> --- README.md | 56 +++++++++++++ app/build.gradle.kts | 4 + app/src/main/AndroidManifest.xml | 14 ++++ .../emojifysample/CustomEmojiInitializer.kt | 45 ++++++++++ .../plugin/components/AndroidConfiguration.kt | 2 +- .../deserializer/KotlinxDeserializer.kt | 36 ++++++++ .../initializer/AbstractEmojiInitializer.kt | 82 +++++++++++++++++++ .../emojify/initializer/EmojiInitializer.kt | 70 ++-------------- .../emojify/initializer/IEmojiDeserializer.kt | 31 +++++++ gradle/libs.versions.toml | 6 ++ gradle/version.properties | 2 +- 11 files changed, 285 insertions(+), 63 deletions(-) create mode 100644 app/src/main/java/io/wax911/emojifysample/CustomEmojiInitializer.kt create mode 100644 emojify/src/main/kotlin/io/wax911/emojify/deserializer/KotlinxDeserializer.kt create mode 100644 emojify/src/main/kotlin/io/wax911/emojify/initializer/AbstractEmojiInitializer.kt create mode 100644 emojify/src/main/kotlin/io/wax911/emojify/initializer/IEmojiDeserializer.kt diff --git a/README.md b/README.md index 01646b2..1abf004 100644 --- a/README.md +++ b/README.md @@ -177,6 +177,8 @@ dependencies { Don't know how to do that?? Take a look at the [application class example](./app/src/main/java/io/wax911/emojifysample/App.kt) +**In order for EmojiInitilizer to work you need to add in your build.gradle `implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2"` + ```kotlin class App : Application() { @@ -193,6 +195,60 @@ class App : Application() { } ``` +### Optional - Custom Serializer + +`EmojiInitializer` is using kotlinx.serialization as default serializer. If you wish to use another serializer + +#### Step 1. Create Custom EmojiInitializer and implement IEmojiDeserializer (moshi for this example) +```kotlin +class CustomEmojiInitializer: AbstractEmojiInitializer() { + class MoshiDeserializer: IEmojiDeserializer { + private val moshi = Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build() + + override fun decodeFromStream(inputStream: InputStream): List { + val myType = Types.newParameterizedType(List::class.java, Emoji::class.java) + return moshi.adapter>(myType).fromJson(inputStream.source().buffer()) ?: listOf() + } + } + + override val serializer: IEmojiDeserializer = MoshiDeserializer() +} +``` + +#### Step 2. Modify your application class +```kotlin +class App : Application() { + + /** + * Application scope bound emojiManager, you could keep a reference to this object in a + * dependency injector framework like as a singleton in `Hilt`, `Dagger` or `Koin` + */ + internal val emojiManager: EmojiManager by lazy { + // should already be initialized if we haven't disabled initialization in manifest + // see: https://developer.android.com/topic/libraries/app-startup#disable-individual + AppInitializer.getInstance(this) + .initializeComponent(CustomEmojiInitializer::class.java) + } +} +``` + +#### Step 3. Modify AndroidManifest.xml of your application +```xml + + + + +``` + ## Screenshots diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2705116..62937d9 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -10,6 +10,10 @@ dependencies { implementation(libs.google.android.material) implementation(libs.androidx.constraintlayout) + implementation(libs.moshi.kotlin) + implementation(libs.gson) + implementation(libs.jackson.databind) + implementation(libs.jetbrains.kotlinx.coroutines.android) implementation(libs.jetbrains.kotlinx.coroutines.core) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ebadac4..c220bbc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -18,6 +18,20 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/java/io/wax911/emojifysample/CustomEmojiInitializer.kt b/app/src/main/java/io/wax911/emojifysample/CustomEmojiInitializer.kt new file mode 100644 index 0000000..3bc67ea --- /dev/null +++ b/app/src/main/java/io/wax911/emojifysample/CustomEmojiInitializer.kt @@ -0,0 +1,45 @@ +package io.wax911.emojifysample + +import com.fasterxml.jackson.databind.ObjectMapper +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.squareup.moshi.Moshi +import com.squareup.moshi.Types +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import io.wax911.emojify.initializer.AbstractEmojiInitializer +import io.wax911.emojify.initializer.IEmojiDeserializer +import io.wax911.emojify.model.Emoji +import okio.buffer +import okio.source +import java.io.InputStream + +class CustomEmojiInitializer: AbstractEmojiInitializer() { + class MoshiDeserializer: IEmojiDeserializer { + private val moshi = Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build() + + override fun decodeFromStream(inputStream: InputStream): List { + val myType = Types.newParameterizedType(List::class.java, Emoji::class.java) + return moshi.adapter>(myType).fromJson(inputStream.source().buffer()) ?: listOf() + } + } + + class JacksonDeserializer: IEmojiDeserializer { + private val jackson = ObjectMapper() + + override fun decodeFromStream(inputStream: InputStream): List { + val myType = jackson.typeFactory.constructCollectionType(List::class.java, Emoji::class.java) + return jackson.readValue(inputStream, myType) + } + } + + class GsonDeserializer: IEmojiDeserializer { + private val gson = Gson() + + override fun decodeFromStream(inputStream: InputStream): List { + val myType = TypeToken.getParameterized(List::class.java, Emoji::class.java).type + return gson.fromJson(inputStream.reader(), myType) + } + } + + override val serializer: IEmojiDeserializer = MoshiDeserializer() +} diff --git a/buildSrc/src/main/java/io/wax911/emoji/buildSrc/plugin/components/AndroidConfiguration.kt b/buildSrc/src/main/java/io/wax911/emoji/buildSrc/plugin/components/AndroidConfiguration.kt index 3ff10d9..695f742 100644 --- a/buildSrc/src/main/java/io/wax911/emoji/buildSrc/plugin/components/AndroidConfiguration.kt +++ b/buildSrc/src/main/java/io/wax911/emoji/buildSrc/plugin/components/AndroidConfiguration.kt @@ -44,7 +44,7 @@ private fun DefaultConfig.applyAdditionalConfiguration(project: Project) { internal fun Project.configureAndroid(): Unit = baseExtension().run { compileSdkVersion(34) defaultConfig { - minSdk = 23 + minSdk = 21 targetSdk = 34 versionCode = props[PropertyTypes.CODE].toInt() versionName = props[PropertyTypes.VERSION] diff --git a/emojify/src/main/kotlin/io/wax911/emojify/deserializer/KotlinxDeserializer.kt b/emojify/src/main/kotlin/io/wax911/emojify/deserializer/KotlinxDeserializer.kt new file mode 100644 index 0000000..f79d13b --- /dev/null +++ b/emojify/src/main/kotlin/io/wax911/emojify/deserializer/KotlinxDeserializer.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2024 AniTrend + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.wax911.emojify.deserializer + +import io.wax911.emojify.initializer.IEmojiDeserializer +import io.wax911.emojify.model.Emoji +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.decodeFromStream +import java.io.InputStream + +/** + * Default implementation for kotlinx-serialization + */ +class KotlinxDeserializer : IEmojiDeserializer { + private val json = Json { isLenient = true } + + override fun decodeFromStream(inputStream: InputStream): List { + val deserializer = ListSerializer(Emoji.serializer()) + return json.decodeFromStream(deserializer, inputStream) + } +} diff --git a/emojify/src/main/kotlin/io/wax911/emojify/initializer/AbstractEmojiInitializer.kt b/emojify/src/main/kotlin/io/wax911/emojify/initializer/AbstractEmojiInitializer.kt new file mode 100644 index 0000000..9bf132b --- /dev/null +++ b/emojify/src/main/kotlin/io/wax911/emojify/initializer/AbstractEmojiInitializer.kt @@ -0,0 +1,82 @@ +/* + * Copyright 2021 AniTrend + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.wax911.emojify.initializer + +import android.content.Context +import android.content.res.AssetManager +import androidx.startup.Initializer +import io.wax911.emojify.EmojiManager +import io.wax911.emojify.model.Emoji +import kotlinx.serialization.SerializationException +import java.io.IOException + +/** + * Abstract the logic of Initializer so that + */ +abstract class AbstractEmojiInitializer : Initializer { + abstract val serializer: IEmojiDeserializer + + /** + * Initializes emoji objects from an asset file in the library directory + * + * @param assetManager provide an assert manager + * @param path location where emoji data can be found + * + * @throws IOException when the provided [assetManager] cannot open [path] + * @throws SerializationException when an error occurs during deserialization + */ + @Throws(IOException::class, SerializationException::class) + fun initEmojiData( + assetManager: AssetManager, + path: String = DEFAULT_PATH, + ): List { + return assetManager.open(path).use { inputStream -> + serializer.decodeFromStream(inputStream) + } + } + + /** + * Initializes and a component given the application [Context] + * + * @param context The application context. + */ + override fun create(context: Context): EmojiManager { + val emojiManagerDefault = EmojiManager(emptyList()) + val result = + runCatching { + val emojis = initEmojiData(context.assets) + EmojiManager(emojis) + }.onFailure { it.printStackTrace() } + return result.getOrNull() ?: emojiManagerDefault + } + + /** + * @return A list of dependencies that this [Initializer] depends on. This is + * used to determine initialization order of [Initializer]s. + * + * For e.g. if a [Initializer] `B` defines another + * [Initializer] `A` as its dependency, then `A` gets initialized before `B`. + */ + override fun dependencies() = emptyList>>() + + companion object { + /** + * Default location with assets where emojis can be found + */ + internal const val DEFAULT_PATH = "emoticons/emoji.json" + } +} diff --git a/emojify/src/main/kotlin/io/wax911/emojify/initializer/EmojiInitializer.kt b/emojify/src/main/kotlin/io/wax911/emojify/initializer/EmojiInitializer.kt index 88c0210..4ebb110 100644 --- a/emojify/src/main/kotlin/io/wax911/emojify/initializer/EmojiInitializer.kt +++ b/emojify/src/main/kotlin/io/wax911/emojify/initializer/EmojiInitializer.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 AniTrend + * Copyright 2024 AniTrend * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,67 +16,15 @@ package io.wax911.emojify.initializer -import android.content.Context -import android.content.res.AssetManager -import androidx.startup.Initializer -import io.wax911.emojify.EmojiManager -import io.wax911.emojify.model.Emoji -import kotlinx.serialization.SerializationException -import kotlinx.serialization.builtins.ListSerializer -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.decodeFromStream -import java.io.IOException - -class EmojiInitializer : Initializer { - /** - * Initializes emoji objects from an asset file in the library directory - * - * @param assetManager provide an assert manager - * @param path location where emoji data can be found - * - * @throws IOException when the provided [assetManager] cannot open [path] - * @throws SerializationException when an error occurs during deserialization - */ - @Throws(IOException::class, SerializationException::class) - fun initEmojiData( - assetManager: AssetManager, - path: String = DEFAULT_PATH, - ): List { - assetManager.open(path).use { inputStream -> - val json = Json { isLenient = true } - val deserializer = ListSerializer(Emoji.serializer()) - return json.decodeFromStream(deserializer, inputStream) - } - } - - /** - * Initializes and a component given the application [Context] - * - * @param context The application context. - */ - override fun create(context: Context): EmojiManager { - val emojiManagerDefault = EmojiManager(emptyList()) - val result = - runCatching { - val emojis = initEmojiData(context.assets) - EmojiManager(emojis) - }.onFailure { it.printStackTrace() } - return result.getOrNull() ?: emojiManagerDefault - } +import io.wax911.emojify.deserializer.KotlinxDeserializer +/** + * Default Implementation of AbstractEmojiInitializer + * **Note: You need to have kotlinx.serialization gradle implementation in your project to work** + */ +class EmojiInitializer : AbstractEmojiInitializer() { /** - * @return A list of dependencies that this [Initializer] depends on. This is - * used to determine initialization order of [Initializer]s. - * - * For e.g. if a [Initializer] `B` defines another - * [Initializer] `A` as its dependency, then `A` gets initialized before `B`. + * Kotlinx implementation is needed in your project for this to work */ - override fun dependencies() = emptyList>>() - - companion object { - /** - * Default location with assets where emojis can be found - */ - internal const val DEFAULT_PATH = "emoticons/emoji.json" - } + override val serializer: IEmojiDeserializer = KotlinxDeserializer() } diff --git a/emojify/src/main/kotlin/io/wax911/emojify/initializer/IEmojiDeserializer.kt b/emojify/src/main/kotlin/io/wax911/emojify/initializer/IEmojiDeserializer.kt new file mode 100644 index 0000000..e2abe71 --- /dev/null +++ b/emojify/src/main/kotlin/io/wax911/emojify/initializer/IEmojiDeserializer.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2024 AniTrend + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.wax911.emojify.initializer + +import io.wax911.emojify.model.Emoji +import java.io.InputStream + +/** + * Interface to implement if user wants to use a custom deserializer. + * For more information on the necessary steps refer to README.md + */ +interface IEmojiDeserializer { + /** + * Decodes the given [InputStream] to an object of type List<[Emoji]> + */ + fun decodeFromStream(inputStream: InputStream): List +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7c6ef6d..a99dd72 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,4 +1,6 @@ [versions] +gson = "2.10.1" +jackson-databind = "2.16.0" ktlint = "1.0.1" gradle-plugin = "8.2.2" @@ -21,6 +23,7 @@ jetbrains-kotlinx-datetime = "0.5.0" jetbrains-kotlinx-serialization = "1.6.2" mockk = "1.13.9" +moshi-kotlin = "1.15.0" [plugins] @@ -28,6 +31,9 @@ gradle-versions = { id = "com.github.ben-manes.versions", version = "0.51.0" } [libraries] +gson = { module = "com.google.code.gson:gson", version.ref = "gson" } +jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson-databind" } +moshi-kotlin = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi-kotlin" } timber = { module = "com.jakewharton.timber:timber", version = "5.0.1" } junit = { module = "junit:junit", version = "4.13.2" } diff --git a/gradle/version.properties b/gradle/version.properties index e337fd5..91d9f5a 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1,2 +1,2 @@ version=1.7.1 -code=1007001000 \ No newline at end of file +code=1007001000