diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml index 2956c3f..767547e 100644 --- a/.idea/deploymentTargetDropDown.xml +++ b/.idea/deploymentTargetDropDown.xml @@ -12,6 +12,6 @@ - + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 8fe89da..28020af 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,6 +4,7 @@ plugins { id 'kotlin-kapt' id 'com.google.dagger.hilt.android' id 'maven-publish' + id 'kotlinx-serialization' } android { @@ -90,4 +91,6 @@ dependencies { kapt "com.google.dagger:hilt-compiler:2.44.2" implementation 'androidx.lifecycle:lifecycle-runtime-compose:2.6.0-alpha01' + + implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1' } \ No newline at end of file diff --git a/app/src/main/java/com/jeremy/thunder/di/SocketModule.kt b/app/src/main/java/com/jeremy/thunder/di/SocketModule.kt index 0e41a61..a997fd1 100644 --- a/app/src/main/java/com/jeremy/thunder/di/SocketModule.kt +++ b/app/src/main/java/com/jeremy/thunder/di/SocketModule.kt @@ -1,7 +1,7 @@ package com.jeremy.thunder.di import android.content.Context -import com.jeremy.thunder.Thunder +import com.jeremy.thunder.event.converter.ConverterType import com.jeremy.thunder.makeWebSocketCore import com.jeremy.thunder.socket.SocketService import com.jeremy.thunder.thunder @@ -37,6 +37,7 @@ internal object SocketModule { return thunder { webSocketCore(okHttpClient.makeWebSocketCore("wss://fstream.binance.com/stream")) setApplicationContext(context) + setConverterType(ConverterType.Serialization) }.create() } } \ No newline at end of file diff --git a/app/src/main/java/com/jeremy/thunder/socket/model/AllMarketTickerResponse.kt b/app/src/main/java/com/jeremy/thunder/socket/model/AllMarketTickerResponse.kt index fbcc565..c464bfc 100644 --- a/app/src/main/java/com/jeremy/thunder/socket/model/AllMarketTickerResponse.kt +++ b/app/src/main/java/com/jeremy/thunder/socket/model/AllMarketTickerResponse.kt @@ -1,13 +1,17 @@ package com.jeremy.thunder.socket.model import com.google.gson.annotations.SerializedName +import kotlinx.serialization.Serializable +@Serializable data class AllMarketTickerResponse( - @SerializedName("stream") + //@SerializedName("stream") val stream: String = "", - @SerializedName("data") - val data: List = emptyList() + //@SerializedName("data") + val data: List = emptyList(), + + val nickName: String = "default" ) /* @@ -38,25 +42,28 @@ data class AllMarketTickerResponse( * * */ +@Serializable data class AllMarketTickerResponseItem( - @SerializedName("E") + //@SerializedName("E") val E: Long, //event time - @SerializedName("P") + val e: String, + + //@SerializedName("P") val P: String, // price change percent - @SerializedName("c") + //@SerializedName("c") val c: String, // last Price - @SerializedName("h") + //@SerializedName("h") val h: String, // high price - @SerializedName("l") + //@SerializedName("l") val l: String, // low price - @SerializedName("o") + //@SerializedName("o") val o: String, // open price - @SerializedName("s") +// @SerializedName("s") val s: String, // symbol ) diff --git a/build.gradle b/build.gradle index c0b7e96..14e4932 100644 --- a/build.gradle +++ b/build.gradle @@ -10,6 +10,8 @@ plugins { id 'org.jetbrains.kotlin.android' version '1.8.10' apply false id 'com.android.library' version '7.4.2' apply false id 'com.google.dagger.hilt.android' version '2.44.2' apply false + id 'org.jetbrains.kotlin.jvm' version '1.8.10' apply false + id 'org.jetbrains.kotlin.plugin.serialization' version '1.8.10' apply false } task clean(type: Delete) { diff --git a/thunder/build.gradle b/thunder/build.gradle index 00f8d45..51ac17a 100644 --- a/thunder/build.gradle +++ b/thunder/build.gradle @@ -58,4 +58,6 @@ dependencies { implementation 'com.squareup.okhttp3:logging-interceptor:4.9.2' implementation 'com.google.code.gson:gson:2.9.0' + + implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1' } \ No newline at end of file diff --git a/thunder/src/main/java/com/jeremy/thunder/Thunder.kt b/thunder/src/main/java/com/jeremy/thunder/Thunder.kt index 6bb2036..d69bc3d 100644 --- a/thunder/src/main/java/com/jeremy/thunder/Thunder.kt +++ b/thunder/src/main/java/com/jeremy/thunder/Thunder.kt @@ -8,6 +8,7 @@ import com.jeremy.thunder.coroutine.CoroutineScope.scope import com.jeremy.thunder.event.EventProcessor import com.jeremy.thunder.event.WebSocketEvent import com.jeremy.thunder.event.WebSocketEventProcessor +import com.jeremy.thunder.event.converter.ConverterType import com.jeremy.thunder.internal.ServiceExecutor import com.jeremy.thunder.internal.ThunderProvider import com.jeremy.thunder.internal.ThunderStateManager @@ -57,6 +58,7 @@ class Thunder private constructor( private var thunderStateManager: ThunderStateManager? = null private var context: Context? = null private val appConnectionProvider by lazy { AppConnectionProvider() } + private var converterType: ConverterType = ConverterType.Serialization fun webSocketCore(core: WebSocket.Factory): Builder = apply { this.webSocketCore = core } @@ -65,6 +67,10 @@ class Thunder private constructor( (this.context as Application).registerActivityLifecycleCallbacks(appConnectionProvider) } + fun setConverterType(type: ConverterType) = apply { + converterType = type + } + private fun createThunderStateManager(): ThunderStateManager { thunderStateManager = ThunderStateManager.Factory( connectionListener = appConnectionProvider, @@ -101,6 +107,7 @@ class Thunder private constructor( private fun createServiceExecutor(): ServiceExecutor { return ServiceExecutor.Factory( thunderProvider = createThunderProvider(), + converterType = converterType, scope = scope ).create() } diff --git a/thunder/src/main/java/com/jeremy/thunder/event/EventMapper.kt b/thunder/src/main/java/com/jeremy/thunder/event/EventMapper.kt index ddf1ba2..1c4305f 100644 --- a/thunder/src/main/java/com/jeremy/thunder/event/EventMapper.kt +++ b/thunder/src/main/java/com/jeremy/thunder/event/EventMapper.kt @@ -1,5 +1,6 @@ package com.jeremy.thunder.event +import com.jeremy.thunder.event.converter.Converter import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map diff --git a/thunder/src/main/java/com/jeremy/thunder/event/SocketEventKeyStore.kt b/thunder/src/main/java/com/jeremy/thunder/event/SocketEventKeyStore.kt index e48bd56..00728b4 100644 --- a/thunder/src/main/java/com/jeremy/thunder/event/SocketEventKeyStore.kt +++ b/thunder/src/main/java/com/jeremy/thunder/event/SocketEventKeyStore.kt @@ -1,5 +1,6 @@ package com.jeremy.thunder.event +import com.jeremy.thunder.event.converter.Converter import java.lang.reflect.Type /** diff --git a/thunder/src/main/java/com/jeremy/thunder/event/converter/Converter.kt b/thunder/src/main/java/com/jeremy/thunder/event/converter/Converter.kt new file mode 100644 index 0000000..61ddf2b --- /dev/null +++ b/thunder/src/main/java/com/jeremy/thunder/event/converter/Converter.kt @@ -0,0 +1,5 @@ +package com.jeremy.thunder.event.converter + +interface Converter { + fun convert(data: String): T +} \ No newline at end of file diff --git a/thunder/src/main/java/com/jeremy/thunder/event/converter/ConverterType.kt b/thunder/src/main/java/com/jeremy/thunder/event/converter/ConverterType.kt new file mode 100644 index 0000000..6b4b5bd --- /dev/null +++ b/thunder/src/main/java/com/jeremy/thunder/event/converter/ConverterType.kt @@ -0,0 +1,7 @@ +package com.jeremy.thunder.event.converter + +sealed class ConverterType { + object Gson: ConverterType() + + object Serialization: ConverterType() +} \ No newline at end of file diff --git a/thunder/src/main/java/com/jeremy/thunder/event/Converter.kt b/thunder/src/main/java/com/jeremy/thunder/event/converter/GsonConvertAdapter.kt similarity index 68% rename from thunder/src/main/java/com/jeremy/thunder/event/Converter.kt rename to thunder/src/main/java/com/jeremy/thunder/event/converter/GsonConvertAdapter.kt index f75fce0..0d2bfda 100644 --- a/thunder/src/main/java/com/jeremy/thunder/event/Converter.kt +++ b/thunder/src/main/java/com/jeremy/thunder/event/converter/GsonConvertAdapter.kt @@ -1,4 +1,4 @@ -package com.jeremy.thunder.event +package com.jeremy.thunder.event.converter import com.google.gson.Gson import com.google.gson.TypeAdapter @@ -6,11 +6,7 @@ import com.google.gson.reflect.TypeToken import java.io.StringReader import java.lang.reflect.Type -interface Converter { - fun convert(data: String): T -} - -class ConvertAdapter private constructor( +class GsonConvertAdapter private constructor( private val gson: Gson, private val typeAdapter: TypeAdapter, private val type: Type @@ -22,9 +18,9 @@ class ConvertAdapter private constructor( } class Factory { - fun create(type: Type): ConvertAdapter<*> { + fun create(type: Type): GsonConvertAdapter<*> { val typeAdapter = Gson().getAdapter(TypeToken.get(type)) - return ConvertAdapter(Gson(), typeAdapter, type) + return GsonConvertAdapter(Gson(), typeAdapter, type) } } } \ No newline at end of file diff --git a/thunder/src/main/java/com/jeremy/thunder/event/converter/SerializeConvertAdapter.kt b/thunder/src/main/java/com/jeremy/thunder/event/converter/SerializeConvertAdapter.kt new file mode 100644 index 0000000..c282866 --- /dev/null +++ b/thunder/src/main/java/com/jeremy/thunder/event/converter/SerializeConvertAdapter.kt @@ -0,0 +1,45 @@ +package com.jeremy.thunder.event.converter + +import com.jeremy.thunder.thunderLog +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerialFormat +import kotlinx.serialization.StringFormat +import kotlinx.serialization.json.Json +import kotlinx.serialization.serializer +import okhttp3.internal.ignoreIoExceptions +import java.lang.reflect.Type + +class SerializeConvertAdapter private constructor( + type: Type, + private val kSerializer: KSerializer +) : Converter { + private fun SerialFormat.serializeAsType(type: Type): KSerializer = serializersModule.serializer(type) + class Factory { + fun create(type: Type): SerializeConvertAdapter<*> { + return SerializeConvertAdapter(type, serializer(type)) + } + } + + private val json = Json { + isLenient = true + useAlternativeNames = true // can use alternative key + ignoreUnknownKeys = true // prevent ignore no matching key exception + coerceInputValues = true // Set as non-null type but receive as null type. you can use default value as set true + encodeDefaults = true // You can use default value when received data does not have that value. + } + + private val stringFormat: StringFormat = json.apply { + ignoreIoExceptions { + thunderLog("[IoException] Cause convert specific type is not supported.") + } + } + + private val loader: DeserializationStrategy = stringFormat.serializeAsType(type) as DeserializationStrategy + + override fun convert(data: String): T { + return kotlin.run { + stringFormat.decodeFromString(loader, data) + } + } +} diff --git a/thunder/src/main/java/com/jeremy/thunder/internal/ServiceExecutor.kt b/thunder/src/main/java/com/jeremy/thunder/internal/ServiceExecutor.kt index 2050825..358e759 100644 --- a/thunder/src/main/java/com/jeremy/thunder/internal/ServiceExecutor.kt +++ b/thunder/src/main/java/com/jeremy/thunder/internal/ServiceExecutor.kt @@ -1,9 +1,12 @@ package com.jeremy.thunder.internal import com.google.gson.Gson -import com.jeremy.thunder.event.ConvertAdapter import com.jeremy.thunder.event.SocketEventKeyStore import com.jeremy.thunder.event.WebSocketEvent +import com.jeremy.thunder.event.converter.Converter +import com.jeremy.thunder.event.converter.ConverterType +import com.jeremy.thunder.event.converter.GsonConvertAdapter +import com.jeremy.thunder.event.converter.SerializeConvertAdapter import com.jeremy.thunder.getAboutRawType import com.jeremy.thunder.getParameterUpperBound import kotlinx.coroutines.CoroutineScope @@ -14,6 +17,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import java.lang.reflect.Method import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type /** * create logic by annotation @@ -23,6 +27,7 @@ import java.lang.reflect.ParameterizedType class ServiceExecutor internal constructor( private val thunderProvider: ThunderProvider, + private val converterType: ConverterType, private val scope: CoroutineScope ) { @@ -49,7 +54,9 @@ class ServiceExecutor internal constructor( method.requireParameterTypes { "Receive method must have zero parameter: $method" } method.requireReturnTypeIsOneOf(ParameterizedType::class.java) { "Receive method must return ParameterizedType: $method" } val returnType = (method.genericReturnType as ParameterizedType).getParameterUpperBound(0) - val converter = ConvertAdapter.Factory().create(returnType) + //val converter = ConvertAdapter.Factory().create(returnType) + //val converter = SerializeConvertAdapter.Factory().create(returnType) + val converter = checkConverterType(converterType, returnType) val eventMapper = SocketEventKeyStore().findEventMapper(returnType, method.annotations, converter) thunderProvider.observeEvent() .map(eventMapper::mapEvent) @@ -62,6 +69,13 @@ class ServiceExecutor internal constructor( } } + private fun checkConverterType(converterType: ConverterType, returnType: Type): Converter { + return when(converterType) { + ConverterType.Gson -> GsonConvertAdapter.Factory().create(returnType) + ConverterType.Serialization -> SerializeConvertAdapter.Factory().create(returnType) + } + } + private fun Flow.createPipeline(): ReceivePipeline<*> = ReceivePipeline(this, scope) fun executeSend(method: Method, args: Array) { @@ -74,11 +88,12 @@ class ServiceExecutor internal constructor( class Factory( private val thunderProvider: ThunderProvider, - private val scope: CoroutineScope + private val converterType: ConverterType, + private val scope: CoroutineScope, ) { fun create(): ServiceExecutor { - return ServiceExecutor(thunderProvider, scope) + return ServiceExecutor(thunderProvider, converterType, scope) } }