How To Dynamically Change Properties In ViewModel - android

So in my DAO, I have a query like this
#Query("Select * from transaction_history where wallet_type=:walletType AND email=:email AND transaction_type='Expenses' AND date BETWEEN :from AND :to")
fun getLastSevenDaysExpensesTransaction(walletType: String, email:String, from: Long?, to: Long?) : LiveData<List<ExpensesTransaction>>?
#Query("Select * from transaction_history where wallet_type=:walletType AND email=:email AND transaction_type='Income' AND date BETWEEN :from AND :to")
fun getLastSevenIncomeTransaction(walletType: String, email:String, from: Long?, to: Long?) : LiveData<List<IncomeTransaction>>
And in my viewModel, this is how I call getLastSevenDaysExpensesTransaction method
class GraphViewModel(dataSource: NetWalletDatabaseDao, application: Application, email: String, walletType: String) : ViewModel() {
val database = dataSource
var from : Long = 0L
var to : Long = 0L
val lastSevenDaysIncome = database.getLastSevenIncomeTransaction(walletType, email, from, to)
val lastSevenDaysExpenses = database.getLastSevenDaysExpensesTransaction(walletType, email, from, to)
fun funcLastSevenDaysIncome(fromParam: Long, toParam: Long) {
from = fromParam
to = toParam
}
And in my Fragment, this is how I display the data
val application = requireNotNull(this.activity).application
val dataSource = NetWalletDatabase.getInstance(application).netWalletDatabaseDao
val viewModelFactory = GraphViewModelFactory(
dataSource,
application,
getEmail.toString(),
getWalletType.toString(),
)
val viewModel =
ViewModelProvider(this, viewModelFactory).get(GraphViewModel::class.java)
val tvLastSevenDays = binding.tvLastSevenDays
viewModel.funcLastSevenDaysIncome(sevenDaysInMili, todayMili)
val chart : LineChart = binding.chart
val expenses = ArrayList<Entry>()
viewModel.lastSevenDaysMut.observe(viewLifecycleOwner, Observer { list ->
list?.let {
for (i in 0..list.size - 1) {
expenses.add(Entry(i.toFloat(), list.get(i).value!!.toFloat()))
}
Log.e("Result", list.get(0).value!!.toString())
}
val expensesLineDataSet = LineDataSet(expenses, "Expenses")
expensesLineDataSet.mode = LineDataSet.Mode.CUBIC_BEZIER
expensesLineDataSet.color = Color.BLUE
expensesLineDataSet.circleRadius = 5f
expensesLineDataSet.setCircleColor(Color.BLUE)
val income = ArrayList<Entry>()
viewModel.lastSevenDaysMut.observe(viewLifecycleOwner, Observer { list ->
list?.let {
for (i in 0..list.size - 1) {
income.add(Entry(i.toFloat(), list.get(i).value!!.toFloat()))
Log.e("Result", list.get(0).value!!.toString())
}
}
val incomeLineDataSet = LineDataSet(income, "Income")
incomeLineDataSet.mode = LineDataSet.Mode.CUBIC_BEZIER
incomeLineDataSet.color = Color.RED
incomeLineDataSet.circleRadius = 5f
incomeLineDataSet.setCircleColor(Color.RED)
val legend = chart.legend
legend.isEnabled = true
legend.setVerticalAlignment(Legend.LegendVerticalAlignment.TOP)
legend.setHorizontalAlignment(Legend.LegendHorizontalAlignment.CENTER)
legend.setOrientation(Legend.LegendOrientation.HORIZONTAL)
legend.setDrawInside(false)
chart.description.isEnabled = false
chart.xAxis.position = XAxis.XAxisPosition.BOTTOM
chart.data = LineData(expensesLineDataSet, incomeLineDataSet)
chart.animateXY(100, 500)
})
})
The problem is that the data won't show. What I understand so far why the data won't show is that, when val lastSevenDaysIncome = database.getLastSevenIncomeTransaction(walletType, email, from, to) and val lastSevenDaysExpenses = database.getLastSevenDaysExpensesTransaction(walletType, email, from, to) from my viewModel is run, the properties of var from : Long = 0L and var to : Long = 0L is still zero even though in my Fragment, I have called the method to change those properties viewModel.funcLastSevenDaysIncome(sevenDaysInMili, todayMili).
So how do I fix it? How do I change the properties var from : Long = 0L and var to : Long = 0L before they are used for query?
P.S. I used to put all the necessary parameters for query in constructor. For example
class GraphViewModel(dataSource: NetWalletDatabaseDao, application: Application, email: String, walletType: String, from: Long, to: Long) : ViewModel()
and use it for example
val lastSevenDaysIncome = database.getLastSevenIncomeTransaction(walletType, email, from, to)
But, since "from" and "to" is dynamic, I can't use it anymore because then in my fragment, I have to initialize the viewModelProvider something like this
val viewModelFactory = GraphViewModelFactory(
dataSource,
application,
getEmail.toString(),
getWalletType.toString(),
***dynamic from*
*dynamic to***
)
Thank you in advance.

Related

How to write and read Android Realm?

I need to cache data in Realm in Android/Kotlin project.
When I write and then read – I get nothing. Previously I could write once, so I know that reading code works. But writes do nothing. Then I reset emulator and now I can't read anything. What I do wrong?
I tried to follow and an official example and the mistake slips away from me.
I have a data class:
open class DataItemExtra: RealmObject {
#PrimaryKey
var id: String? = null
var strVal: String = ""
var intVal: Int = 0
var extra : String = "extra"
constructor(id: String?, s: String, n: Int){
this.id = id
this.strVal = s
this.intVal = n
}
constructor()
}
and a code in activity:
class MainActivity : AppCompatActivity() {
lateinit var realm: Realm
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val buttonSet = findViewById<Button>(R.id.buttonSet)
val buttonGet = findViewById<Button>(R.id.buttonGet)
val editTextString = findViewById<EditText>(R.id.editText)
val editTextInt = findViewById<EditText>(R.id.editNum)
val textRes = findViewById<TextView>(R.id.textRes)
Realm.init(this)
val realmName = "My Project"
val config = RealmConfiguration.Builder()
.schemaVersion(1)
.deleteRealmIfMigrationNeeded()
.name(realmName)
.build()
this.realm = Realm.getInstance(config)
buttonSet.setOnClickListener{
val strVal = editTextString.text.toString()
val intVal = editTextInt.text.toString().toInt()
Toast.makeText(this, "$strVal : $intVal",Toast.LENGTH_LONG).show()
val di = DataItemExtra("keyVal", strVal, intVal)
Thread{
this.realm.executeTransaction { transactionRealm ->
transactionRealm.insertOrUpdate(di)
}
}
}
buttonGet.setOnClickListener{
val tasks : RealmResults<DataItemExtra> = this.realm.where<DataItemExtra>().findAll()
textRes.text = ""
val r = tasks.toArray()
val rSize = r.size
if (rSize != 1) {
textRes.text = "wrong items number $rSize"
} else {
val d = tasks[0]
textRes.text = "${d?.id}, ${d?.strVal}, ${d?.intVal}, ${d?.extra}"
}
tasks.forEach { d ->
Log.d("REALM-RES","${d.id}, ${d.strVal}, ${d.intVal}, ${d.extra}")
}
}
}
}
Well, that's some threading issues. I didn't figure out what it is yet, but the right way to write data is to run executeTransactionAsync instead of running separate thread:
realm.executeTransactionAsync { transactionRealm ->
transactionRealm.insertOrUpdate(di)
}

