There is a mutableLiveData Holding 2 array "deal" and "category" I need to parse this both in different adapters.
Is there a way I can convert 1 mutable live data to 2 array and then parse them to two different adapters
Suppose There is MutableVariable Name se
private lateinit var mHomePojo: MutableLiveData<List<HomePojo>>
having parse Json as below
{
"status": 0,
"response": "success",
"category": [
{
"categoryName": "demo",
"categoryDesc": "demo"
},
{
"categoryName": "demo1",
"categoryDesc": "demo"
}
],
"deal": [
{
"dealImg": "https://aiotechnology.in/Aditechweb/upload/153102117.jpg",
"dealDesc": "gd",
"dealStartDate": "2019-10-18",
"dealEndDate": "2019-10-19"
}
]
}
Is there any way to parse private lateinit var mHomePojo: MutableLiveData<List<HomePojo>> to lateinit var mDealModel: MutableLiveData<List<DealModel>> and
lateinit var mCategoryModel: MutableLiveData<List<CategoryModel>>
I am new to MVVM please help
I think Transformations might be able to help you separate your home live data to two separate livedata object with specified properties. below is piece of code for this. (NOTE : not used lateinit var for example)
private val homeLiveData: LiveData<HomePojo> = MutableLiveData<HomePojo>()
//Category Live data
private val categoryPojo = Transformations.map(homeLiveData) {
it.category
}
//Deal live data
private val dealPojo = Transformations.map(homeLiveData) {
it.deal
}
data class HomePojo(
/*-- Other fields --*/
val category: List<CategoryPojo>? = null,
val deal: List<DealPojo>? = null)
data class CategoryPojo(
val categoryName: String? = null,
val categoryDesc: String? = null)
data class DealPojo(
val dealImg: String? = null,
val dealDesc: String? = null,
val dealStartDate: String? = null,
val dealEndDate: String? = null)
Related
I m new to kotlin and MVVM, I have been working around this issue for a week now, couldn't get any idea even after searching for some code on the internet.
I'm trying to edit or modify the retrofit response (to observe a specific type; say "sf") according to my need and neglecting other data which is not needed. I'm using mutable livedata to fetch and update the JSON data from the retrofit response to the recylerview.
Here is the link for the JSON data: http://www.nactem.ac.uk/software/acromine/dictionary.py?sf=HMM
3 Data classes based on JSON response:
data class sf(
#SerializedName("sf")
#Expose
val sf : String? = null,
#SerializedName("lfs")
#Expose
val lfs : List<lfs>? = null,
)
data class lfs(
#SerializedName("lf")
#Expose
var lf : String? = null,
#SerializedName("freq")
#Expose
var freq : Int? = null,
#SerializedName("since")
#Expose
var since : Int? = null,
#SerializedName("vars")
#Expose
var vars : List<vars>? = null,
) : Serializable
class vars (
#SerializedName("lf")
#Expose
var lf : String? = null,
#SerializedName("freq")
#Expose
var freq : Int? = null,
#SerializedName("since")
#Expose
var since : Int?
): Serializable
Code in Activity:
listUsers = mutableListOf()
adapter = WordAdapters(this, listUsers )
recyclerView.adapter = adapter
wordViewModel = ViewModelProviders.of(this,
WordViewModelFactory(this)).get(WordsViewModel::class.java)
wordViewModel!!.getData().observe(this, { t: ArrayList<sf>? ->
listUsers.clear()
t?.let { listUsers.addAll(it)
}
adapter.notifyDataSetChanged()
})
ViewModel:
class WordsViewModel ( context: Context) : ViewModel() {
private var listData = MutableLiveData<ArrayList<sf>>()
init {
val wordRepository: WordsRepository by lazy {
WordsRepository
}
//if (context.isInternetAvailable()) {
listData = wordRepository.getMutableLiveData(context)
// }
}
fun getData(): MutableLiveData<ArrayList<sf>> {
return listData
} }
Repository:
object WordsRepository {
var call: Call<MutableList<sf>>? = null
fun getMutableLiveData(context: Context) : MutableLiveData<ArrayList<sf>> {
val mutableLiveData = MutableLiveData<ArrayList<sf>>()
//context.showProgressBar()
call = NetworkApiClient.apiService.getWordsMatching("HMM")
call!!.enqueue(object : Callback<MutableList<sf>> {
override fun onFailure(call: Call<MutableList<sf>>, t: Throwable) {
//hideProgressBar()
Log.e("error", t.localizedMessage.toString())
}
override fun onResponse(call: Call<MutableList<sf>>, response:
Response<MutableList<sf>>)
{
//hideProgressBar()
if (!response.isSuccessful){
Log.e("Code " , response.code().toString());
return
}
val raw: okhttp3.Response = response.raw()
val usersResponse : MutableList<sf>? = response.body()
/* if (usersResponse != null) {
for( movie in usersResponse[0].lfs!!){
Log.v("MainActivity", movie.vars.toString())
}
}*/
Log.e("Output : ", usersResponse.toString())
usersResponse?.let { mutableLiveData.value = it as ArrayList<sf> }
}
})
return mutableLiveData
}
}
this is the base structure of JSON: here "sf" is a string, lfs is the array, according to this JSON response link provided I get 8 lfs arrays, but currently after parsing the recyclecount is 1 which is the same in the adapter itemcount method, so I get one row displayed in recylerview and rest are ignored.
JSON response:
[
{
"sf":"HMM",
"lfs":[
{
"lf":"heavy meromyosin",
"freq":267,
"since":1971,
"vars":[
{
"lf":"heavy meromyosin",
"freq":244,
"since":1971
},
{
"lf":"Heavy meromyosin",
"freq":12,
"since":1975
},
{
"lf":"H-meromyosin",
"freq":5,
"since":1975
},
{
"lf":"heavy-meromyosin",
"freq":4,
"since":1977
},
{
"lf":"heavy meromyosin",
"freq":1,
"since":1976
},
{
"lf":"H-Meromyosin",
"freq":1,
"since":1976
}
]
},
I want to ignore "sf" string after response and parse the ArrayList which is present under the "sf" which is "lfs", so based on "lfs" I need to display the data.
Mutable live data is not accepting any other type other than sf, since I placed the observer on it.
On the json you posted, there is only one parent item ( one sf ), but you are actually trying to pass the 8 lfs children. You have to perform such transformation somewhere, it could be on the network call directly, like this:
usersResponse?.let { mutableLiveData.value = it[0].lfs as ArrayList }
Take into account two things:
It could be better to check if "it" is not empty before going for the first item.
This only works if you will always have only one item on the parent array ( this sounds strange since if this is the case then the service should be returning an object, not a list, as the root of the json. If you will receive more than one object you will have to map the response into a single list of lfs. Something like (pseudo code since I'm from my phone):
It.map( item -> item.lfs)
I have read other comments on this same issue, but none of them has touched on a situation like mine
In mine, below describes how the data is structured
val ref: DatabaseReference? = Firebase.database.getReference("symbols/${alphabets}")
{
"symbols" : {
"alphabets" : {
"a" : {
"available" : true,
"text" : "A",
"timestamp" : 1.512686825309134E9
},
"b" : {
"available" : true,
"text" : "B",
"timestamp" : 1.512687248764272E9
}
"NameOfSymbols" : "group of alphabets"
}
}
}
the list of the data continues, then the string is at the end of the objects
*The reason why mine is showing the error is because it can't convert the string "NameOfSymbols" : "alphabets" to the objects as specified in the data class
So, what can be done about it, I use kotlin
Is there a way I can exclude that part of the children value while I only get the one that is specified in the data class?
Data Class
data class alphabets(
val name: Names,
var NameOfSymbols: String? = null) {
data class Names(
var available: Boolean? = null,
var text: String? = null,
var timestamp: Long? = null) {
}
}
If you are listening to
"symbols/alphabets"
Then the children inside are:
"a" : {
"available" : true,
"text" : "A",
"timestamp" : 1.512686825309134E9
}
That is a Map<String, Any> in this case we can define that Any into a specific data class as you want
data class Names(
val available: Boolean? = null,
val text: String? = null,
val timestamp: Long? = null
)
Then you have that you have to transform your snapshot to:
Map<String, Names>
This is my model class
#Parcel
data class ClientModel(
var name: String? = "",
var phone: String? = "",
var princpalAddresse: String? = "",
var homeAddresse: String? = "",
var travailleAddresse: String? = "",
var email: String? = "",
var userToken: String? = "",
var principalAddresseCoords: Pair<Double, Double>? = null,
var homeAddresseCoords: Pair<Double, Double>?= null,
var workAddresseCoords: Pair<Double, Double>? = null,
)
My proGuard file keep the class :
-keep class com.olivier.oplivre.models.ClientModel
But! when I try to get the snapshot with a singleValueEventListener I got exception because of the Pair<Double,Double> variables
val utilisationInfo = snapshot.getValue(ClientModel::class.java) //todo CRASH
Exception :
com.google.firebase.database.DatabaseException: Class kotlin.Pair does not define a no-argument constructor. If you are using ProGuard, make sure these constructors are not stripped.
Database Structure :
I think firebase Realtime database treat your principalAddresseCoords as a list of long so in your ClientModel change the value of principalAddresseCoords to emptyList() and the type List
As #Sami Shorman said , firebase took my Pair instance and transform it but not as list, as Hashmap<,> ! so I changed my class model like that :
var principalAddresseCoords: HashMap<String,Double>? = null,
var homeAddresseCoords: HashMap<String,Double >? = null,
var workAddresseCoords: HashMap<String,Double >? = null,
To put the data as Hashmap I just had to do :
clientModel.workAddresseCoords = HashMap<String,Double>().apply {
put("lat",lat)
put("long",long)
}
I have a JSON string that I need to converted to data class object in Kotlin, the problem is that there is a field (details) that can have a different structure depending of the value of another field like this
val jsonString1 = "{'name': 'Juan', 'phase': 'step1', 'details': { 'name': 'product 1' }}"
val jsonString2 = "{'name': 'Juan', 'phase': 'step2', 'details': { 'position': 10 }}"
now I have something like
data class Customer(
var name: String? = null
var phase: String? = null
var details: Details? = null
)
data class Details(
var name: String? = null
)
data class Details2(
var position: Int? = null
)
now with gson I know I can
Gson().fromJson(jsonString1, Customer::class.java)
I want to be able to automatically use the right data class depending on the value of the phase field, I know I can create an adapterFactory, but I can't figure out how, an in kotlin is worse
I was reading this post
http://anakinfoxe.com/blog/2016/02/01/gson-typeadapter-and-typeadapterfactory/
and I'm pretty sure is the way to go, but I can't quite get it
Yep, it's pretty easy to write such adapter. I've slightly changed your example:
data class Customer(
var name: String? = null,
var phase: String? = null,
var details: Details? = null
)
sealed class Details {
data class Details1(var name: String? = null) : Details()
data class Details2(var position: Int? = null) : Details()
}
class CustomerDeserializer : JsonDeserializer<Customer> {
override fun deserialize(json: JsonElement, typeOfT: Type?, context: JsonDeserializationContext?): Customer {
val customerObject = json.asJsonObject
val detailsObject = customerObject.getAsJsonObject("details")
val details = if (detailsObject.has("name")) {
Details.Details1(detailsObject.get("name").asString)
} else {
Details.Details2(detailsObject.get("position").asInt)
}
return Customer(
name = customerObject.get("name").asString,
phase = customerObject.get("phase").asString,
details = details
)
}
}
fun main() {
val gson = GsonBuilder()
.registerTypeAdapter(Customer::class.java, CustomerDeserializer())
.create()
println(gson.fromJson(jsonString1, Customer::class.java))
println(gson.fromJson(jsonString2, Customer::class.java))
}
data class Customer(
var name: String? = null
var phase: String? = null
var details: Details? = null
)
data class Details(
var name: String? = null
var position: Int? = null
)
Define Details class in this way
Gson().fromJson(jsonString1, Customer::class.java)
return a Customer either name is null or position is null
I have these classes written in kotlin, Location, and the rest is in the Application.kt
#RealmClass
open class Location(
#PrimaryKey
#SerializedName("id")
var id: Int = 0,
#SerializedName("city_name")
var city_name: String? = null,
#SerializedName("elevation")
var elevation: Int = 0,
#SerializedName("state_code")
var state_code: String? = null,
#SerializedName("state_name")
var state_name: String? = null,
#SerializedName("country_code")
var country_code: String? = null,
#SerializedName("country_name")
var country_name: String? = null
):RealmObject()
and the rest:
private fun loadStuff() {
val inputStream = this.resources.openRawResource(R.raw.city_json)
val jsonReader = JsonReader(InputStreamReader(inputStream, "UTF-8"))
val gson = Gson()
Realm.getDefaultInstance().executeTransactionAsync(Realm.Transaction { realm ->
val weatherList = gson.fromJson<List<Location>>(jsonReader , Array<Location>::class.java).toList()
//realm.insertOrUpdate(location)
jsonReader.endArray()
jsonReader.close()
}, Realm.Transaction.OnSuccess {
Log.d("TAG", "Success")
})
}
and I keep getting exception:
com.example.android.sunshine.data.Location[] cannot be cast to java.lang.Iterable
what am I doing wrong ?
the object looks like this:
[
{
"id":3040051,
"city_name":"les Escaldes",
"elevation":0,
"state_code":"08",
"state_name":"ParrĂ²quia d'Escaldes-Engordany",
"country_code":"AD",
"country_name":"Andorra"
},
{
"id":3041563,
"city_name":"Andorra la Vella",
"elevation":0,
"state_code":"07",
"state_name":"ParrĂ²quia d'Andorra la Vella",
"country_code":"AD",
"country_name":"Andorra"
}
]
This:
List<Location>
Is a List of Location. List implements Iterable.
This:
Array<Location>
is an Array of Location. Array does not implement Iterable.
The differences are bigger than that, but his is the one your error is for.
It was enough to swap List with Array and remove .toList() and it worked like magic