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)
}
Related
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.
I have an activity with two spinners. I have made arrays for each spinner containing data from popular foods, but I want the user to be able to add three of their own selections to the lists. The app compiles and installs and runs, BUT when I select the specific activity, the screen closes and either goes to the apps main screen or to the emulator's home screen. Logcat shows:-
java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.example.kotlinsql/com.example.kotlinsql.CarbsInput}: java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.SharedPreferences android.content.Context.getSharedPreferences(java.lang.String, int)' on a null object reference
It's where I call the shared preferences.
I have tried different contexts but still get errors that vary slightly according to the context and I have included them in the code as remarks.
I have tried moving everything into onCreate, but this gives me an error in the class definition line, because the function "override fun onItemSelected" seems to have to be stand-alone, so must be outside onCreate.
Please help. I have only been learning this for less than a year, and I apologise for any stupid mistakes. No offence is intended.
import android.content.Context
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.*
import kotlinx.android.synthetic.main.input_carbs.*
import java.time.Clock
import java.time.LocalDateTime
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import android.content.SharedPreferences
import android.content.res.Configuration
import java.security.AccessController.getContext
import kotlin.math.*
class CarbsInput : AppCompatActivity(),AdapterView.OnItemSelectedListener {
var spinner:Spinner? = null
var spinner2:Spinner? = null
val sharedPrefFile = "greenbandbasicpreference"
val sharedPreferences: SharedPreferences by lazy { getSharedPreferences(sharedPrefFile, MODE_PRIVATE) }
val dataModel: CarbsInputModel by lazy { CarbsInputModel(sharedPreferences) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.input_carbs)
spinner = this.gi_spinner
spinner2 = this.carbs_per_spinner
// Create an ArrayAdapter using a simple spinner layout and gIndices array
val aa = ArrayAdapter(this, android.R.layout.simple_spinner_item, dataModel.gIndices)
// Set layout to use when the list of choices appear
aa.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
// Set Adapter to Spinner
spinner!!.setAdapter(aa)
//spinner!!.setSelection(9)//optional, better to leave favourites at top
val aa2 = ArrayAdapter(this, android.R.layout.simple_spinner_item, dataModel.carbsPer)
aa2.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner2!!.setAdapter(aa2)
input_carbs_btn.setOnClickListener {//set an onclick listener
enterCarbs() }
backbtn.setOnClickListener {
val fourth = Intent(this, MainActivity::class.java)//sets "fourth" to be MainActivity
// start your next activity
startActivity(fourth) }
btn_view_carbs.setOnClickListener { viewCarbs() }
btn_carb_calc.setOnClickListener {
var carbPer = et_carbsper.text.toString().toLong()
var weight = et_weight.text.toString().toLong()
var carbs = round((weight * carbPer) /100.0).toLong()
et_carbs.setText(carbs.toString())
}//end of button onClick listener
}//end of on create
fun enterCarbs(){//get inputs from keys and calculate carbLife using GI
var noow = ZonedDateTime.now(Clock.systemUTC())
var noowSecs: Long = noow.toEpochSecond()
var noowMins: Long = (noowSecs) / 60
//var carbLife:Long = 220// this has to be calculated from GI
var nowLocal = LocalDateTime.now()
var carbTime: Long = noowMins-1
var showCarbTime: String = nowLocal.format(DateTimeFormatter.ofPattern("E d MMM kk:mm "))+"local"
var sharedPrefFile = "greenbandbasicpreference"
val sharedPreferences: SharedPreferences = getSharedPreferences(sharedPrefFile, Context.MODE_PRIVATE)
val databaseHandler: DatabaseHandler = DatabaseHandler(this)
if (et_carbs.text.toString().trim() != "" && et_carbGI.text.toString().trim() != "") {
val carbs = et_carbs.text.toString().toLong()
val carbGi = (et_carbGI.text.toString().toLong())
//val carbLife = 12_000 /carbGi.toLong()// to be replaced with 1-(X/L)^n calculation in stage2
var carbDecayIndex:Double= sharedPreferences.getFloat("carbDecayIndex_key",0.8F).toDouble()//n
//public fun carbLifeCalc():Double//L = 10^((log120^n-logGI)/n)
var logLtoN = log10(120.00.pow(carbDecayIndex))//log120^n
var logGi = log10(carbGi / 100.00)//logGI
var carbLife = 10.00.pow((logLtoN - logGi) / carbDecayIndex).toLong()//gives L
//end of carbLifeCalculation
val status =
databaseHandler.saveCarbs(CarbsModelClass(carbTime, showCarbTime, carbs, carbGi, carbLife))
if (status > -1) {
Toast.makeText(applicationContext, "Carbohydrate saved", Toast.LENGTH_LONG).show()
//MainActivity.evaluateCarbs //want to call this function from here without writing it again
et_carbs.text.clear()
et_carbGI.text.clear()
}
} else {
Toast.makeText(
applicationContext,
"No field can be blank enter GI as 50 if unknown",
Toast.LENGTH_LONG
).show()
}
}//end of function entercarbs
fun viewCarbs() {
//creating the instance of DatabaseHandler class
val databaseHandler: DatabaseHandler = DatabaseHandler(this)
//calling the viewCarbs method of DatabaseHandler class to read the records
val carbohs: List<CarbsModelClass> = databaseHandler.viewCarbs()
//val carbohsArraycarbTime = Array<String>(carbohs.size) { "null" }//not needed
val carbohsArrayshowCarbTime = Array<String>(carbohs.size) { "null" }
val carbohsArraycarbs = Array<String>(carbohs.size) { "null" }
val carbohsArraycarbGI = Array<String>(carbohs.size) { "null" }
val carbohsArraycarbLife = Array<String>(carbohs.size) { "null" }
var index = 0
for (e in carbohs) {
//carbohsArraycarbTime[index] = e.carbTime.toString()//not needed
carbohsArrayshowCarbTime[index] = e.showCarbTime
carbohsArraycarbs[index] = e.carbs.toString()
carbohsArraycarbGI[index] = e.carbGi.toString()//note small i inGi
carbohsArraycarbLife[index] = e.carbLife.toString()
//index--
index++
}
//creating custom ArrayAdapter
val myCarbListAdapter = CarbListAdapter(
context = this,
//carbTime = carbohsArraycarbTime,//not needed
showCarbTime = carbohsArrayshowCarbTime,
carbs = carbohsArraycarbs,
carbGI = carbohsArraycarbGI,
carbLife = carbohsArraycarbLife
)
lv_carb_view.adapter = myCarbListAdapter
}//end of fun view carbs
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
// see https://stackoverflow.com/questions/9262871/android-two-spinner-onitemselected
if(parent?.getId() == R.id.gi_spinner) {
var giFullSelected = dataModel.gIndices[position]
var gIprelimString : String =
giFullSelected[0].toString() + giFullSelected[1]//selecting just digits
var GIprelim = gIprelimString.toLong()
et_carbGI.setText(gIprelimString)
}//end of first if
else{ if (parent?.getId() == R.id.carbs_per_spinner) {
var carbPerFullSelected = dataModel.carbsPer[position]
var carbPerString: String =
carbPerFullSelected[0].toString() + carbPerFullSelected[1]
var carbPer = carbPerString.toLong()
et_carbsper.setText(carbPerString)
var weight = et_weight.text.toString().toLong()
var carbs = round((weight * carbPer) /100.0).toLong()
et_carbs.setText(carbs.toString())}//end of second if
else { Toast.makeText(applicationContext, "parent id "+parent?.getId().toString(), Toast.LENGTH_LONG).show()
}//end of second else
}//end of elseif OR /first else
}//end of on item selected
override fun onNothingSelected(parent: AdapterView<*>?) { }
}//end of class carbs input
New Class CarbsInputModel Below
//start of CarbsInputModel
import android.content.SharedPreferences
class CarbsInputModel(private val sharedPreferences:SharedPreferences) {
// val sharedPrefFile = "greenbandbasicpreference"
//val sharedPreferences:SharedPreferences = getSharedPreferences(sharedPrefFile, MODE_PRIVATE)
val sharedFav1Value: String? = sharedPreferences.getString("fav1_key", "50 50 defaultone")
val sharedFav2Value: String? = sharedPreferences.getString("fav2_key", "50 50 defaultwo")
val sharedFav3Value: String? = sharedPreferences.getString("fav3_key", "50 50 defaulthree")
val favDescr1:String = sharedFav1Value?.takeLastWhile { !it.isDigit() }.toString().trim()
val favDescr2:String = sharedFav2Value?.takeLastWhile { !it.isDigit() }.toString().trim()
val favDescr3:String = sharedFav3Value?.takeLastWhile { !it.isDigit() }.toString().trim()
val favData1:String = sharedFav1Value?.takeWhile { !it.isLetter() } .toString()
val favData2:String = sharedFav2Value?.takeWhile { !it.isLetter() } .toString()
val favData3:String = sharedFav3Value?.takeWhile { !it.isLetter() } .toString()
val favCarbPerString1 = favData1.take(3).trim()
val favCarbPerString2 = favData2.take(3).trim()
val favCarbPerString3 = favData3.take(3).trim()
val favGiString1 = favData1.takeLast(4).trim()
val favGiString2 = favData2.takeLast(4).trim()
val favGiString3 = favData3.takeLast(4).trim()
val favFullCarbPer1 = favCarbPerString1+" "+favDescr1+" "
val favFullCarbPer2 = favCarbPerString2+" "+favDescr2+" "
val favFullCarbPer3 = favCarbPerString3+" "+favDescr3+" "
val favFullGi1 = favGiString1+" "+favDescr1+" "
val favFullGi2 = favGiString2+" "+favDescr2+" "
val favFullGi3 = favGiString3+" "+favDescr3+" "
}//end of class Carbs Input Model
Attempted tidy code for override function. This still does absolutely Nothing
//trying to tidy up code
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
when {
parent.id == R.id.gi_spinner -> {
var giFullSelected = dataModel.gIndices[position]
var gIprelimString: String =
giFullSelected[0].toString() + giFullSelected[1]//selecting just leading digits
et_carbGI.setText(gIprelimString)
}
parent.id == R.id.carbs_per_spinner -> {
var carbPerFullSelected = dataModel.carbsPer[position]
var carbPerString: String =
carbPerFullSelected[0].toString() + carbPerFullSelected[1]
var carbPer = carbPerString.toLong()
et_carbsper.setText(carbPerString)
var weight = et_weight.text.toString().toLong()
var carbs = round((weight * carbPer) / 100.0).toLong()
et_carbs.setText(carbs.toString())
}
else -> {
Toast.makeText(applicationContext, "parent id " + parent?.getId().toString(),
Toast.LENGTH_LONG ).show()
}
}//end of when
}//end of override fun onitemselected
When you assign a value at the declaration site like this:
val sharedPreferences:SharedPreferences = getSharedPreferences(sharedPrefFile, MODE_PRIVATE)
the function(s) you are calling to create the object that will be assigned to the property is getting called at the time the Activity is instantiated by Android. Unfortunately, this is too early to be calling anything that relies on the Activity being fully instantiated and set up, for example, anything that needs the Context as a constructor parameter.
The easy fix for this is to make these properties instantiate themselves lazily, so they are created after the Activity is already fully instantiated:
val sharedPreferences: SharedPreferences by lazy { getSharedPreferences(sharedPrefFile, MODE_PRIVATE) }
An alternate solution is the use a lateinit var and prepare the item in onCreate():
lateinit var sharedPreferences: SharedPreferences
// ...
override fun onCreate(bundle: SavedInstanceState) {
super.onCreate(bundle)
sharedPreferences = getSharedPreferences(sharedPrefFile, MODE_PRIVATE)
}
I usually prefer the lazy method because it avoid splitting the declaration and the assignment so the code is easier to read. And it allows you to use val instead of var so the intent is clearer.
However, you also have many properties that are reliant on the SharedPreference instance, so they would all have to use one of the above solutions as well, which will lead to very verbose code. I recommend that you move all of these properties into a separate class that uses the SharedPreferences as a constructor paraamter. For example:
class CarbsInputModel(private val sharedPreferences: SharedPreferences) {
val sharedFav1Value: String? = sharedPreferences.getString("fav1_key", "50 50 defaultone")
val sharedFav2Value: String? = sharedPreferences.getString("fav2_key", "50 50 defaultwo")
val sharedFav3Value: String? = sharedPreferences.getString("fav3_key", "50 50 defaulthree")
// etc...
}
and then in your activity:
class CarbsInput : AppCompatActivity(),AdapterView.OnItemSelectedListener {
var spinner:Spinner? = null
var spinner2:Spinner? = null
val sharedPrefFile = "greenbandbasicpreference"
val sharedPreferences: SharedPreferences by lazy { getSharedPreferences(sharedPrefFile, MODE_PRIVATE) }
val dataModel: CarbsInputModel by lazy { CarbsInputModel(sharedPreferences) }
}
And then access your properties through the dataModel property. It is also better design practice to separate your UI and your functions that modify the data, so you could put those functions in your data model class.
You might also want to read up on how to use a ViewModel class. It would possibly be a more scalable solution than what I put above.
I want to save data acquired from Volley, But lambda used in VolleyRequest function(which gets json data from server) blocks it.
How should I change local variable that is in outside of lambda?
Thanks in advance.
class ConDataforReturn( val title:String , val imgDataList: ArrayList<ConImgData>)
fun getConData(context: Context, idx : String):ConDataforReturn{
val params = HashMap<String,String>()
var cd = arrayListOf<ConImgData>()
var title =""
params.put("package_idx",idx)
Log.e("idx size",idx.length.toString())
VolleyRequest(context,params,"https://dccon.dcinside.com/index/package_detail") { response ->
val answer = JSONObject(response)
var json = answer.getJSONArray("detail")
title = answer.getJSONObject("info").getString("title")
Log.d("title",title)//Prints right data
for (i in 0..(json.length() - 1)) {
val v = json.getJSONObject(i)
cd.add(ConImgData(v.getString("title"), v.getString("ext"), v.getString("path")))
}
}
return ConDataforReturn(title,cd)//returns ConDataforReturn("",arrayListOf<ConImgData>())
}
Here the the code from were you are calling this method
getConData(this, "id") { condata ->
}
Now, your method look like this,
fun getConData(context: Context, idx : String, returnConData : (condata : ConDataforReturn) -> Unit){
val params = HashMap<String,String>()
var cd = arrayListOf<ConImgData>()
var title =""
params.put("package_idx",idx)
Log.e("idx size",idx.length.toString())
VolleyRequest(context,params,"https://dccon.dcinside.com/index/package_detail") { response ->
val answer = JSONObject(response)
var json = answer.getJSONArray("detail")
title = answer.getJSONObject("info").getString("title")
Log.d("title",title)//Prints right data
for (i in 0..(json.length() - 1)) {
val v = json.getJSONObject(i)
cd.add(ConImgData(v.getString("title"), v.getString("ext"), v.getString("path")))
}
returnConData(ConDataforReturn(title,cd)) //returns ConDataforReturn("",arrayListOf<ConImgData>())
}
}
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
I have trouble with the management with one-to-many relations between Kotlin objects using Realm. I don't understand how to do some insert or update.
Here my two objects with one-to-many relation between exam and career.
open class Career : RealmObject() {
#PrimaryKey
var id: Long = 1
var status: String = CareerStatus.ACTIVE.value
var careerName: String? = null
var exams: RealmList<Exam>? = null
}
open class Exam : RealmObject() {
#PrimaryKey
var id: Long = 1
var teachers: String? = null
var lode: Boolean? = null
var score: String = ""
var exam: String = ""
var examDescription: String? = null
var date: Date = Date()
var cfu: Long = Long.MIN_VALUE
#LinkingObjects("exams")
val career: RealmResults<Career>? = null
}
Here the two class Manager the manage the read/write on Realm
class CareerManager : DatabaseManager() {
fun findActiveCareer(): Career? {
return realm.where(Career::class.java).equalTo("status", CareerStatus.ACTIVE.value).findFirst()
}
fun insertExamInCareer(exam: Exam) {
realm.executeTransaction {
var activeCareer: Career = findActiveCareer()
?: throw Resources.NotFoundException("No active career found")
activeCareer.exams?.add(exam)
realm.copyToRealmOrUpdate(exam)
}
}
fun closeActiveCareer() {
realm.executeTransaction {
val activeCareer: Career = findActiveCareer()
?: throw Resources.NotFoundException("No active career found")
activeCareer.status = CareerStatus.TERMINATED.value
}
}
}
class ExamManager : DatabaseManager() {
fun findAll(): List<Exam> {
val findAll = realm.where(Exam::class.java).findAll()
return realm.copyFromRealm(findAll).toList()
}
fun findAllActive(): List<Exam> {
val findAll = realm.where(Exam::class.java).equalTo("career.status", CareerStatus.ACTIVE.value).findAll()
return realm.copyFromRealm(findAll).toList()
}
fun insert(exam: Exam): Long {
realm.beginTransaction()
var newId: Long = 1
if (realm.where(Exam::class.java).max("id") != null) {
newId = realm.where(Exam::class.java).max("id") as Long + 2
}
val examToSave = realm.createObject(Exam::class.java, newId)
examToSave.exam = exam.exam
examToSave.examDescription = exam.examDescription
examToSave.date = exam.date
examToSave.teachers = exam.teachers
examToSave.score = exam.score
examToSave.lode = exam.lode
examToSave.cfu = exam.cfu
realm.commitTransaction()
CareerManager().updateCareerExam(findAll())
return newId
}
}
In my application I need to insert some exams in my career that is actually in ACTIVE state. For the insert I use CareerManager().insertExamInCareer(exam) this call (before I try to use ExamManager().insert(exam) but doesn't work).
Another use case is when I need to close a career and add the exam to another career. I do this in this manner
CareerManager().closeActiveCareer()
CareerManager().createCareer()
CareerManager().insertExamInCareer(exam)
The exam in the two career are the same, but they had to be different and in Exam there must have been two records, not one