How to get my retrofit2 model to parse this?

This is unusuall response where the name of object is the same as the object's ID and at this point I don't really know how to parse this response
"addresses": {
"163492": {
"address_id": "163492",
//more of String variables
},
"166127": {
"address_id": "166127",
//more of String variables
},
"166202": {
"address_id": "166202",
//more of String variables
}
}
this is how my Event model looks like, I'm using room database to save this response later
#Entity
data class Event(
#PrimaryKey(autoGenerate = false)
#SerializedName("id") val id: Int,
#SerializedName("title") val title: String,
#SerializedName("description") val desc: String,
#SerializedName("note") val note: String? = null,
#SerializedName("date") val dateTs: Long,
#SerializedName("begintime") val beginTime: String,
#SerializedName("enddate") val endDate: String,
#SerializedName("endtime") val endTime: String,
#SerializedName("customerid") val customerId: String? = null,
#SerializedName("address_id") val addressId: String? = null,
#SerializedName("pin") val pin: String? = null,
#SerializedName("location") val location: String? = null,
#SerializedName("customerlocation") val customerLocation: String? = null,
#field:TypeConverters(beskidmedia.pl.scanner.room.TypeConverters::class)
#SerializedName("nodes") val nodes: List<Node>? = null,
#SerializedName("closed") val closed: Int,
#SerializedName("type") val type: Int,
#SerializedName("ticketid") val ticketId: String? = null,
#SerializedName("customername") val customerName: String? = null,
#field:TypeConverters(beskidmedia.pl.scanner.room.TypeConverters::class)
#SerializedName("contacts") val contacts: List<Contacts>? = null,
#field:TypeConverters(beskidmedia.pl.scanner.room.TypeConverters::class)
#SerializedName("addresses") val addresses: List<Address>? = null,
#Embedded
#SerializedName("assignments") val assignments: Assignments? = null,
#SerializedName("lastUpdate") val lastUpdate: Long = System.currentTimeMillis()
)
everything beside the addresses part is fine cos I tested it using response with null for addresses, I tried to do deserializer for this but it appears like it don't recognise it, this is how it looks like
class EventDeserializer : JsonDeserializer<Event> {
override fun deserialize(
json: JsonElement?,
typeOfT: Type?,
context: JsonDeserializationContext?
): Event {
json?.asJsonObject!!.let { event ->
val nodes = mutableListOf<Node>()
val contacts = mutableListOf<Contacts>()
val addresses = mutableListOf<Address>()
val net = mutableListOf<Assignment>()
val tv = mutableListOf<Assignment>()
val assignments = Assignments(net, tv)
val netTemp = event.get("assignments").asJsonObject.get("assignments_net").asJsonArray
val tvTemp = event.get("assignments").asJsonObject.get("assignments_tv").asJsonArray
netTemp.forEach { assignment ->
assignment.asJsonObject.let {
net.add(
Assignment(
name = it.get("name").asString,
id = it.get("id").asInt
)
)
}
}
tvTemp.forEach { assignment ->
assignment.asJsonObject.let {
tv.add(
Assignment(
name = it.get("name").asString,
id = it.get("id").asInt
)
)
}
}
val nodesTemp = event.get("nodes").asJsonArray
nodesTemp.forEach { node ->
node.asJsonObject.let {
nodes.add(
Node(
id = it.get("id").asInt,
name = it.get("name").asString,
mac = it.get("mac").asString,
ip = it.get("ip").asString,
location = it.get("location").asString,
netName = it.get("netname").asString
)
)
}
}
val contactsTemp = event.get("contacts").asJsonArray
contactsTemp.forEach { contact ->
contact.asJsonObject.let {
contacts.add(
Contacts(
phone = it.get("phone").asString,
contact = it.get("contact").asString,
name = it.get("name").asString,
type = it.get("type").asString,
typeStr = it.get("typestr").asString
)
)
}
}
val addressesTemp = event.get("addresses").asJsonObject
addressesTemp?.keySet()?.let { names ->
names.forEach { name ->
addressesTemp.get(name).asJsonObject.let {
addresses.add(
Address(
id = it.get("address_id").asString,
name = it.get("location").asString
)
)
}
}
}
return Event(
id = event.get("id").asInt,
title = event.get("title").asString,
desc = event.get("description").asString,
note = event.get("note")?.asString,
dateTs = event.get("date").asLong,
beginTime = event.get("begintime").asString,
endDate = event.get("enddate").asString,
endTime = event.get("endtime").asString,
customerId = event.get("customerid")?.asString,
addressId = event.get("address_id")?.asString,
pin = event.get("pin")?.asString,
location = event.get("location")?.asString,
customerLocation = event.get("customerlocation")?.asString,
nodes = nodes,
closed = event.get("closed").asInt,
type = event.get("type").asInt,
ticketId = event.get("ticketid")?.asString,
customerName = event.get("customername")?.asString,
contacts = contacts,
addresses = addresses,
assignments = assignments
)
}
}
}
and this is how I'm creating gson factory
val gson = GsonBuilder().registerTypeAdapter(Event::class.java, EventDeserializer())
Retrofit
.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(
GsonConverterFactory.create(gson.create())
)
.callbackExecutor(Executors.newSingleThreadExecutor())
and the structure of response looks like this
Call<List<Event>>
but the list always have 1 element and this is artifact of old api that i can't change
Ok, so I figured this out, apparently your deserializer needs to be the exactly the same type as your response, so I added the interceptor that removed excess array that wrapped every response and now deserializer is being ussed as intended.

