I'm new to Android programming and Realm. Couldn't find any related articles so I'm posting the question here..
I'm writing to Realm from my JSONfile, which is recorded and reflected in the RealmBrowser. But when I restart my app, I'm getting error on io.realm.exceptions.RealmPrimaryKeyConstraintException: Value already exists: 20151101. All values are derived directly from my JSON file and I run it in a for loop under all data has been recorded in the Realm database.
for (int i = 0; i < jsonFile.length(); i++) {
try {
RealmConfiguration objectDB = new RealmConfiguration.Builder(getContext()).
name("objectDB.realm").build();
Realm realm = Realm.getInstance(objectDB);
realm.beginTransaction();
Object object = realm.createOrUpdateObjectFromJson(Object.class, jsonFile);
object.setPrimaryId(primaryId);
//and set more more data...
realm.copyToRealmOrUpdate(object);
realm.commitTransaction();
} catch (JSONException e) {
e.printStackTrace();
}
}
My understanding is that if the primaryKey exists on the Realm table, it will only update changes to any of the setters(), but now I'm having "io.realm.internal.Table.throwDuplicatePrimaryKeyException" error. Can anyone advise where or what I have done wrong along the way?
Many thanks to the kind folks here!
I think you should replace this
Object object = realm.createOrUpdateObjectFromJson(Object.class, jsonFile);
object.setPrimaryId(primaryId);
//and set more more data...
realm.copyToRealmOrUpdate(object);
With something like this
Object object = gson.fromJson(jsonFile, Object.class);
object.setPrimaryId(primaryId);
realm.beginTransaction();
realm.copyToRealmOrUpdate(object);
realm.commitTransaction();
Gson is a dependency of Realm, so I think you can just do Gson gson = new Gson() somewhere to make one.
Why do you have this line?
object.setPrimaryId(primaryId);
I would assume that information would be part of the jsonFile, and the primary key you set there probably conflicts with one that already exists.
Related
I have a database schema set up using Android Room, Dao, and Entity classes set up as POJOs. Except the POJO entity isn't so "plain" and that it actually holds a reference to another object. I thought this was a great idea at the time as it allowed me more flexibility in changing the object and using it in other places in the app and only saving to the database as needed.
The problem I'm facing now is that the migration guideline only mentions how to migrate the database by altering the SQL, but I changed the object itself. My typeconverter class simply converts the object to and from a string.
Because it's being saved as a long string I know I essentially have to do a simple REPLACE(string, old_string, new_string) in the SQL
migration code block with the updated object being the new string. How can I retrieve the old objects and update values before running the replace SQL command in the migration block?
UPDATE: I'm using GSON in my typeconverter class to change the object to a string, so the solution that comes to mind is to simply download the old object and upload the new one with the added fields. Only problem is that you can't access the database and download the json, convert it to the object, add the new data fields, then reconvert to a new json string.
I'm lucky I'm not at scale yet because this would be a tricky thing to do for so many users. (So I recommend that anyone reading this not do what I did and implement object nesting. It's easier to convert the Entry objects to the other portable objects instead of nesting when it comes to updating the data you want saved.)
I think if you already did what I did and can't go back, the best bet is to simply create the new portable object and make new typeconverter functions for that one and add the SQL COLUMN for the new object. The problem then lies in how you then retrieve those objects from the Entry Dao, which will cause a lot more code to write and possible errors to debug if not done carefully.
Long story short, if anyone is reading this, DO NOT nest objects in Room DBs on Android unless you are 100% sure it's a final form of your model... but is there such a thing anyways?
I just ran into this issue, but fortunately I only needed to add a new key/value pair to a "flat" object model. So hopefully my answer can be expanded on to fully answer #Mr.Drew question.
Assuming you have a table town with a column star_citizen that is the object model being typeconverted:
{"name":"John", "age":30, "car":false}
and you want to update the object to have an extra property "house": true
you could add a migration to your App's Room Database class like this (Kotlin example):
#Database(entities = [Town::class], version = 2, exportSchema = true)
#TypeConverters(DataConverters::class)
abstract class AppDatabase : RoomDatabase() {
abstract val sharedDao : SharedDao
companion object {
private val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
val cursor = database.query("SELECT * FROM `town`")
// iterate through each row in `town`, and update the json
// of the StarCitizen object model
cursor.moveToFirst()
while (!cursor.isAfterLast) {
val colIdIdx = cursor.getColumnIndex("id")
val id = cursor.getInt(colIdIdx)
val colStarCitizenIdx = cursor.getColumnIndex("star_citizen")
val rawJson = cursor.getString(colStarCitizenIdx)
val updatedRawJson = starCitizenModelV1ToV2(rawJson)
database.execSQL("""UPDATE town SET star_citizen ='${updatedRawJson}' WHERE ID = $id""")
cursor.moveToNext()
}
}
}
//[...]
private fun starCitizenModelV1ToV2(rawJson: String): String {
val rawJsonOpenEnded = rawJson.dropLast(1)
val newProperty = "\"house\":true"
return "$rawJsonOpenEnded,$newProperty}"
}
}
}
{
"data":[{"compulsory_field": 1}, {"compulsory_field": 2}, {}]
}
converts into object by gson
data class Something(val compulsory_field: Int)
val somethingList = //gson parse the data
println(somethingList)
//[
// Something(compulsory_field = 1),
// Something(compulsory_field = 2),
// Something(compulsory_field = null) //Should not exists
//]
and I want to get rid of the 3rd item. Is it possible to do it AFTER it has been converted to object? Or can it only be done when it's String/InputStream? And how can I do that?
Thanks!
Edit: clarify that the constructor works, but gson failed to understand kotlin rules and injected objects that I can't check in Kotlin
I came up with a ugly "solution"/workaround, but I am still searching for a better answer (or get the project to switch to moshi codegen or something else, whichever comes first)
Basically I just copy each object again to make sure it goes through all the null-safety checking kotlin provides
val somethingList = //gson parse the data
val fixedSomethingList = somethingList.mapNotNull {
try {
it.copy(compulsory_field = it.compulsory_field)
} catch (e: IllegalArgumentException) { //if gson inserted a null to a non-null field, this will make it surface
null //set it to null so that they can be remove by mapNotNull above
}
}
Now the fixedSomethingList should be clean. Again, very hacky, but it works......
If you don't like empty objects then just remove them. You should always can do it after parsing. However please be aware that in Kotlin lists can be mutable or not. It you received an inmutable one (built with "listOf") then you will have to build a new list including only the elements you want.
https://kotlinlang.org/docs/reference/collections.html
Edited: Ok, I understand you can't even parse the json in first place. In this case maybe you can try this:
To allow nulls: change type of compulsory_field from Int to Int? at declaration
Fix your json string before parsing: In your case you can replace all {} with {"compulsory_field": null}
Now gson parser should obtain a valid list.
I'm trying to get a query using as condition the Pointer Id of the object, for example I'm saving all the id's of an object in an array, then I want to get from another class (related by pointer), all the objects that uses that Id, so I already have this:
for (i = 0; i < num; i++) {
//restaurant.setObjectId(restId[i]);
ParseQuery<ParseObject> resultsitems = ParseQuery.getQuery("Item").whereEqualTo ("restaurant", restId[i]);
try {
objects=resultsitems.find();
} catch (ParseException e) {
e.printStackTrace();
}
(.......)
}
In my first try I tried to set the id into the restaurant object, then tried to use the query as:
ParseQuery<ParseObject> resultsitems = ParseQuery.getQuery("Item").whereEqualTo ("restaurant",restaurant );
But it didn't work, then I tried to search as shown in the code above, it doesn't crash but brings me nothing, how can I do this?
This is what really worked for me:
ParseObject obj = ParseObject.creatWithoutData("classNameThatPointedTo","fieldValue");
query.whereEqualTo("fieldName", obj);
Use: .findInBackground(new FindCallBack<Item>... (this will auto-complete in Android Studio), then put objects=resultsitems.find(); in the curly braces of the done() function.
The callback waits for the query to return before moving on with the script. Otherwise, the main thread will keep moving on without waiting for the data to come back from the server.
I have a RealmResults <Student> object. I want to convert it to RealmList <Student> object. any suggestions?
RealmList <Student> results = new RealmList<Student>();
results.addAll(realmResultList.subList(0, realmResultList.size()));
Please try and let me know if this work for you.
RealmList <Student> finalList = new RealmList<Student>();
finalList.addAll(yourRealmResults.subList(0, yourRealmResults.size()));
Since 0.87.0
Added Realm.copyFromRealm() for creating detached copies of Realm objects (#931).
Which allow just return list List<E extends RealmObject>
RealmResults implements the List interface and so does the RealmList.
RealmList <Student> results = new RealmList<Student>();
results.addAll(realmResultsList);
In new update you can use copyFromRealm method to do so :
RealmList<Student> finalList = realm.copyFromRealm(resultsAnswers);
RealmResults is returned if a query is expected to give a collection of objects (e.g. RealmQuery<E>.findAll()). Otherwise, single object queries will return a RealmObject.
Managed and Unmanaged Objects
RealmResults are managed objects, meaning they cannot be manipulated outside of Realm transactions and are confined in the thread that created them. Converting RealmResults into a RealmList will make the data unmanaged, as what #epicpandaforce pointed out, meaning the objects in the list are not connected to the database anymore and are basically normal Java objects which can be transferred in between threads and manipulated.
To convert RealmResults to a RealmList:
RealmResults<User> results = realm.where(User.class).findAll();
RealmList<Users> users = realm.copyFromRealm(results);
Changes to an unmanaged object will not in any means affect the original in the database unless a realm.copyToRealm(users), doing the opposite of copyFromRealm(), is executed after. Keep in mind that RealmLists can be managed or unmanaged, as a RealmObject from a RealmResult can have the following structure in which the RealmList in this case is a managed object:
class User {
int id;
String name;
RealmList<String> petNames;
}
Finally, copyFromRealm() returns a List so it's also possible to do
ArrayList<User> users = realm.copyFromRealm(results);
Realm has some new features check-in documentation
Realm Documentation
Realm has copyfromRealm function which we can use to convert the result to list
RealmList<Student> student=realm.copyfromRealm(Realmresult);
#JemshitIskenderov This should copy for you.
public RealmList<Student> convertResultToList(RealmResult<Student> realResultsList){
RealmList <Student> results = new RealmList<Student>();
for(Student student : realResultsList){
results.add(copy(student));
}
}
private Student copy(Student student){
Student o = new Student();
o.setCreated(student.getCreated());
o.setModified(student.getModified());
o.setDeleted(student.getDeleted());
o.setName(student.getName());
//List more properties here
return o;
}
Code:
public class RealmCollectionHelper {
public static <C extends RealmModel> RealmList<C> mapperCollectionToRealmList(Collection<C> objects){
if (objects == null){
return null;
}
RealmList<C> realmList = new RealmList<>();
realmList.addAll(objects);
return realmList;
}
}
Here my gist: https://gist.github.com/jmperezra/9b4708051eaa2686c83ebf76066071ff
Just another way of doing it:
RealmList<YourClass> dummy = new RealmList<>();
Iterator<YourClass> it = realmResultsList.listIterator();
while (it.hasNext()) {
dummy.add(it.next());
}
I'm using ORM Lite on a project , I decided to use the facility to make the persistence part of the Web service once and can reuse it on Android.
But I am suffering a lot because possou complex objects that have multiple ForeignCollectionField and Foreign object , and at the hour of perssistir temenda these data is a headache, because I have to enter one by one of their children , I think the idea of an ORM is make life easier , ie you have to persist the object and father and all the rest is done behind the scenes ...
Well, it is now too late to give up lite ORM , I wonder if there is a way to do what sitei above ..
I found a piece of code here
tried to implement but it seems not work , just keeps saving the parent object .
follows the function I'm trying to use , but do not know whether imports are correct because the code I found in the link above did not have this data
public int create(Object entity, Context context) throws IllegalArgumentException, IllegalAccessException, SQLException, SQLException {
try{
if (entity!=null){
// Class type of entity used for reflection
Class clazz = entity.getClass();
// Search declared fields and save child entities before saving parent.
for(Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
// Inspect annotations
Annotation[] annotations = field.getDeclaredAnnotations();
try{
for(Annotation annotation : annotations) {
// Only consider fields with the DatabaseField annotation
if(annotation instanceof DatabaseField) {
// Check for foreign attribute
DatabaseField databaseField = (DatabaseField)annotation;
if(databaseField.foreign()) {
// Check for instance of Entity
Object object = field.get(entity);
Dao gDao = getDatabase(context).getDao(object.getClass());
gDao.create(object);
}
}else if (annotation instanceof ForeignCollectionField){
Object object = field.get(entity);
for(Object obj : new ArrayList<Object>((Collection<?>)object)){
Class c = obj.getClass();
Dao gDao = getDatabase(context).getDao(obj.getClass());
gDao.create(obj);
}
}
}
}catch (NullPointerException e){
e.printStackTrace();
}
}
// Retrieve the common DAO for the entity class
Dao dao = getDatabase(context).getDao(entity.getClass());
// Persist the entity to the database
return dao.create(entity);
}else
return 0;
}finally {
if (database != null) {
OpenHelperManager.releaseHelper();
database = null;
}
}
}
Leveraging the same post, also need a colução to delete cascade, imagine a situation where I have the following tables:
Company > Category> person> contact> Phone and email
Deleting and now I do as described in the documentation:
public int deleteCascade(Prefeitura prefeitura, Context context){
try{
Dao<Prefeitura, Integer> dao = getDatabase(context).getDao(Prefeitura.class);
DeleteBuilder db = dao.deleteBuilder();
db.where().eq("prefeitura_id", prefeitura.getId());
dao.delete(db.prepare());
// then call the super to delete the city
return dao.delete(prefeitura);
}catch (SQLException e){
e.printStackTrace();
}
return 0;
}
But the objects that are not directly linked the company would still be in the database, how could I do?
But without hacks, I want a clean code ...
I know ORM Lite really is lite, but one that saves the children create and delete cascade is essential for any ORM, hopefully for the next versions it is implemented, it is regrettable not have these features, for simple projects is very good, but in a complex project because a lot of headaches, I'm feeling on the skin.
Any help is welcome!