Hi I have a Kotlin data class as follows
data class User (
#get:Exclude val gUser: Boolean,
#get:Exclude val uid: String,
#get:PropertyName("display_name") val displayName: String,
#get:PropertyName("email") val email: String,
#get:PropertyName("account_picture_url") val accountPicUrl: String,
#get:PropertyName("provider") val provider: String
)
I am able to serialize the object without an issues. But i'm having trouble deserializing the object when doing a firebase query. Currently this is what i'm doing to get the data
_firebaseReference.child(getString(R.string.firebase_users_key)).child(user.uid)
.setValue(user).addOnCompleteListener{
_firebaseReference.child("users").child(user.uid)
.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onCancelled(p0: DatabaseError) {
}
override fun onDataChange(p0: DataSnapshot) {
if (p0.exists()) {
val userHash = p0.value as HashMap<*, *>
var currentUser: User
if (userHash[getString(R.string.provider_key)]
!= getString(R.string.provider_google)) {
currentUser = User(false, p0.key!!,
userHash["display_name"].toString(),
userHash["email"].toString(),
userHash["account_picture_url"].toString(),
userHash["provider"].toString())
} else {
currentUser = User(true, p0.key!!,
userHash["display_name"].toString(),
userHash["email"].toString(),
userHash["account_picture_url"].toString(),
userHash["provider"].toString())
}
}
}
})
}
This is only a test project that i'm working on to practice my Kotlin, but this is something I would like to figure out.
If i'm doing it completely wrong please let me know, any advise would be greatly appreciated
Thanks
Firebase needs an empty constructor to be able to deserialize the objects:
data class User(
#Exclude val gUser: Boolean,
#Exclude val uid: String,
#PropertyName("display_name") val displayName: String,
#PropertyName("email") val email: String,
#PropertyName("account_picture_url") val accountPicUrl: String,
#PropertyName("provider") val provider: String
) {
constructor() : this(false, "", "", "", "", "")
}
You can either declare it like so and provide some default values to be able to call the primary constructor or you can declare default values for all your parameters:
data class User (
#Exclude val gUser: Boolean = false,
#Exclude val uid: String = "",
#PropertyName("display_name") val displayName: String = "",
#PropertyName("email") val email: String = "",
#PropertyName("account_picture_url") val accountPicUrl: String = "",
#PropertyName("provider") val provider: String = ""
)
Then various constructors will be created for you, including an empty constructor.
If there's a problem with serialization there might be because of the getters and setters generated by the ide, try reinforcing them with #get and #set annotations:
data class User (
#Exclude val gUser: Boolean = false,
#Exclude val uid: String = "",
#set:PropertyName("display_name")
#get:PropertyName("display_name")
var displayName: String = "",
#PropertyName("email") val email: String = "",
#set:PropertyName("account_picture_url")
#get:PropertyName("account_picture_url")
var accountPicUrl: String = "",
#PropertyName("provider") val provider: String = ""
)
What I actually wanted is a Kotlin data class which is derived from a domain model interface like so
data class Dto(#PropertyName("serialized_title") val override title: String) : DomainModel
In this case DomainModel is defined this way
interface DomainModel { val title: String }
My goal was to fetch data from Firestore and get deserialized Dto objects which are provided to clients which receive objects of type DomainModel. So this solution above unfortunately didn't work. I saw the workarounds using #get: and #set: Annotations but I wanted my data class properties to be immutable. Simply using vars is a bad design decision in my use case. And also this solution looks quite ugly...
After inspecting the decompiled Java-Code I came up with this solution
data class Dto(
#field:[JvmField PropertyName("serialized_title")]
override val title: String = "") : DomainModel
The decompiled Java-Code simply uses title as public final field having the PropertyName annotation.
I prefer this solution since it doesn't violate certain design decisions I made...
In Android Studio (kotlin)
use this (only var and getter and setter):
#set:PropertyName("email") #get:PropertyName("email") var emailPerson: String = ""
None of this works:
#PropertyName("email") var emailPerson: String = ""
#PropertyName("email") val emailPerson: String = ""
#get:PropertyName("email") val emailPerson: String = ""
Android Studio 4.1.2. Gradle: com.google.firebase:firebase-database:19.6.0
Related
I have this type of array in firebase but how to fetch it and use in kotlin
I was able to get as String but how to get its as a data class Like this
data class Comment(
val uid: String,
val comment: String,
val stamp: Timestamp
)
and here's the code of getting string
var text by remember { mutableStateOf("loading...") }
FirebaseFirestore.getInstance().collection("MyApp")
.document("Something").get().addOnSuccessListener {
text = it.get("Comments").toString()
}
Firebase has a toObject method that can be used to turn your document into a custom object.
db.collection("Comments")
.get()
.addOnSuccessListener { documents ->
for (document in documents) {
val comment = document.toObject<Comment>()
}
}
The Comment data class should also define default values. So, it should be like...
data class Comment(
val uid: String = "",
val comment: String = "",
#ServerTimeStamp val stamp: Date? = null
)
I got ArrayLists with HashMaps represents my entities entities just using this:
val cleanAnswers: List<Answer> = (document.get(FIELD_ANSWERS)
as ArrayList<HashMap<String, Any>>).map {
Answer(
it[FIELD_VARIANT] as String,
it[FIELD_IS_CORRECT] as Boolean
)
}
My entity:
class Answer(val answer: String,
val isCorrect: Boolean) : Serializable
I am trying to show list of messages with different types of ViewHolders i.e. Text, ImageText, Video etc. I get a list of these objects from API somewhat in this format:
{
"message":"success",
"total_pages":273,
"current_page":1,
"page_size":10,
"notifications":[
{
"id":4214,
"notification_message":"test notification 1",
"meta_data":{
"messageId":"19819189",
"viewHolderType":"textOnly",
"body":{
"time":"10-06-21T02:31:29,573",
"type":"notification",
"title":"Hi, Welcome to the NT experience",
"description":"This is the welcome message",
"read":true
}
}
},
{
"id":9811,
"notification_message":"test vss notification",
"meta_data":{
"messageId":"2657652",
"viewHolderType":"textWithImage",
"body":{
"time":"11-06-21T02:31:29,573",
"type":"promotions",
"title":"Your Package - Premium",
"description":"Thank you for subscribing to the package. Your subscription entitles you to Premium 365 Days Plan (worth $76.61)",
"headerImage":"www.someurl.com/image.jpg",
"read":true
}
}
}
]
}
Now I have to parse this list from network module for client module which will use only the objects inside meta_data. To that end I have created following classes:
open class BaseMessageListItem
internal data class MessageListResponse(
#field:SerializedName("current_page")
val current_page: Int,
#field:SerializedName("notifications")
val notifications: List<MessageListItem>,
#field:SerializedName("message")
val message: String,
#field:SerializedName("page_size")
val page_size: Int,
#field:SerializedName("total_page")
val total_page: Int
)
internal data class MessageListItem(
#field:SerializedName(“id”)
val id: String,
#field:SerializedName("notification_message")
val notification_message: String,
#field:SerializedName("meta_data")
val meta_data: MessageListMetaDataItem,
)
internal data class MessageListMetaDataItem(
#field:SerializedName("messageId")
val messageId: String = "",
#field:SerializedName("viewHolderType")
val viewHolderType: String = "",
#field:SerializedName("body")
val body: BaseMessageListItem = BaseMessageListItem()
)
internal data class ImageMessageListItem(
#field:SerializedName("description")
val description: String,
#field:SerializedName("headerImage")
val headerImage: String,
#field:SerializedName("read")
val read: Boolean,
#field:SerializedName("time")
val time: String,
#field:SerializedName("title")
val title: String,
#field:SerializedName("type")
val type: String
): BaseMessageListItem()
internal data class TextMessageListItem(
#field:SerializedName("description")
val description: String,
#field:SerializedName("read")
val read: Boolean,
#field:SerializedName("time")
val time: String,
#field:SerializedName("title")
val title: String,
#field:SerializedName("type")
val type: String
): BaseMessageListItem()
The notifications>meta_data>body can be polymorphic. I have set of classes (for ImageItem, ImageWithTextItem, VideoItem etc) which extend to BaseMessageListItem.
private var runtimeTypeAdapterFactory: RuntimeTypeAdapterFactory<BaseMessageListItem> = RuntimeTypeAdapterFactory
.of(BaseMessageListItem::class.java, "viewHolderType")
.registerSubtype(ImageMessageListItem::class.java, MessageListItemTypes.TEXT_WITH_IMAGE.value)
.registerSubtype(TextMessageListItem::class.java, MessageListItemTypes.TEXT_ONLY.value)
private var gson: Gson = GsonBuilder()
.registerTypeAdapterFactory(runtimeTypeAdapterFactory)
.create()
I tried parsing it using viewHolderType in RuntimeTypeAdapterFactory but since it's not a property of BaseMessageListItem, it is not able to parse it.
Any one has any experience dealing with this type of JSON, please do share any pointers.
RuntimeTypeAdapterFactory requires the viewHolderType field to be put right into the body objects. In order to fix this, you have
either patch RuntimeTypeAdapterFactory (it is not even published as a compiled JAR, but rather still retains in the public repository as source code free to modify), or fix your class hierarchy to lift up the missing field because it can only work with fields on the same nest level.
internal var gson: Gson = GsonBuilder()
.registerTypeAdapterFactory(
RuntimeTypeAdapterFactory.of(BaseMessageListMetaDataItem::class.java, "viewHolderType")
.registerSubtype(TextWithImageMessageListMetaDataItem::class.java, "textWithImage")
.registerSubtype(TextOnlyMessageListMetaDataItem::class.java, "textOnly")
)
.create()
internal data class MessageListItem(
#field:SerializedName("meta_data")
val metaData: BaseMessageListMetaDataItem<*>?,
)
internal abstract class BaseMessageListMetaDataItem<out T>(
#field:SerializedName("viewHolderType")
val viewHolderType: String?,
#field:SerializedName("body")
val body: T?
) where T : BaseMessageListMetaDataItem.Body {
internal abstract class Body
}
internal class TextOnlyMessageListMetaDataItem
: BaseMessageListMetaDataItem<TextOnlyMessageListMetaDataItem.Body>(null, null) {
internal data class Body(
#field:SerializedName("title")
val title: String?
) : BaseMessageListMetaDataItem.Body()
}
internal class TextWithImageMessageListMetaDataItem
: BaseMessageListMetaDataItem<TextWithImageMessageListMetaDataItem.Body>(null, null) {
internal data class Body(
#field:SerializedName("title")
val title: String?,
#field:SerializedName("headerImage")
val headerImage: String?
) : BaseMessageListMetaDataItem.Body()
}
I might be understanding you wrong, but I would like to suggest a different approach. I am assuming you would like to assign to get a ViewHolder type directly from what you get in your API response.
There are two approaches I would like to suggest:
First, if it is possible to get the API response modified, I would suggest to change viewHolderType from a String to an Int so as you can be clear with your mapping and then you can directly compare it.
Second what I would suggest is to keep another key in your data class which sets value as per the viewHolderType it receives which would be something of as follows.
internal data class MessageListMetaDataItem(
#field:SerializedName("messageId")
val messageId: String = "",
#field:SerializedName("viewHolderType")
val viewHolderType: String = "",
#field:SerializedName("body")
val body: BaseMessageListItem = BaseMessageListItem()
) {
val viewHolderMapping: Int
get() = when(viewHolderType){
"textOnly" -> MessageListItemTypes.TEXT_ONLY
"textWithImage" -> MessageListItemTypes.TEXT_WITH_IMAGE
else -> MessageListItemTypes.UNKNOWN_TYPE
}
}
I have an Entity like this:
data class Person(
#PrimaryKey(autoGenerate = true) val id: Long = 0,
#ColumnInfo(name = "user_id", index = true) var userId: Long = 0,
#ColumnInfo(name = "first_name") var firstName: String = "",
#ColumnInfo(name = "mid_name") var midName: String = "",
#ColumnInfo(name = "last_name") var lastName: String = ""
) {
public fun fullName(): String {
return "$firstName $midName $lastName"
}
}
I know that I can set a "not null" attribute to each named property. But in my case, it's no need to fill up all the name properties, I only want to validate the full name is blank or not before this entity saves to the room database.
I'm practicing to use the MVVM framework, but now I'm not sure where should I put the validation. Activity/Fragment? ViewModel? Repository? or inside the Entity directly?
I think maybe I should do this in a repository so that I can prevent the wrong input before I save it to the database. But what if there is another use case that I need to validate the same thing in a different repository? If so, then the code will be duplicated in two repositories.
I've ever written ruby on rails before, there is validation function in the Model like:
# This is ruby on rails code
validate :name_validation
def name_validation
if first_name == "" && mid_name == "" && last_name == ""
errors.add(:name, "the name should not be totally blank")
end
end
I wonder if there is a similar way to validate a property in the Entity, or there is any better practice to resolve this?
Please help me figure it out.
I think maybe we can just use a fullName property directly and let it not null.
Then make a Name class for name processing, and NameConveter class for converting.
So the sample code may seems like this:
class Name(
var firstName: String = "",
var midName: String = "",
var lastName: String = ""
) {
public fun fullName(): String {
return "$firstName $midName $lastName".trim()
}
}
#Entity
data class Person(
#PrimaryKey(autoGenerate = true) val id: Long = 0,
#ColumnInfo(name = "user_id", index = true) var userId: Long = 0,
#ColumnInfo(name = "full_name") var fullName: Name
)
class NameStringConverter {
#TypeConverter
fun fromString(value: String): Name {
val nameStr = value.split(" ")
return Name(nameStr[0], nameStr[1], nameStr[2])
}
#TypeConverter
fun nameToString(name: Name): String? {
val fullName = name.fullName()
// Here is the trick
// to make a blank full name become an invalid name for a not null property
if (name.isNullOrBlank()) {
return null
} else {
return fullName
}
}
}
So that we can access multiple name by Name Class, and database will help us to check the fullname now.
I know that my sample code may cause some problem such as an extra blank character in a name string, but let us focus on the fullName validation issue for now.
I figured out this solution today, I'm not sure it's a good answer or not.
I post here and open for everyone to judge it.
I have one issue about code data class kotlin android.
How to implement server response? sometimes I get String value or sometime get Object class.
class CMSRespTemp {
data class CMSRespApi(
val status: Boolean = false,
val message: String = "",
val data: String as Data
)
data class Data(
val cms_id: String = "",
val cms_content: String = ""
)
}
When I implement only Data class it works, like this val data: Data or val data: String. But I need together Data and String with key only data.
Is it possible?
When having multiple type for same variable, we can use Any type which is equivalent to Object type in java. So solution is like below :
class CMSRespTemp {
data class CMSRespApi(
val status: Boolean = false,
val message: String = "",
var data: Any? = null // changed it to var from val, so that we can change it's type runtime if required
)
data class Data(
val cms_id: String = "",
val cms_content: String = ""
)
}
And when accessing that variable, one can simply cast like below :
val apiResponse : CMSRespApi //= some API response here from network call
when (apiResponse.data) {
is String -> {
// apiResponse.data will be smart-casted to String here
}
else -> {
val responseData = Gson().fromJson<CMSRespApi.Data>(
Gson().toJsonTree(apiResponse.data),
CMSRespApi.Data::class.java
)
}
}
After 12 Hrs spend and got the solution my self,
val getResultCon = getSerCont.result // response Any
val gson = Gson()
val jsonElement = gson.toJsonTree(getResultCon)
val resultData = gson.fromJson(jsonElement, SearchContactApi.Result::class.java)
Convert your data string to toJsonTree and fromJson with model class then got result.
I try to write a network request function,Its role is to analyze the general format and provide data content to callbacks.
This is my defined class:
data class Response<T>(
val code: Int = 0,
#JvmSuppressWildcards
var data: ArrayList<T>,
val message: String = "")
in this class 'code' and 'message' is fixed,'data' has different types
Select one of data:
data class TestData(
#SerializedName("create_by")
val createBy: String = "",
#SerializedName("create_time")
val createTime: String = "",
#SerializedName("name")
val name: String = "",
#SerializedName("id")
val id: String = "")
There is my network request function:
fun <T> post(callBack: (ArrayList<T>) -> Unit) {
...
override fun onSuccess(response:Response<String>) {
val result = gson.fromJson<Response<T>>(response.body().reader(),Response::class.java)
when{
result.code==200-> callBack.invoke(result.data)
}
}
...
}
Use it at Activity:
Request.addr(Constants.GET_TEST)
.post<TestData> {
tv.text = it[0].name
}
When i use Gson parse the server returns data,and want use JavaBean ,Logcat throw this Exception:
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to com.example.network.response.TestData
i tried to use TypeToken to solve this problem ,but it also does not work.
It cause, because the parser can't fetch the real type T at runtime.
This extension works for me
inline fun Gson.fromJson(json: String) =
this.fromJson(json, object: TypeToken() {}.type)!!
Then you need modify as in you methods, as IDE recommend you.