Type mismatch: inferred type is com.google.firebase.firestore.auth.User but com.example.socialmediaapp.model.User was expected

Error is showing for the user when passed in post i.e val post = Post(text, user, currentTime)
class PostDao {
private val db = FirebaseFirestore.getInstance()
private val postCollection = db.collection("posts")
private val auth = Firebase.auth
fun addPost(text:String){
val currentUserId = auth.currentUser!!.uid
GlobalScope.launch {
val userDao = UserDao()
//getting actual user document from the task through user id
val user = userDao.getUserById(currentUserId).await().toObject(User::class.java)!!
val currentTime = System.currentTimeMillis()
val post = Post(text, user, currentTime)
}
}
}
Here is the Post data class :
data class Post(
val text:String = "",
val createdBy: User = User(),
val createdAt:Long = 0L,
val likedBy: ArrayList<String> = ArrayList()
)
Please help me solve this issue.
Thank you !
This is because you are not using same user object at both of the places. You need to declare and get same type of USER class in your code.
You are trying to get the value of com.google.firebase.firestore.auth.User but com.example.socialmediaapp.model.User is expected as per your code.

Insert 3000 rows into Room Database but Method too large

I'm trying to prepopulate a database, I generated the insert data but
when I run the app it gives this build error:
Caused by: org.jetbrains.org.objectweb.asm.MethodTooLargeException: Method too large:
I have a funtion like this:
RoomDatabase.Callback(){
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
val pDao = database.get().pDao()
val pLangDao = database.get().pLangDao()
applicationScope.launch {
insertPWithPLangEnglish_1_10(pDao, pLangDao)
}
}
}
private suspend fun insertPWithPLangEnglish_1_10(pDao: PDao, pLangDao: PLangDao){
var insId = pDao.insert(P(pcId = 1))
pLangDao.insert(PLang(pItemId = insId.toInt(), title = "herbert", locale = "en_US", langCode = "en"))
insId = pDao.insert(P(pcId = 1))
pLangDao.insert(PLang(pItemId = insId.toInt(), title = "others", locale = "en_US", langCode = "en"))
... and so on about 3000 more lines
}
Any idea how to solve this?
P class is the following:
#Entity(tableName = "p")
#Parcelize
data class P (
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "pid")
val pId: Int = 0,
#ColumnInfo(name = "pc_id")
val pcId: Int,
val created: Long = System.currentTimeMillis()
) : Parcelable {
}
PLang class is the following:
#Entity(tableName = "p_lang")
#Parcelize
data class PLang (
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "p_lang_id")
val pLangId: Int = 0,
#ColumnInfo(name = "p_item_id")
val pItemId: Int,
val locale: String = "",
#ColumnInfo(name = "lang_code")
val langCode: String,
val title: String,
val description: String = ""
) : Parcelable {
}
Now I'm trying another way with help of Workers, and seed database from json files.
So clearly, the Kotlin compiler is complaining because your method is too long and you should optimize it.
In order to do that you could create a global list containing all your PLang objects:
val pLangList = listOf(
PLang(title = "herbert", locale = "en_US", langCode = "en"),
PLang(title = "others", locale = "en_US", langCode = "en"),
...
)
Note that I didn't set the pItemId property, in fact, you should also change your PLang class, assigning a default value to it for convenience:
#Entity(tableName = "p_lang")
#Parcelize
data class PLang (
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "p_lang_id")
val pLangId: Int = 0,
#ColumnInfo(name = "p_item_id")
val pItemId: Int = -1, // <--- Added default value here
val locale: String = "",
#ColumnInfo(name = "lang_code")
val langCode: String,
val title: String,
val description: String = ""
) : Parcelable {
}
Now you can loop through your list and add every item with just 3 lines of code instead of ~3000:
private suspend fun insertPWithPLangEnglish_1_10(pDao: PDao, pLangDao: PLangDao)
{
for (pLang in pLangList)
{
val insId = pDao.insert(P(pcId = 1))
pLangDao.insert(PLang(pItemId = insId.toInt(), title = pLang.title, locale = pLang.locale, langCode = pLang.langCode))
}
}

