Realm db DynamicRealmObject not valid during migration - android

I've been working with Realm for well over a year so I'm not new to the whole migration flow but this has me scratching my head for several days:
During a data migration to a new schema version I need to create some objects and insert them to the DB, and later connect them to another type of objects.
First I create a map of the dynamic objects so I could connect them to the second type later:
val generatedStoreVisitTypes = mutableMapOf<String, DynamicRealmObject>()
Then I create and use the dynamicObjects:
fun migrateToVersion19(realm: DynamicRealm) {
// an extension method I created which adds the field if it doesn’t exist already, impl at the bottom
realm.schema.getOrCreate<RealmMetadata>()
// an extension method I created which adds the field if it doesn’t exist already, impl at the bottom
.safeAddRealmListField(RealmMetadata::storeVisitTypes, realm.schema)
.transform { metadata ->
// I use the string name of the property here and not reflection since this field is deleted during this migration
val currentStoreTaskList = metadata.getList("storeTasks")
currentStoreTaskList.forEach { storeTasks ->
// create an instance here and initialise it
val visitTypeTasks = realm.createObject(MetaVisitTypeTasks::class.java.simpleName)
visitTypeTasks[MetaVisitTypeTasks::visitTypeId.name] = "1"
val visitTasks = visitTypeTasks.getList(MetaVisitTypeTasks::visitTasks.name)
storeTasks.getList("storeTasks").forEach {
visitTasks.add(it)
}
// save the object to the map
generatedStoreVisitTypes[storeUid] = visitTypeTasks
}
}
.safeRemoveField("storeTasks")
realm.schema.getOrCreate<RealmStore>()
.safeAddRealmListField(RealmStore::visitTypes, realm.schema)
.transform {
val storeUid = it.getString(RealmStore::storeUid.name)
// crash here on the “add” method
it.getList(RealmStore::visitTypes.name).add(generatedStoreVisitTypes[storeUid])
}
}
}
private inline fun <reified T> RealmSchema.getOrCreate(): RealmObjectSchema {
return get(T::class.java.simpleName) ?: create(T::class.java.simpleName)
}
private inline fun <reified TClass : RealmObject, reified TListItem : RealmObject, reified TList : RealmList<TListItem>> RealmObjectSchema.safeAddRealmListField(addedField: KMutableProperty1<TClass, TList>, schema: RealmSchema): RealmObjectSchema {
val fieldName = addedField.name
val listItemObjectSchema = schema.get(TListItem::class.java.simpleName)
if (!hasField(fieldName)) {
return addRealmListField(fieldName, listItemObjectSchema)
}
return this
}
Calling the "add" method in the second “transform” method sometimes causes a -
“java.lang.IllegalStateException: Object is no longer valid to operate on. Was it deleted by another thread?”
I’m familiar with this error and usually know how to handle it but I can’t recreate it or understand how such a thing happens in this case.
Since we’re talking about migration, there shouldn’t be another thread that’s operating on the same schema - as the execution is synchronised, isn’t it?.
Also, we’re talking about an object that was just created. There’s no other context in which it’s referenced or used.
I don’t understand how this object could be deleted. What could cause such an error?

Related

Do you have to instantiate a Kotlin object?

