I am working on an android application that uses Firebase Firestore as a database. I am trying to store a list of SoldItems where each soldItem has a PrescriptionType formed from Sealed classes.
data class SoldItem(var itemName : String = "",
var quantity : String = "",
var totalPrice : Int = 0,
var itemQuantityType: ItemQuantityType = ItemQuantityType.SINGLE,
var prescriptionType: String = ""
) : Serializable
prescriptionType while defined as a string is originally a Sealed Class PrescriptionType
sealed class PrescriptionType : Parcelable {
// Check how to parcelize objects
#Parcelize
data class Syrup(var quantityInMls : Double = 2.5, var frequency : PrescriptionFrequency = PrescriptionFrequency.Stat, var time : PrescriptionTime = PrescriptionTime.OneDay) : PrescriptionType()
#Parcelize
data class Lotion(var frequency : PrescriptionFrequency = PrescriptionFrequency.Stat, var time : PrescriptionTime = PrescriptionTime.OneDay): PrescriptionType()
#Parcelize
data class Capsule(var quantity : Int = 1, var frequency : PrescriptionFrequency = PrescriptionFrequency.Stat, var time : PrescriptionTime = PrescriptionTime.OneDay ) : PrescriptionType()}
I store prescriptionType as a string because of the lack of support in FirebaseFirestore android SDK directly for sealed classes. I've had to create my own TypeConverter. This works quite well in generating a string (JSON) from each prescriptionType and also generating a prescriptionType from the string.
#JvmStatic
#TypeConverter
fun fromPrescription(prescriptionType: PrescriptionType) : String{
val prescriptionAdapterFactory : RuntimeTypeAdapterFactory<PrescriptionType> = RuntimeTypeAdapterFactory.
of(PrescriptionType::class.java, "type")
.registerSubtype(PrescriptionType.Syrup::class.java, "Syrup")
.registerSubtype(PrescriptionType.Lotion::class.java, "Lotion")
.registerSubtype(PrescriptionType.Capsule::class.java, "Capsule")
val gsonBuilder = GsonBuilder().registerTypeAdapterFactory(prescriptionAdapterFactory).create()
return "\"" +gsonBuilder.toJson(prescriptionType, PrescriptionType::class.java) + "\""
}
#JvmStatic
#TypeConverter
fun toPrescriptionType(prescriptionType : String) : PrescriptionType {
val prescriptionAdapterFactory : RuntimeTypeAdapterFactory<PrescriptionType> = RuntimeTypeAdapterFactory.
of(PrescriptionType::class.java, "type")
.registerSubtype(PrescriptionType.Syrup::class.java, "Syrup")
.registerSubtype(PrescriptionType.Lotion::class.java, "Lotion")
.registerSubtype(PrescriptionType.Capsule::class.java, "Capsule")
val gsonBuilder = GsonBuilder().registerTypeAdapterFactory(prescriptionAdapterFactory).create()
return gsonBuilder.fromJson(prescriptionType, PrescriptionType::class.java)
}
}
Storing the string works quite well in the database. The object is stored and everything is fine - we can see that the prescriptionType is a string.
However in retrieving soldItems in Firebase, we encounter an issue where it seems the firebase SDK returns the JSON string and converts it to an object and hence crashes as prescriptionType is a String not an object.
val soldItemsType = object : TypeToken<List<SoldItem>>() {}.type
val soldItemsFromBackend = it.get("soldItems").toString()
Timber.d(soldItemsFromBackend)
val soldItems = Gson().fromJson<List<SoldItem>>(
soldItemsFromBackend,
soldItemsType
)
Logging soldItemsFromBackend gives
[{drugQuantityType=SINGLE, prescriptionType={"type":"Capsule","frequency":"Stat","quantity":1,"time":"OneDay"}, itemName=Docusate, quantity=2, totalPrice=0}]
and trying to create soldItems using Gson gives an error
Caused by: java.lang.IllegalStateException: Expected a string but was BEGIN_OBJECT at line 1 column 46 path $[0].prescriptionType
I think Gson wants to parse prescriptionType as a string but instead sees an object. I've tried to pad the prescriptionType string with extra quotes but it still didn't work.
Does anyone please know how I can retrieve prescriptionType as a string?
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 have a simple Json class that handles retrofit perfectly.
But there is no timestamp in it.
I add my custom field which is not in json, but it is always null
//Custom field for timestamp
val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
//My class for JSON respons
#Entity(tableName = "nal")
data class CurrencyItem(
#PrimaryKey(autoGenerate = true)
val id:Long,
#SerializedName("base_ccy")
var baseCcy: String,
#SerializedName("buy")
val buy: String,
#SerializedName("ccy")
val ccy: String,
#SerializedName("sale")
val sale: String,
val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
)
Simplest solution add custom field in model for timestamp.
val timeStamp: String
Аnd when retrofit response come, rewrite this null field with timestamp, i use method SimpleDateFormat
// retrofit response
var res = resp.body()
// new list which i create and rewrite with timestamp and then return
var l = ArrayList<CurrencyItem>()
//read response list with json converted data
for (r in res.orEmpty()){
l.add(CurrencyItem(1 ,
r.baseCcy,
r.buy,
r.ccy,
r.sale,
SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())))
}
//return this new list with timestamp. I use repository pattern with livedata
response.value = l
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 have following groups of string and would like to create a string following the syntax: substr1.substr2.substr3 = substr1 + substr2 + substr3
data class Protocol( val http: String = "http://",
val https: String = "https://")
data class BaseUrl( val baseProd: String = "url_prod",
val baseDev: String = "url_dev",
val baseTest: String = "url_test")
data class ApiEndpoint( val GET_USER: String = "endpoint_get_user",
val LOGIN: String = "endpoint_login",
val REGISTER: String = "endpoint_get",
val FETCH_HISTORY: String = "endpoint_fetch_history")
data class WebUrl( val HOME: String = "path_home",
val BALANCE: String = "path_balance",
val MANAGE_SUBSCRIPTION: String = "path_manage_subscription")
data class RequestEnvironment( val mobile: String = "query_mobile",
val desktop: String = "query_desktop")
My goal is to make something that build the strings like this
UrlFactory.https.baseDev.GET_USER //result: https://url_dev/get_user
UrlFactory.https.baseProd.HOME.mobile //result: https://url_prod/home?mobile=1
UrlFactory.http.baseDev.BALANCE //result: http://url_dev/balance
Have anyone built a nice way to handle url strings dynamically like this?
You can create an object Url which is in charge of building up your URL string.
You can build up Url by passing in your selected options through the constructor.
When the object is constructed, you can then call toString which will concatenate and return the values together.
class Url ( var protocol : Protocol,
var baseUrl : BaseUrl,
var apiEndpoint : ApiEndpoint,
var webUrl : WebUrl,
var requestEnvironment : RequestEnvironment) {
override fun toString() : String {
return protocol.value +
baseUrl.value +
apiEndpoint.value +
webUrl.value +
requestEnvironment.value
}
}
To add another level of safety when working with your Strings, I took the liberty of converting them to enums. This will give you the benefit of allowing you to limit the possible values which can be set:
enum class Protocol(val value : String) {
HTTP("http://"),
HTTPS("https://")
}
enum class BaseUrl(val value : String) {
BASE_PROD("url_prod"),
BASE_DEV("url_dev"),
BASE_TEST("url_test")
}
enum class ApiEndpoint(val value : String) {
GET_USER("endpoint_get_user"),
LOGIN("endpoint_login"),
REGISTER("endpoint_get"),
FETCH_HISTORY("endpoint_fetch_history")
}
enum class WebUrl(val value : String) {
HOME("path_home"),
BALANCE("path_balance"),
MANAGE_SUBSCRIPTION("path_manage_subscription")
}
enum class RequestEnvironment(val value : String) {
MOBILE("query_mobile"),
DESKTOP("query_desktop")
}
Finally, here is an example of how you can now build your URL:
fun main() {
val url : Url = Url(Protocol.HTTP,
BaseUrl.BASE_DEV,
ApiEndpoint.GET_USER,
WebUrl.HOME,
RequestEnvironment.MOBILE);
println(url.toString())
}
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.