Android Kotlin: Realm many to one relation - android

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

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 use Transformations to set multiple properties for a LiveData object?

I have these types:
data class Match(
#PrimaryKey var uid: Long? = null,
#ColumnInfo var homeTeamId: Long? = null,
#ColumnInfo var awayTeamId: Long? = null,
#Ignore var homeTeam: Team? = null,
#Ignore var awayTeam: Team? = null
)
data class Team(
#PrimaryKey var uid: Long? = null,
#ColumnInfo var captainId: Long? = null,
#Ignore var captain: Player? = null
)
data class Player(
#PrimaryKey var uid: Long? = null,
#ColumnInfo var name: String? = null,
var number: Int? = null
)
and I'm getting match as a LiveData:
// inside fragment
matchDao.getLive(matchId).observe(this, Observer {})
I want to set homeTeam on match using the homeTeamId property. How can I use transformations to set the values for homeTeam, awayTeam and captain?
This is how far I have gotten:
fun getMatchLive(id: Long): LiveData<Match> {
return Transformations.switchMap(matchDao.getLive(id)) { inputMatch ->
val output1: LiveData<Match>? = inputMatch.homeTeamId?.run {
Transformations.switchMap(teamDao.getLive(this)) { team ->
team.captainId?.run {
Transformations.map(playerDao.getLive(this)) { player ->
team.apply {
captain = player
inputMatch.homeTeam = team
}
inputMatch
}
}
}
}
output1?.run {
Transformations.switchMap(this) { inputMatch1 ->
inputMatch1.awayTeamId?.run {
Transformations.switchMap(teamDao.getLive(this)) { team ->
team.captainId?.run {
Transformations.map(playerDao.getLive(this)) { player ->
team.apply {
captain = player
inputMatch1.awayTeam = team
}
inputMatch1
}
}
}
}
}
}
}
}
but I'm almost certain that's neither the right way to do it nor is it maintainable. For example, if I want to add a new property to the match variable, I have to transform the result of output1.run.

How to copy a property between 2 lists of different types using declarative Kotlin?

Context
Using a declarative approach in Kotlin, need to copy a single name property from List of User objects to a List of UserDetail objects based on matching id properties as shown below:
val users = Arrays.asList(
User(1, "a"),
User(2, "b")
)
val details = Arrays.asList(
UserDetail(1),
UserDetail(2)
)
val detailsWithName = copyNameToUser(users, details)
Models are:
class User {
var id = -1;
var name = "" // given for all Users
constructor(id: Int, name: String)
// ...
}
class UserDetail {
var id = -1;
var name = "" // blank for all UserDetails
constructor(id: Int)
// ...
}
Problem
Tried to use a declarative approach via forEach iterable function:
fun copyNameToDetails(users: List<User>, details: List<UserDetail>): List<UserDetail> {
details.forEach(d ->
users.forEach(u ->
if (d.id == u.id) {
d.name = u.name
}
)
)
return details
}
This can be achieved in Java as shown below:
private static List<UserDetail> copyNameToDetails(List<User> users, List<UserDetail> details) {
for (UserDetail d: details) {
for (User u : users) {
if (d.id == u.id) {
d.name = u.name;
}
}
}
return details;
}
Question
How can this be done in Kotlin using a declarative approach?
You make too many iterations over both lists (users.size * details.size) so creating a hashmap can fix it a bit:
fun copyNameToUsers(users: List<User>, details: List<UserDetail>): List<UserDetail> {
val usersById = users.associate { it.id to it }
details.forEach { d ->
usersById[d.id]?.let { d.name = it.name }
}
return details
}
An other approach with non mutable values :
data class User(val id: Int = -1, val name: String = "")
data class UserDetail(val id: Int = -1, val name: String = "")
private fun List<UserDetail>.copyNameToUser(users: List<User>): List<UserDetail> = map { userDetail ->
users.firstOrNull { userDetail.id == it.id }?.let { userDetail.copy(name = it.name) } ?: userDetail
}

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

DbFlow Kotlin and List<String> Type Converter

DBFlow Version: 4.0.4
Hi, I'm strugling with List Type converter with dbflow Android ORM and Kotlin. I have a data class defined like this:
#Table(database = StopsDb::class)
data class FavouriteStop(
#PrimaryKey #Column var id: String = "",
#Index #Column var name: String = "",
#Column(typeConverter = StringListConverter::class) var directions: List<String> = listOf(),
#Column(typeConverter = StringListConverter::class) var selectedDirections: List<String> = listOf()
) : BaseRXModel()
and as I don't want to create a separate table only to store Strings I created a List Type converter like this:
class StringListConverter : TypeConverter<String, List<String>>() {
val separator = ","
override fun getDBValue(model: List<String>?): String {
if(model==null || model.isEmpty())
return ""
else
return model.joinToString (separator = separator){ it }
}
override fun getModelValue(data: String?): List<String> {
return data?.split(separator) ?: listOf()
}
}
however following error is thrown during build phase:
Error:error: *==========* :The specified custom TypeConverter's Model Value java.util.List<? extends java.lang.String> from com.kapuscinski.departures.persistence.db.StringListConverter must match the type of the column java.util.List<java.lang.String>.*==========*
Am I missing something here, and how to fix this? Thanks in advance for help!
Change everything from "List" to "MutableList"
#Table(database = StopsDb::class)
data class FavouriteStop(
#PrimaryKey #Column var id: String = "",
#Index #Column var name: String = "",
#Column(typeConverter = StringListConverter::class) var directions: MutableList<String> = mutableListOf(),
#Column(typeConverter = StringListConverter::class) var selectedDirections: MutableList<String> = mutableListOf()
) : BaseRXModel()
class StringListConverter : TypeConverter<String, MutableList<String>>() {
val separator = ","
override fun getDBValue(model: MutableList<String>?): String =
if (model == null || model.isEmpty())
""
else
model.joinToString(separator = separator) { it }
override fun getModelValue(data: String?): MutableList<String> {
return data?.split(separator)?.toMutableList() ?: mutableListOf()
}
}

Categories

Resources