I am working on an Android Studio project where I am using a singleton class to keep track of data (I have already done research on the pros and cons of singleton objects, and I decided it was the best solution for my project). I am, however, running into a few problems that seem to point back to my singleton object, for which I have not been able to find any good solutions on StackOverflow or other developer forums.
The first error I'm getting happens where I call the singleton object in another class.
Note: I am not instantiating this singleton class before using it, because if I understand Kotlin singletons correctly, you don't have to.
for (item in items) {
with(item.device) {
if (name == "BLE_DEVICE") {
count++
Data.addresses.add(address) >>> This is where I call the singleton object <<<
}
}
}
The second error I get comes from my initialization of SharedPreferences in the singleton class.
var sharedPref: SharedPreferences? = MainActivity().getSharedPreferences("MySharedPreferencesFile", Context.MODE_PRIVATE)
The third error I get comes from calling this function from my singleton object.
fun saveSharedPreferences() {
for ((key, value) in names) {
if (sharedPref != null) {
if (!sharedPref?.contains(key)!!) {
sharedPref
?.edit()
?.putString(key, value)
?.apply()
}
}
}
}
FOR REFERENCE:
a. Here are the important lines from my stack trace...
2022-08-30 16:07:05.422 9946-9946/? E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.punchthrough.blestarterappandroid, PID: 9946
>>> java.lang.ExceptionInInitializerError
>>> at com.punchthrough.blestarterappandroid.ScanResultAdapter.getItemCount(ScanResultAdapter.kt:62)
...
...
>>> Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.SharedPreferences android.content.Context.getSharedPreferences(java.lang.String, int)' on a null object reference
>>> at android.content.ContextWrapper.getSharedPreferences(ContextWrapper.java:174)
>>> at com.punchthrough.blestarterappandroid.Data.<clinit>(Data.kt:35)
at com.punchthrough.blestarterappandroid.ScanResultAdapter.getItemCount(ScanResultAdapter.kt:62) 
b. This is my singleton class used for tracking data.
object Data {
// Format: <Device Address, Name>
// Used for keeping a record of all the devices, keeping
// duplicate advertisements off the screen, and saving the
// user-inputted names to the MAC address
var names: MutableMap<String, String> = mutableMapOf()
// Format: <Device Address>
// Used for keeping a count of how many views should be populated
// in the RecyclerView
var addresses = mutableSetOf<String>()
// TODO: Fix this line to resolve an initialization error
var sharedPref: SharedPreferences? = MainActivity().getSharedPreferences("MySharedPreferencesFile", Context.MODE_PRIVATE)
fun saveSharedPreferences() {
for ((key, value) in names) {
if (sharedPref != null) {
if (!sharedPref?.contains(key)!!) {
sharedPref
?.edit()
?.putString(key, value)
?.apply()
}
}
}
}
}
Never instantiate an Activity. It simply won't work and cannot be used for any useful purpose. Activities are full of properties that are set up when the OS creates the Activity for you. If you instantiate it yourself, you have a dead object full of null properties that are not supposed to be null.
Kotlin's built-in singleton (object) is unsuitable for singletons that depend on something else because it has no constructor for you to call to initialize the dependencies.
In this case, your singleton would have to be dependent on a Context to be able to use shared preferences, so a Kotlin object is not suitable.
This is how you can create a singleton that needs a context:
class Data private constructor(val context: Context) {
companion object {
private var instance: Data? = null
fun getInstance(context: Context): Data {
return instance ?: synchronized(this) {
instance ?: Data(context.applicationContext).also { instance = it }
}
}
}
val names: MutableMap<String, String> = mutableMapOf()
val sharedPref: SharedPreferences = context.getSharedPreferences("MySharedPreferencesFile", Context.MODE_PRIVATE)
fun saveSharedPreferences() { // I simplified this function a bit
sharedPref.edit {
for ((key, value) in names) {
if (!sharedPref.contains(key)) {
putString(key, value)
}
}
}
}
}
And each time you use it, you would need to pass a Context to Data.getInstance.
By the way, I highly discourage combining var with MutableList or MutableSet. It invites mistakes because outside classes won't know whether they should swap out the collection for a new instance or mutate it in place when they want to make changes. And other classes cannot know whether it's safe to cache a copy of the list because it may or may not change out from under them based on something some other class is doing.
Really, I wouldn't recommend ever exposing a MutableCollection (MutableList or MutableSet) or a var read-only Collection publicly from any class. It leaves you open to many possible types of bugs when outside classes can change a collection that a class is using internally, or that is used by multiple classes. Instead, I would make the collections private and expose functions that indirectly modify them such as addName().

RxJava - Kotlin

