Return different object types from pre-existing classes in kotlin - kotlin

I am working with a method which return different kind of objects and I am using the Any type to achieve this.
the method looks like this:
override fun presentNativeItem(dcsItem: DCSItem): Any {
var type = dcsItem?.type?.toUpperCase()
return when (type) {
DSCType.NAVMENU.name -> buildNavMenu(dcsItem)
DSCType.NAVLINK.name -> buildNavLink(dcsItem)
DSCType.IMAGE.name -> buildImage(dcsItem)
else -> throw IllegalStateException("Unknown Type ${dcsItem?.type} of NavItem")
}
}
The model of the classes is just like:
abstract class NavItem {
abstract val attributes: String
abstract val text: String
abstract val hasChildren: Boolean
abstract val childrenIds: List<Int>?
abstract val items: List<NavItem>?
abstract val hasImages: Boolean
abstract val image: String?
}
data class NavMenu(override val items: List<NavItem>?,
override var image: String?,
override val attributes: String,
override val text: String,
override val hasChildren: Boolean,
override val childrenIds: List<Int>?,
override val hasImages: Boolean) : NavItem()
data class NavLink(override val items: List<NavItem>?,
val shortText: String?,
override var image: String?,
override val attributes: String,
override val text: String,
override val hasChildren: Boolean,
override val childrenIds: List<Int>?,
override val hasImages: Boolean) : NavItem()
And finally I am using this method in the next way:
override fun getNavItemById(dCSServiceContext: DCSServiceContext): Single<Any> {
return scribeProvider.getNavItemById(dCSServiceContext).map { navItem ->
scribePresenter.presentNativeItem(navItem)
}
}
I have read about sealed classes, but you have to create classes with a constructor inside the sealed classes, I have this model which I can't modify because it is used in several places.
Any ideas?
Thanks!

Wrap the different return types in a sealed class hierarchy and return NativeItem from your function:
sealed class NativeItem
class NavMenuItem(val menu: NavMenu) : NativeItem()
class NavLinkItem(val link: NavLink) : NativeItem()
class ImageItem(val image: Image) : NativeItem()

Related

how can I convert my room entity to my data class in kotlin?

I have a data class that I pull from internet and I want to save room database but there is a problem like that.
It always gives an error like this, how can I overcome this problem?
my room entity class
#Entity(tableName = "ExchangeValues")
data class ExchangeEntity(
#ColumnInfo(name = "base_code") val base_code: String,
#ColumnInfo(name = "conversion_rates") val conversion_rates: ConversionRates,
#ColumnInfo(name = "result") val result: String,
#PrimaryKey(autoGenerate = true) val uid:Int?=null
)
my dao
#Dao
interface ExchangeDao {
#Query("SELECT * FROM ExchangeValues")
suspend fun getAll() : List<ExchangeEntity>
#Query("UPDATE ExchangeValues SET base_code=:base_code,conversion_rates=:conversion_rates , result=:result")
suspend fun update(base_code:String,conversion_rates:ConversionRates,result:String)
}
my exchange data class
#Serializable
data class Exchange(
val base_code: String,
val conversion_rates: ConversionRates,
val documentation: String,
val result: String,
val terms_of_use: String,
val time_last_update_unix: Int,
val time_last_update_utc: String,
val time_next_update_unix: Int,
val time_next_update_utc: String
) {
fun toEntity() = ExchangeEntity(
base_code = base_code,
conversion_rates = conversion_rates,
result = result
)
}
#Serializable
data class ConversionRates(
val conversionRates : Map<String,Double>
)
I cant use toEntity function in getAll()
exchangeRepositoryImpl
class ExchangeRepositoryImpl #Inject constructor(
private val dao:ExchangeDao
) : ExchangeRepository{
override suspend fun getAll() : Flow<List<Exchange>> {
return flow {
emit(dao.getAll())
}
}
override suspend fun update(exchange: Exchange) {
dao.update(exchange.base_code,exchange.result,exchange.conversion_rates)
}
}
my exchange converter
class ExchangeConverter {
#TypeConverter
fun fromSource(conversionRates: ConversionRates) : String{
val gson = Gson()
return gson.toJson(conversionRates)
}
#TypeConverter
fun toSource(json: String): ConversionRates {
val gson = Gson()
val typeToken = object : TypeToken<List<ConversionRates>>() {}.type
return Gson().fromJson(json, typeToken)
}
}
I wrote a converter like this, but it might not be correct, I'm not so sure. How can I solve this problem?
Inside flow you have created call map function the call to toEntity() eg
flow{
emit (dao.getAll().map{it.toEntity()})
}
Well your flow returns a flow of
List<Exchange>
and your repo returns
List<ExchangeEntity>
and there's nothing in your code to map an ExchangeEntity to an Exchange.
So you need something like:
override suspend fun getAll() : Flow<List<Exchange>> {
return flow {
emit(dao.getAll().map{Exchange(base_code = it.baseCode)})// add in other fields on exchange constructor
}
}

