Android Studio 3.2.
Realm 5.8.0
public class Merchant extends RealmObject {
#PrimaryKey
private long id;
private Image preview;
}
public class Image extends RealmObject {
#PrimaryKey
private long id;
}
I need to delete Merchants object with specific ids AND all it embedded objects (Image in my example).
So here code:
public static void updateMerchantList(final List<Merchant> thatMerchantsList) {
Realm realm = Realm.getDefaultInstance();
try {
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
RealmList<Merchant> localMerchantList = getMerchantsRealmList();
if (!EqualsUtil.areEqualContentLists(localMerchantList, thatMerchantsList)) {
List<Merchant> itemNotExistInThatMerchants = new ArrayList<>(localMerchantList);
itemNotExistInThatMerchants.removeAll(thatMerchantsList);
if (itemNotExistInThatMerchants.size() > 0) {
localMerchantList.removeAll(itemNotExistInThatMerchants);
Long[] idsToDeleteArray = new Long[itemNotExistInThatMerchants.size()];
for (int index = 0; index < itemNotExistInThatMerchants.size(); index++) {
Merchant merchant = itemNotExistInThatMerchants.get(index);
idsToDeleteArray[index] = merchant.getId();
}
RealmResults<Merchant> localMerchantsForDelete = realm.where(Merchant.class).in(Merchant.ID, idsToDeleteArray).findAll();
boolean isDelete = localMerchantsForDelete.deleteAllFromRealm();
}
}
}
});
} finally {
realm.close();
}
}
public static RealmList<Merchant> getMerchantsRealmList() {
Realm realm = Realm.getDefaultInstance();
try {
RealmResults<Merchant> realmResults = realm.where(Merchant.class).findAll();
RealmList<Merchant> realmList = new RealmList<>();
realmList.addAll(realmResults.subList(0, realmResults.size()));
return realmList;
} finally {
realm.close();
}
}
As result 2 Merchant success delete from Realm (by method deleteAllFromRealm) .
Nice.
But all embedded objects (like Image) NOT delete from Realm.
Questions:
This is because I need to write custom method that cascade delete Merchant and all it embedded objects?
Is Realm can cascade delete objects?
Related
I'm using Realms as a database in Android app. Works fine, but I've added a new label in my user model and I'm getting the error that I need to migrate my schema:
java.lang.RuntimeException: Unable to create application com.apelucy.apelucy.app.base.MyApplication: io.realm.exceptions.RealmMigrationNeededException: Migration is required due to the following errors:
- Property 'User.testRealm' has been added.
How can I do the migration? I've found other solutions here but I can't implement them in my code. I can't use a solution of delete and install the app. I now that work in development, but I need to update the app in production.
My UserRespository class:
public class UserRepository {
private static UserRepository sInstance = null;
private Context mContext = null;
public static UserRepository getInstance(Context context) {
if (sInstance == null) {
sInstance = new UserRepository();
sInstance.mContext = context;
}
return sInstance;
}
// DATABASE Methods
public void storeUser(final User user) {
AppSingleton.getInstance().setUser(user);
Realm realm = null;
try {
realm = Realm.getDefaultInstance();
realm.executeTransaction(realm1 -> realm1.insertOrUpdate(user));
} finally {
if (realm != null) {
realm.close();
}
}
}
public User retrieveUser() {
Realm realm = null;
User user = null;
try {
realm = Realm.getDefaultInstance();
User userRealmResult = realm.where(User.class)
.findFirst();
if (userRealmResult != null) {
user = realm.copyFromRealm(userRealmResult);
}
} finally {
if (realm != null) {
realm.close();
}
}
return user;
}
public void clearUser() {
// Clear Database objects
Realm realm = null;
try {
realm = Realm.getDefaultInstance();
realm.executeTransaction(realm1 -> realm1.delete(User.class));
} finally {
if (realm != null) {
realm.close();
}
}
}
}
Init realm in my Application:
Realm.init(this);
My model change:
#SerializedName("test")
#Expose
private String testRealm;
Migrations allow you to modify the schema of the application, which means that it lets you add, remove, rename tables/fields in the Realm schema. If you change a RealmModel class, then you must write the migration that will map the existing Realm file to reflect the new model classes.
RealmConfiguration config = new RealmConfiguration.Builder()
.schemaVersion(1)
.migration(new MyMigration())
.build();
Realm.setDefaultConfiguration(config);
The default schema version is 0.
Migrations are fairly straightforward:
you must increment the schema version, so Realm knows you want to increment the schema's version to a specific number
you must supply a migration that will handle the change from one version to another
Migrations describe the operations to do when you need to go from one schema version to another:
public class MyMigration implements RealmMigration {
#Override
public void migrate(final DynamicRealm realm, long oldVersion, long newVersion) {
RealmSchema schema = realm.getSchema();
// Migrate from version 0 to version 1
if (oldVersion == 0) {
RealmObjectSchema userSchema = schema.get("User");
userSchema.addField("testRealm", String.class);
oldVersion++;
}
if (oldVersion == 1) { // ...
// ...
}
}
#Override
public int hashCode() { return MyMigration.class.hashCode(); }
#Override
public boolean equals(Object object) { return object != null && object instanceof MyMigration; }
}
Add this in your Application file. This will Realm to delete everything if you add a new table to a column.
RealmConfiguration config = new RealmConfiguration.Builder().name("dbname.realm")
.deleteRealmIfMigrationNeeded()
.build();
Realm.setDefaultConfiguration(config);
Scenario:
I have a model(DBBasket) to persist locally, the number of added products and products itself.
When the user clicks on the + button under each product thumbnail
I'm increasing totalQuantity in Product,
Adding the product to the DBBasket,
Set totalProducts which I get from server, in DBBasket too.
Codes in + button:
holder.HomeProductBindGrid.thatPlusButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
final Product productIns = mProductsList.get(holder.getAdapterPosition());
mRealm = mRealmManager.getLocalInstance();
mRealm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(#NonNull Realm realm) {
mProductRealm = realm.where(Product.class).equalTo(ProductFields.UNIQUE_ID, productIns.getUniqueId()).findFirst();
....
realm = mRealmManager.getLocalInstance();
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
String totalNumberOfItemsInBasket = parseDataFromServer(ServerResponse, "numberOfProducts");
if (totalNumberOfItemsInBasket.matches("")) {
totalNumberOfItemsInBasket = "0";
}
Product product = realm.where(Product.class).equalTo(ProductFields.UNIQUE_ID, prod.getUniqueId()).findFirst();
if (product == null) {
product = realm.createObject(Product.class, mProductRealm.getUniqueId());
}
if (product != null) {
if (product.totalQuantity.get() == null) {
product.totalQuantity.set(0);
product.totalQuantity.increment(countOrder);
} else {
product.totalQuantity.increment(countOrder);
}
realm.insertOrUpdate(product);
DBBasket dbBasket = realm.where(DBBasket.class).findFirst();
if (dbBasket == null) {
dbBasket = realm.createObject(DBBasket.class);
}
dbBasket.getProducts().add(product);
dbBasket.setTotalProducts(totalNumberOfItemsInBasket);
realm.insertOrUpdate(dbBasket);
Log.wtf("productRealm", dbBasket.getProducts().get(0).getUniqueId() + "");// It shows the UID correctly.
}
}
});
Cause of some reason, I clean the Product on Activity Destroy, but not DBBasket:
Realm realm = mRealmManager.getLocalInstance();
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(#NonNull Realm realm) {
realm.delete(Product.class);
}
});
Each time the user comes to the main page, I receive the list of products from the server, and insert them to the local DB:
for (int j = 0; j < receivedProductsFromServer.getLength(); j++) {
final Product product = new Product(...);
product.setUniqueId(Utils.UniqueIdMaker());
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(#NonNull Realm realm) {
Product productRealm = realm.where(Product.class).equalTo(ProductFields.UNIQUE_ID, product.getUniqueId()).findFirst();
if (productRealm == null) {
realm.insert(product);
}
}
});
Problem:
Now, for those products which added previously to the DBBasket, I want to show their totalQuantity in front of that +.
So I've changed above snippet code to:
for (int j = 0; j < receivedProductsFromServer.getLength(); j++) {
final Product product = new Product(...);
product.setUniqueId(Utils.UniqueIdMaker());
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(#NonNull Realm realm) {
Product productRealm = realm.where(Product.class).equalTo(ProductFields.UNIQUE_ID, product.getUniqueId()).findFirst();
if (productRealm == null) {
DBBasket dbBasketRealm = realm.where(DBBasket.class).findFirst();
if(dbBasketRealm != null) {
RealmList<Product> productInBasket = dbBasketRealm.getProducts(); //Size() is zero!!!
RealmResults<Product> productFiltered = productInBasket.where().contains(ProductFields.UNIQUE_ID, product.getUniqueId()).findAll();
Product p = productFiltered.get(0);
if(p != null) {
product.totalQuantity.set(0);
product.totalQuantity.increment(Integer.valueOf(p.getQuantity()));
} else Log.wtf("productRealm", "productFiltered is Null.");
}
realm.insert(product);
}
}
});
But didn't work! and dbBasketRealm.getProducts() size is zero.
Edit:
+ Button on Debug mode:
dbBasketRealm.getProducts() on Debug mode:
DBBasket model:
public class DBBasket extends RealmObject{
public String totalProducts;
public RealmList<Product> products;
public DBBasket() {}
}
Product model:
public class Product extends RealmObject implements Observable {
#PrimaryKey
#Required
private String uniqueId;
public final MutableRealmInteger totalQuantity = MutableRealmInteger.valueOf(0);
public Product() {}
}
Get the previously added products in the basket, from the server. Then add them again to the local database. Something like below snippet code in OnCreate for example:
for (int i = 0; i < receivedFromServer.getLength(); i++) {
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(#NonNull Realm realm) {
Product productRealm = realm.where(Product.class).equalTo(ProductFields.UNIQUE_ID, receivedFromServer[i].getUniqueId()).findFirst();
if (productRealm != null) {
productRealm.setTotalQuantity(quantity));
DBBasket dbBasket = realm.where(DBBasket.class).findFirst();
if (dbBasket == null) {
dbBasket = realm.createObject(DBBasket.class);
}
RealmList<Product> productRealmList = dbBasket.getProducts();
productRealmList.add(productRealm);
dbBasket.products = productRealmList;
dbBasket.setProducts(productRealmList);
dbBasket.totalProductsSetZero();
dbBasket.setTotalProducts(quantity));
realm.insertOrUpdate(dbBasket);
}
}
});
I'm trying to write data on one service and accessing it from another service.
The data I'm getting from another service is not updated, it's old copy.
Restarting the app sometimes gets updated data.
The services are both normal Service extended, so UI thread only.
And I'm not keeping any realm instances open anywhere in the app.
How do I ensure it's always new and updated one?
Writing -
data is detached using realm.copyFromRealm(...)
try (Realm realm = Realm.getDefaultInstance()) {
if (realm != null) {
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(#NonNull Realm realm) {
data.setValue("New value ...");
realm.insertOrUpdate(data);
}
});
}
}
Reading -
Data data = null;
try (Realm realm = Realm.getDefaultInstance()) {
if (realm != null) {
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(#NonNull Realm realm) {
data = realm.copyFromRealm(realm.where(Data.class).equalTo("Id", id).findFirst());
}
});
}
}
Data -
public class Data {
...
private String Value;
public String getValue() { return Value; }
public void setValue(String v) { Value = v; }
...
}
Edit -
I ended up merging both services into one, which works for now. But I'll look forward if anyone can provide some tips or has a similar problem.
How do I ensure it's always new and updated one?
Don't use copyFromRealm(). and use RealmResults field variable + RealmChangeListener
RealmResults<T> results;
RealmChangeListener<RealmResults<T>> listener = new RealmChangeListener<...>() {
#Override public void onChange(RealmResults<T> results) {
...
}
}
public void something() {
results = realm.where(...).findAll*();
results.addChangeListener(
I'm trying to use realm as a persistence method of the downloaded data from an API. I'm getting the error: *
Caused by: java.lang.IllegalStateException: This Realm instance has
already been closed, making it unusable.
and I don't know what I'm doing wrong with the persistence methods. My class for persistence is the next:
public class PersistenceManager {
#Inject
public PersistenceManager(){}
public void saveHabitants(final List<Habitant> habitants){
Realm realm = Realm.getDefaultInstance(); // opens db
try {
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.insertOrUpdate(habitants);
}
});
} finally {
realm.close();
}
}
public void saveHabitant(final Habitant habitant){
Realm realm = Realm.getDefaultInstance();
try {
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.insertOrUpdate(habitant);
}
});
} finally {
realm.close();
}
}
public List<Habitant> loadHabitants(){
Realm realm = Realm.getDefaultInstance();
List<Habitant> results;
try {
results = realm.where(Habitant.class).findAll();
} finally {
realm.close();
}
return results;
}
public List<Habitant> loadHabitants(int indexFrom, int limit){
Realm realm = Realm.getDefaultInstance();
List<Habitant> results;
try {
results = realm.where(Habitant.class).findAll();
results.subList(indexFrom, limit);
} finally {
realm.close();
}
return results;
}
public Habitant loadHabitant(long id){
Realm realm = Realm.getDefaultInstance();
Habitant result;
try {
result = realm.where(Habitant.class).equalTo("id", id).findFirst();
} finally {
realm.close();
}
return result;
}
}
When I used the method loadHabitants(int indexFrom, int limit) is when I get this error.
A RealmResults is accessible through lazy loading (per accessor) for as long as there is at least 1 open Realm instance on the given thread.
I actually intended to refer to the documentation, but for some reason I couldn't find it? Anyways, a RealmResults is accessed lazily, so when there are no open Realm instances, it becomes invalid, and can no longer be accessed. This is true for RealmObjects as well.
The docs do say:
...RealmObjects and RealmResults are accessed through a lazy cache...
so you cannot return a managed RealmResults, and expect it to remain open, if you close the thread-local Realm instance it is associated with.
Two solutions:
1.) refer to the documentation on how to properly manage Realm lifecycle on UI thread and background thread
2.) use realm.copyFromRealm(), which eagerly evaluates the whole data set, creates a detached copy of it. Please note that this is not a free operation, larger data sets can easily cause UI thread to freeze up if you use copyFromRealm() on UI thread.
public List<Habitant> loadHabitants(int indexFrom, int limit){
try(Realm realm = Realm.getDefaultInstance()) {
List<Habitant> results = realm.where(Habitant.class).findAll();
results = new ArrayList<>(results.subList(indexFrom, limit));
for(int i = 0, size = results.size(); i < size; i++) {
results.set(i, realm.copyFromRealm(results.get(i)));
}
return Collections.unmodifiableList(results);
}
}
I have two classes :
UniteStratigraphique.java :
#DatabaseTable(tableName = "unitestratigraphique")
public class UniteStratigraphique {
public final static String ID_FIELD_NAME = "id";
#DatabaseField(generatedId = true, columnName = ID_FIELD_NAME)
private int id;
// CAMPAGNES
#DatabaseField(foreign = true, foreignAutoRefresh = true)
private Campagne campagne;
#ForeignCollectionField
private ForeignCollection<Campagne> listeCampagnes;
public UniteStratigraphique() {}
public Campagne getCampagne() {
return campagne;
}
public void setCampagne(Campagne campagne) {
this.campagne = campagne;
}
public ArrayList<Campagne> getListeCampagnes() {
ArrayList<Campagne> campagnesArray = new ArrayList<Campagne>();
for (Campagne campagne : listeCampagnes) {
campagnesArray.add(campagne);
}
return campagnesArray;
}
public ForeignCollection<Campagne> getListeCampagnesForeign() {
return listeCampagnes;
}
public void setListeCampagnes(ForeignCollection<Campagne> listeCampagnes) {
this.listeCampagnes = listeCampagnes;
}
}
Campagne.java :
#DatabaseTable(tableName = "campagne")
public class Campagne {
#DatabaseField(generatedId = true)
private int id;
// UNITE STRATIGRAPHIQUE
#ForeignCollectionField
private ForeignCollection<UniteStratigraphique> listeUniteStratigraphique;
#DatabaseField(foreign = true, foreignAutoRefresh = true)
private UniteStratigraphique uniteStratigraphique;
public Campagne() {}
public ArrayList<UniteStratigraphique> getListeUniteStratigraphique() {
ArrayList<UniteStratigraphique> usArray = new ArrayList<UniteStratigraphique>();
for (UniteStratigraphique us : listeUniteStratigraphique){
usArray.add(us);
}
return usArray;
}
public ForeignCollection<UniteStratigraphique> getListeUniteStratigraphiqueForeign() {
return listeUniteStratigraphique;
}
public void setListeUniteStratigraphique(
ForeignCollection<UniteStratigraphique> listeUniteStratigraphique) {
this.listeUniteStratigraphique = listeUniteStratigraphique;
}
public int getSizeListeUniteStratigraphique() {
return listeUniteStratigraphique.size();
}
public UniteStratigraphique getUniteStratigraphique() {
return uniteStratigraphique;
}
public void setUniteStratigraphique(UniteStratigraphique uniteStratigraphique) {
this.uniteStratigraphique = uniteStratigraphique;
}
}
As you can see, these are Many-To-Many linked (0...n---0...n, with ORMLite annotations).
Now, my workflow is :
I create multiple "UniteStratigraphique" classes and I store them into my database (this works fine).
=> So I have n * "UniteStratigraphique" stored.
After that what I want is to create a "Campagne" class wich will contain multiple "UniteStratigraphique" classes.
=> So I want to set this field from "Campagne.java" :
#ForeignCollectionField
private ForeignCollection<UniteStratigraphique> listeUniteStratigraphique;
with the n * "UniteStratigraphique" elements I just stored before.
I tried to do this with this DAO method but it only duplicate the "UniteStratigraphique" classes into my db and no link is made..
public void addUsToCampagne(Campagne campagne,
ArrayList<UniteStratigraphique> usArray) {
ForeignCollection<UniteStratigraphique> usForeign = campagne
.getListeUniteStratigraphiqueForeign();
if (usForeign == null) {
try {
usForeign = getHelper().getCampagneDao()
.getEmptyForeignCollection("listeUniteStratigraphique");
for (UniteStratigraphique us : usArray) {
usForeign.add(us);
}
} catch (SQLException e) {
e.printStackTrace();
}
}else{
for (UniteStratigraphique us : usArray) {
usForeign.add(us);
}
}
}
And in my Activity I'm doing this :
db.addCampagne(campagne);
if( myUniteStratigraphiqueArray.size() > 0){
db.addUsToCampagne(campagne, myUniteStratigraphiqueArray);
}
Many to Many relations are non automatic with ORMLite, the only way to achieve it is to make a 3rd Table only for link beetween these 2 classes..
This link refers to this problem : What is the best way to implement many-to-many relationships using ORMLite?
And the example here : https://github.com/j256/ormlite-jdbc/tree/master/src/test/java/com/j256/ormlite/examples/manytomany
Hope it helped.