Kotlin, Realm и val — как ужиться?

sashatinkoff
О разработке для Android
3 min readMar 11, 2020

Представим, что у нас есть модель User, у которой есть имя, а также еще пара параметров, которые мы планируем не связывать с моделью в самой базе данных. Пусть это будет состояние онлайн (в зависимости от текущего подключения устройства к интернету), и его город.

Окей, согласен, пример не самый подходящий (я про город), но просто для того, чтоб показать как это может работать — должно сгодиться.

Итак, вот наша модель

@RealmClass
open class User(
@PrimaryKey var id: String = UUID.randomUUID().toString(),
var name: String = ""
) : DataModel

NB. Так как пример из реального проекта, необходимо пояснение. DataModel — это интерфейс, наследуемый от RealmModel, в нем я описываю CRUD экшены для модели. Не суть важно, углубляться не стоит, ниже я дам код этой модели, просто сейчас держите в голове — DataModel можно смело менять на RealmModel

Рассмотрим второй случай использования val в нашей модели — состояние онлайн. С ним все просто и работает из коробки

val isOnline: Boolean 
get() = App.instance.isInternetAvailable()

А так, как для этого поля используется геттер, то значение isOnline может варьироваться от текущего подключения к интернету (в рамках нашей небольшой задачки, конечно). В целом, такая запись абсолютно равносильна следующей

fun isOnline() = App.instance.isInternetAvailable()

Итак, что со вторым параметром — с городом?

Я хочу, чтобы город у нас подтягивался только один раз, да и то только тогда, когда он нужен. Понимаете к чему я клоню? Верно, к использованию конструкции ленивой инициализации lazy

Иногда нам нужно создавать объекты, которые имеют громоздкий процесс инициализации. Кроме того, часто мы не можем быть уверены, что объект, за который мы заплатили стоимость инициализации в начале нашей программы, вообще будет использоваться в нашей программе.
https://www.codeflow.site/ru/article/kotlin-lazy-initialization

Описывается, впрочем, несмотря на возможные ужасы описания, довольно просто

val city: City by lazy {
YRealm.get().where(City::class.java).findFirst() ?: City(name = "Ekat").save() as City
}

Проблема в том, что на этапе сборки компилятор сругается на невозможность использования такой схемы работы val в модели RealmModel. Сругается так, что откажется от дальнейшей сборки приложения. И тут к нам придет на помощь одна небольшая строка @delegate:Ignore, которая ставится перед описанием нашего параметра. В результате, код будет выглядеть вот так

@delegate:Ignore
val city: City by lazy {
YRealm.get().where(City::class.java).findFirst() ?: City(name = "Ekat").save() as City
}

А вот тут уже никто ругаться не будет.

P.S. Вот интерфейс DataModel

interface DataModel : RealmModel {
@CallSuper
fun save() = apply {
if (onSave())
YRealm.executeOnMainThread { it.insertOrUpdate(this) }
}


@CallSuper
fun delete() = apply {
YRealm.executeOnMainThread {
if (onDelete()) {
var item2 = this
if (!isManaged()) item2 = it.copyToRealmOrUpdate(item2)
item2.deleteFromRealm()
}
}
}

@CallSuper
fun update() = apply {
val json = YRealm.gson.toJson(arrayListOf((this)))
YRealm.executeOnMainThread { it.createOrUpdateAllFromJson(javaClass, json) }
}

@CallSuper
fun refresh() = apply { YRealm.refresh() }

/**
* Executes before saving the object
*
@return if true then the object will be saved
*/
fun onSave(): Boolean {
return true
}

/**
* Executes before deleting the object
*
@return if true then the object will be deleted
*/
fun onDelete(): Boolean {
return true
}

/**
* Executes before updating the object
* if some fields are null (or empty) in the UPDATING object they won't be updated in the object
* that is already is database
*
@return if true then the object will be updated
*/
fun onUpdate(): Boolean {
return true
}
}

P.P.S. Использование для переменных var аннотации Ignore в модели RealmModel работает из коробки:

@Ignore var sex = "male"

--

--

sashatinkoff
О разработке для Android

Пишу о разном с матом ем булку с маком никогда не бегал с автоматом