I am working on an integration of a bluetooth sdk,
It forces me to have a static arraylist where the sdk module is hosting the read data, this statement is in Java
public ArrayList<ReaderDevice> tagsList = new ArrayList<>();
In my Kotlin activity I have the static reference
lateinit var sharedObjects: SharedObjects
To get this list of my fragments I use
HomeActivity.sharedObjects.tagsList
What I need is "some way" no matter how "dirty" to be able to have a "listener" -"observer" to know from my fragments when a new element is added to take some action x
but only when I do the "onnext" can I get the size, otherwise it doesn't refresh, I guess it's because it "is copied" but it doesn't have the same reference, can I somehow put the static reference to my viewmodel propertys? I'm lost how to see this list
Try to create the observable like this
//fragment
viewmodel.setReadTagsList.onNext(HomeActivity.sharedObjects.tagsList)
//viewmodel
val setReadTagsList = PublishSubject.create<List<ReaderDevice>>()
private val _tags = BehaviorSubject.create<List<ReaderDevice>>()
setReadTagsList
.bind(_tags)
.disposedBy(disposeBag)
setReadTagsList
.withLatestFrom(_tags){_, o1 -> o1}
.map { "${it.size}" }
.bind(_errorMessage)
.disposedBy(disposeBag)
java.util.ArrayList doesn't support listening (on adding/removing elements), you need to use something different instead.
If you have the option of setting HomeActivity.sharedObjects.tagsList, you can use the following approach:
Set it to another list with the capability. You could either use an existing list supporting that or create a new one that forwards all operation to another list or extends ArrayList and intercepts the methods by overriding like that:
class WatchableArrayList<T>(val listener:(Int, T? , T? )->Unit):ArrayList<T>() {
override fun add(elem: T): Boolean {
val index = size
val ret = super.add(elem)
listener.invoke(index, elem, null)
return ret
}
//similar for other methods
}
This creates a class extending ArrayList that takes a high-order function as a constructor parameter (that takes the index added and removed element as parameters).
You can then create an instance of that and set HomeActivity.sharedObjects.tagsList to that instance like that:
HomeActivity.sharedObjects.tagsList = WatchableArrayList((index, added, removed)->{
//handler here
})
However, you might want to use a wrapper pattern instead of inheritance here:
class WatchableListWrapper<T>(val wrapped: MutableList<T>, val listener:(Int, T? , T? )->Unit) {
override fun add(elem: T): Boolean {
val index = size
val ret = wrapped.add(elem)
listener.invoke(index, elem, null)
return ret
}
//similar for other methods
}
HomeActivity.sharedObjects.tagsList = WatchableListWrapper(HomeActivity.sharedObjects.tagsList, (index, added, removed)->{
//handler here
})

How to use the keyword also in kotlin android

Am learning android kotlin follow this:
https://developer.android.com/topic/libraries/architecture/viewmodel#kotlin
class MyViewModel : ViewModel() {
private val users: MutableLiveData<List<User>> by lazy {
MutableLiveData<List<User>>().also {
loadUsers(it)
}
}
fun getUsers(): LiveData<List<User>> {
return users
}
private fun loadUsers() {
// Do an asynchronous operation to fetch users.
}
}
Dont know how to write the fun loadUsers()
Here is my User:
class User {
constructor(name: String?) {
this.name = name
}
var name:String? = null
}
If dont use the keyword 'also' , i know how to do it.
But if use 'also' , it seems not work.
Here is how i try to write the fun loadUsers:
private fun loadUsers( it: MutableLiveData<List<User>>){
val users: MutableList<User> = ArrayList()
for (i in 0..9) {
users.add(User("name$i"))
}
it = MutableLiveData<List<User>>(users)
}
Error tips near it : Val cant be ressigned
Part 1: According to the Kotlin documentation, also provides the object in question to the function block as a this parameter. So, every function call and property object you access is implied to refer to your MutableLiveData<List<User>>() object. also returns this from the function block when you are done.
Thus, another way of writing your MutableLiveData<> would be like this:
val users = MutableLiveData<List<User>>()
users.loadUsers()
Part 2: As far as how to implement loadUsers(), that is a separate issue (your question is not clear). You can use Retrofit + RxJava to load the data asynchronously, and that operation is totally outside of the realm of ViewModel or also.
Part 3: With your approach, you have conflicting things going on. Instead of doing a loadUsers() from your lazy {} operation, I would remove your lazy {} operation and create a MutableLiveData<> directly. Then, you can load users later on and update the users property any time new data is loaded. Here is a similar example I worked on a while ago. It uses state flows, but the idea is similar. Also use a data class to model the User instead of a regular class. Another example.
It is solved change to code:
private fun loadUsers( it: MutableLiveData<List<User>>){
val users: MutableList<User> = ArrayList()
for (i in 0..9) {
users.add(User("name$i"))
}
it.value = users
}
it can't be reassigned , but it.value could .

Kotlin : implenting an immutable class through the data class method but making sure the input values are clean