Attempt to invoke interface method 'int java.util.List.size()' on a null object reference on passing Parcelable in Intent Kotlin

So I have a sample app that is currently using Retrofit to fetch data and display it in a recyclerview with its custom adapter. I want to pass the data to the a more details page when I click on a character name on my recyclerView. I found some tutorials and decided to use the Parcelize annotation like this:
#Parcelize data class Character (
val charID: Long,
val name: String,
val birthday: String,
val occupation: List<String>,
val img: String,
val status: String,
val nickname: String,
val appearance: List<Long>,
val portrayed: String,
val category: String,
val betterCallSaulAppearance: List<Long> ) : Parcelable
My adapter looks like this:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val character = characters[position]
holder.characterNameText.text = character.name
holder.characterNameText.setOnClickListener {
holder.passData(character)
}
}
override fun getItemCount() = characters.size
class ViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView) {
val characterNameText = itemView.findViewById<TextView>(R.id.character_name)
fun passData(character : Character) {
val intent = Intent(itemView.context, CharacterDetails::class.java)
intent.putExtra(CHARACTER, character)
itemView.context.startActivity(intent)
}
}
And in the CharacterDetails Activity it looks like this:
companion object {
const val CHARACTER = "character"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_character_details)
val bundle = intent.extras
val character = bundle!!.getParcelable<Character>(CHARACTER)
val characterName = findViewById<TextView>(R.id.character_details_name)
Yet I get a
java.lang.NullPointerException: Attempt to invoke interface method 'int java.util.List.size()' on a null object reference
at com.example.myapplication.models.Character.writeToParcel(Unknown Source:85)
I'm still new to this so I really need your help.Thanks!
Obviously, the error caused by the list reference of the data class is null. You can modify the code like this.
#Parcelize data class Character (
val charID: Long,
val name: String,
val birthday: String,
val occupation: List<String>?,
val img: String,
val status: String,
val nickname: String,
val appearance: List<Long>?,
val portrayed: String,
val category: String,
val betterCallSaulAppearance: List<Long>? ) : Parcelable

How to intercept deserialisation with kotlinx.serialization

For example, I have JSON
{
"url": "//n.ya.com"
}
In order to deserialize, I define the data class
#Serializable
data class Foo(
#SerialName("url")
val link: String,
)
After deserializing the Foo object has
foo.link with "//n.ya.com"
How can I remove the // during the deserializing, which means foo.link with "n.ya.com"?
You can add custom Serializer for a single property:
#Serializable
data class Foo(
#SerialName("url")
#Serializable(with = FooLinkDeserializer::class)
val link: String,
)
object FooLinkSerializer : KSerializer<String> {
override val descriptor = PrimitiveSerialDescriptor("Foo.link", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): String {
return decoder.decodeString().substringAfter("//")
}
override fun serialize(encoder: Encoder, value: String) {
encoder.encodeString("//$value")
}
}
Or you can intercept JSON transformations using JsonTransformingSerializer:
#Serializable
data class Foo(
#SerialName("url")
#Serializable(with = FooLinkInterceptor::class)
val link: String,
)
object FooLinkInterceptor : JsonTransformingSerializer<String>(String.serializer()) {
override fun transformDeserialize(element: JsonElement): JsonElement {
return JsonPrimitive(element.jsonPrimitive.content.substringAfter("//"))
}
}

Deserialize generic object using Kotlin Serialization