Android Kotlin save data class in Firebase Database

I am working on an Android application in Kotlin which integrate Firebase.
Now I want to store my data (Kotlin data class) into Firebase Database.
Data Classes:
#Parcelize
data class Trip(
val fromAddress: String,
val toAddress: String,
val fromLocation: String,
val toLocation: String,
val orderUid: String
) : Parcelable
#Parcelize
data class Order(val trip: Trip, val date: Date, var status: OrderStatus, val userUid: String) : Parcelable {
var pickUpDate: Date? = null
var dropOffDate: Date? = null
var price: Double? = null
}
Fireabase Database write operation:
fun createNewOrder(
fromAddress: String,
toAddress: String,
fromLocation: Location,
toLocation: Location
) {
val fromGeoLocation = fromLocation.convertToGeoLocation()
val toGeoLocation = toLocation.convertToGeoLocation()
val userUid = sharedPreferences[CURRENT_USER_UID_KEY, ""]!!
val orderKey = databaseReference.child(DB_ORDERS_KEY).push().key
val tripKey = databaseReference.child(DB_TRIPS_KEY).push().key
val trip = orderKey?.let { createNewTrip(fromAddress, toAddress, it) }
val order = trip?.let { Order(it, Date(), OrderStatus.PENDING, userUid) }
if (trip != null && order != null && !userUid.isNullOrEmpty()) {
ordersGeoFire.setLocation(trip.fromGeoLocation, fromGeoLocation)
ordersGeoFire.setLocation(trip.toGeoLocation, toGeoLocation)
val allData = mutableMapOf<String, Any>()
allData["/$DB_TRIPS_KEY/$tripKey"] = trip?.convertToMap()
allData["/$DB_ORDERS_KEY/$orderKey"] = order?.convertToMap()
allData["/$DB_USERS_KEY/$userUid/$DB_ORDERS_KEY/$orderKey"] = true
databaseReference.updateChildren(allData)
}
}
I received this error:
com.google.firebase.database.DatabaseException: No properties to serialize found on class kotlin.Unit
Any suggestions?
The problem in your code is that the fileds inside your Trip class are not initialized. A recommended way in which you can create your model class would be:
class Trip(
val displayName: String = "",
val email: String = "",
val photoUrl: String = "",
val userId: String = ""
)
This is only what you need. And a way to create a new object of your Trip class, would be:
val trip = Trip(displayName, email, photoUrl, userId)
It was my mistake, because I was forget to add return type in my extensions convertToMap functions. Now they look like this:
fun Trip.convertToMap(): MutableMap<String, Any> {
val map = mutableMapOf<String, Any>()
map["fromAddress"] = fromAddress
map["toAddress"] = toAddress
map["fromGeoLocation"] = fromGeoLocation
map["toGeoLocation"] = toGeoLocation
map["orderUid"] = orderUid
return map
}
And also thanks to #Alex Mamo for his answer, it helps me in my investigation.
Now my code looks like this and works fine:
#Parcelize
data class Trip(
var fromAddress: String = "",
var toAddress: String = "",
var fromGeoLocation: String = "",
var toGeoLocation: String = "",
var orderUid: String = ""
) : Parcelable
#Parcelize
data class Order(
var trip: Trip? = null,
var date: Date? = null,
var status: OrderStatus? = null,
var userUid: String = ""
) : Parcelable {
var pickUpDate: Date? = null
var dropOffDate: Date? = null
var price: Double? = null
}
fun createNewOrder(
fromAddress: String,
toAddress: String,
fromLocation: Location,
toLocation: Location
): LiveData<Order> {
orderLiveData = MutableLiveData()
orderLiveData.value = null
val userUid = sharedPreferences[CURRENT_USER_UID_KEY, ""]!!
val orderKey = databaseReference.child(DB_ORDERS_KEY).push().key
val tripKey = databaseReference.child(DB_TRIPS_KEY).push().key
val trip = orderKey?.let { createNewTrip(fromAddress, toAddress, fromLocation, toLocation, it) }
val order = trip?.let { Order(it, Date(), OrderStatus.PENDING, userUid) }
if (trip != null && order != null && !userUid.isNullOrEmpty()) {
val allData = mutableMapOf<String, Any>()
allData["/$DB_TRIPS_KEY/$tripKey"] = trip.convertToMap()
allData["/$DB_ORDERS_KEY/$orderKey"] = order.convertToMap()
allData["/$DB_USERS_KEY/$userUid/$DB_ORDERS_KEY/$orderKey"] = true
databaseReference.updateChildren(allData) { databaseError, databaseReference ->
if (databaseError == null) orderLiveData.value = order
}
}
return orderLiveData
}
Hope this will be helpful

Categories

Resources