I'm new to coding in kotlin and want to implement an immutable class that represents a project with various fields inside.
The easiest way to do this is by using a data class and using the copy() method so that anytime one of the app user modifies a field it results in the backend in a call to the copy method with the modified field producing the new project.
My problem is that this way does not allow for prior checking of parameters (eg : limit string size of the owner, making sure the number of people added to the project is reasonable etc).
If this was java, I'd use a builder pattern but this seems to defeat the purpose of kotlin, and i've read articles that are positive to using builders in kotlin (https://www.baeldung.com/kotlin/builder-pattern)
and others that are completely against (https://code-held.com/2021/01/23/dont-use-builder-in-kotlin/).
I haven't found any way to "modify" the copy method and to add the parameter sanitization checks that are needed for each parameter. I would appreciate any "smooth" idea to implement this, if anybody has found it. The goal would also be to throw exeptions/sealed classes variables so that the app UI can tell the user what went wrong instead of a generic error message just mentioning that the project was not modified.
I agree with the second link. If you look at the comments on the Baeldung article, you'll see even they were convinced and pledged to revise the article.
You can throw exceptions in an init block but if these are exceptions that are not caused by programmer error, it would be more Kotlin-idiomatic to expose a single constructor-like function that returns a wrapper or just null for invalid input.
Examples:
data class Person(val name: String, val age: Int = 0) {
init {
if (age < 0) {
throw IllegalArgumentException("Age $age is less than 0.")
}
}
}
If you want to return a wrapper or nullable, a data class isn't suitable for preventing invalid input because the generated copy() function will always return a fully constructed object. Sadly, Kotlin does not support overriding the generated copy() function.
sealed class Result<T>
data class Success<T>(val value: T): Result<T>()
data class Failure<T>(val reason: String): Result<T>()
class Person private constructor(val name: String, val age: Int = 0) {
companion object {
fun build(name: String, age: Int = 0): Result<Person> {
return when {
age < 0 -> Failure("Age $age is less than 0.")
else -> Success(Person(name, age))
}
}
}
fun buildCopy(name: String = this.name, age: Int = this.age) = build(name, age)
}

How to test reading from/updating file in MVP?

I'm trying to change my app from having no design pattern to using MVP.
Originally I had the following code:
override fun onCreateInputView(): View {
//favoritesData is an instance variable, same with "Gson", "parser", "favorites", and "stringArrayListType"
favoritesData = File(filesDir, "favorites_data.json")
if (favoritesData.exists()) {
favorites = Gson.fromJson(parser.parse(FileReader(favoritesData)), stringArrayListType)
}
}
and
fun updateFavoritesFile() {
favoritesData.writeText(Gson.toJson(favorites))
}
After trying to use MVP I changed the code to:
class AnimeFaceKeyboardPresenter(val view : AnimeFaceKeyboardView, private val model : KeyboardModel = KeyboardModel()) : Presenter {
override fun onCreateInputView() {
model.favorites = view.loadFavoritesFile()
//At some point, call view.updateFavoritesFile(arrayListOf("test","test2"))
}
override fun onStartInputView() {
}
}
and the code in the activity itself to:
override fun loadFavoritesFile() : ArrayList<String> {
val favoritesData = File(filesDir, favoritesFileName)
var favorites = ArrayList<String>()
//"favorites" is no longer an instance variable
if (favoritesData.exists()) {
favorites = Gson.fromJson(parser.parse(FileReader(favoritesData)), stringArrayListType)
}
return favorites
}
override fun updateFavoritesFile(favorites: ArrayList<String>) {
File(filesDir, favoritesFileName).writeText(Gson.toJson(favorites))
}
override fun onCreateInputView(): View {
super.onCreateInputView()
presenter = AnimeFaceKeyboardPresenter(this)
presenter.onCreateInputView()
}
I'm not sure if I'm using MVP correctly, but if I am, how would I go about testing this code. For example - writing a test that calls updateFavoritesFile(arrayListOf("test1","test2")) and uses loadFavoritesFile() to check if the contents is as expected.
Well, you might want to relocate your file read and write to your model (they are associated with data which doesn't really belong in your view).
Then your test consists of instantiating your model object, and testing the methods which can be done without the view and presenter (simplifying the tests).
I would be very tempted to abstract your file as some form of "Repository" object that knows how to read and write strings (but you don't care where or how). You would pass the repository object to your model as a construction property. The advantage of this is that you can create a mock or fake Repository object which you can use to "feed" test data and examine written data, making testing that part of your model a little easier.
Don't forget, your view shouldn't have direct access to your model under MVP .. that would me more like MVC (one of the very few differences between MVP and MVC).

Categories

Resources