I am trying to replace Gson library by kotlin serialization to handle JSON serialization/deserialization.
I am facing some issues to deserialize generic objects I have setup a simple example of what I am trying to achieve:
#Serializable
data class ContentMessageDto<T>(
val method: String,
val content: T
)
#Serializable
private data class DummyObjectNonNullProperties(
val value: Int,
#SerialName("aaa") val someProp: String,
val bbb: Boolean,
val ccc: Double
)
interface MyParser {
fun <T> parseContentMessage(
json: String
): ContentMessageDto<T>
}
class MyParserImpl(private val jsonSerialization: Json) : MyParser {
override fun <T> parseContentMessage(json: String): ContentMessageDto<T> {
return jsonSerialization.decodeFromString<ContentMessageDto<T>>(json)
}
}
fun main() {
println("start processing...")
val jsonToParse = """
{
"method":"get",
"content":{
"value":345,
"aaa": "some string",
"bbb": true,
"ccc": 23.4
}
}""".trimIndent()
val parser:MyParser = MyParserImpl(Json)
val result = parser.parseContentMessage<DummyObjectNonNullProperties>(jsonToParse)
println("result -> $result")
}
But when I run the main method, I get the following error:
Exception in thread "main" java.lang.IllegalStateException: Only KClass supported as classifier, got T
at kotlinx.serialization.internal.Platform_commonKt.kclass(Platform.common.kt:102)
at kotlinx.serialization.SerializersKt__SerializersKt.serializer(Serializers.kt:52)
at kotlinx.serialization.SerializersKt.serializer(Unknown Source)
at kotlinx.serialization.SerializersKt__SerializersKt.builtinSerializerOrNull$SerializersKt__SerializersKt(Serializers.kt:79)
at kotlinx.serialization.SerializersKt__SerializersKt.serializerByKTypeImpl$SerializersKt__SerializersKt(Serializers.kt:69)
at kotlinx.serialization.SerializersKt__SerializersKt.serializer(Serializers.kt:54)
at kotlinx.serialization.SerializersKt.serializer(Unknown Source)
But I am not sure why. Can someone provide me an explanation and if possible some tips on how I can implement this?
It would have worked if you've simply done:
val result = Json.decodeFromString<ContentMessageDto<DummyObjectNonNullProperties>>(jsonToParse)
But with all this wrapping, type information about T was lost. The problem is that you can't simply use reified generics here, cause inline functions can't be non-final.
Possible workarounds:
Define parseContentMessage as extension function so that it could have inline modifier (and T could be reified):
interface MyParser {
val jsonSerialization: Json
}
inline fun<reified T> MyParser.parseContentMessage(json: String): ContentMessageDto<T> {
return jsonSerialization.decodeFromString(json)
}
class MyParserImpl(override val jsonSerialization: Json) : MyParser
//Usage will be the same
Manually pass serializer for T into parseContentMessage:
interface MyParser {
fun <T> parseContentMessage(json: String, contentSerializer: KSerializer<T>): ContentMessageDto<T>
}
class MyParserImpl(private val jsonSerialization: Json) : MyParser {
override fun <T> parseContentMessage(json: String, contentSerializer: KSerializer<T>): ContentMessageDto<T> {
return jsonSerialization.decodeFromString(ContentMessageDto.serializer(contentSerializer), json)
}
}
//Usage:
val result = parser.parseContentMessage(jsonToParse, DummyObjectNonNullProperties.serializer())

Kotlin - How to make a property delegate by map with a custom name?

I'm trying to get my head around property delegates, and I have an interesting use case. Is it possible to have something like this:
class MyClass {
val properties = mutableMapOf<String, Any>()
val fontSize: Any by MapDelegate(properties, "font-size")
}
That would allow me to store fontSize using the map as a delegate, but with a custom key (i.e. "font-size").
The specific use case if for storing things like CSS property tags that can be accessed through variables (fontSize) for use in code, but can be rendered properly when iterating through the map (font-size: 18px;).
The documentation on the delegated properties is a good source of information on the topic. It probably is a bit longer read than the following examples:
fun <T, TValue> T.map(properties: MutableMap<String, TValue>, key: String): ReadOnlyProperty<T, TValue> {
return object : ReadOnlyProperty<T, TValue> {
override fun getValue(thisRef: T, property: KProperty<*>) = properties[key]!!
}
}
class MyClass {
val properties = mutableMapOf<String, Any>()
val fontSize: Any by map(properties, "font-size")
}
You can ease up things a little bit and avoid typing the CSS property name by converting Kotlin property names to CSS attributes equivalents like so:
fun <T, TValue> map(properties: Map<String, TValue>, naming:(String)->String): ReadOnlyProperty<T, TValue?> {
return object : ReadOnlyProperty<T, TValue?> {
override fun getValue(thisRef: T, property: KProperty<*>) = properties[naming(property.name)]
}
}
object CamelToHyphen : (String)->String {
override fun invoke(camelCase: String): String {
return CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, camelCase)
}
}
fun <T, TValue> T.cssProperties(properties: Map<String,TValue>) = map(properties, CamelToHyphen)
class MyClass {
val properties = mutableMapOf<String, Any>()
val fontSize: Any? by cssProperties(properties)
}
The above sample uses Guava's CaseFormat.
If you'd like to have mutable property your delegate will have to implement setter method:
fun <T, TValue> map(properties: MutableMap<String, TValue?>, naming: (String) -> String): ReadWriteProperty<T, TValue?> {
return object : ReadWriteProperty<T, TValue?> {
override fun setValue(thisRef: T, property: KProperty<*>, value: TValue?) {
properties[naming(property.name)] = value
}
override fun getValue(thisRef: T, property: KProperty<*>) = properties[naming(property.name)]
}
}