The correct way to abstract Realm in Android apps

I am trying to use Realm.io in an Android application, but in order to stay safe, I would like to abstract from the DB level so that, if necessary, I can return to the standard SQLite DB without rewriting most of the application.

However, it is difficult for me to properly abstract the Kingdom because of its special nature:

  • When tied to the kingdom, RealmObjects are proxies, so I cannot transfer them since they were POJOs.
  • All Realm instances must be properly opened and closed for each thread in which they are used.

I resorted to using the recent Realm.copyFromRealm () API instead of passing kingdom-bound RealmObjects to circumvent these restrictions, but in this way I think I'm losing all the benefits of using a scope (me?).

Any suggestions?

+5
source share
3 answers

I will try to answer your first confusion. There is no need to go through RealmObject with intent or, in other terms, it is not necessary that they be parcelable or serializable

What you need to do is pass a specific primary id or other parameter to that particular RealmObject with an intent so that you can request that RealmObject again in the next step.

For example, suppose you pass primaryId when the operation starts,

 Intent intent = new Intent(this, NextActivity.class); intent.putExtra(Constants.INTENT_EXTRA_PRIMARY_ID, id); 

Now, inside NextActivity get the id from the intent and just request RealmObject , i.e.

 int primaryId = getIntent().getIntExtra(Constants.INTENT_EXTRA_PRIMARY_ID, -1); YourRealmObject mObject = realm.where(YourRealmObject.class).equalTo("id", primaryId).findFirst(); 

See, you received your object in a new activity, and do not worry about passing the object around.

I do not know what specific problem you are addressing regarding the opening and closing of sphere objects. Have you tried Realm Transaction blocks ? You do not need to rely on opening and closing if you use it.

Hope this helps.

Update

Since you are looking for abstraction,

Just create a class like DbUtils and try the getYourObjectById method, and then go over the id and return your object in return. This makes it abstract in some way. You can then save this class and method and simply change the contents of the method if you ever switch to another database solution.

+1
source

With the latest Google I / O 2017 announcement for Android Architectural Components , the correct way to abstract Realm in Android applications:

1.) The life cycle of a Realm instance is controlled by the ViewModel class, and it is closed in the onCleared() method

2.) RealmResults is a MutableLiveData<List<T>> , so you can create a RealmLiveData<T> class that wraps RealmResults<T> .

Therefore, you can create a view model as follows:

 // based on https://github.com/googlesamples/android-architecture-components/blob/178fe541643adb122d2a8925cf61a21950a4611c/BasicSample/app/src/main/java/com/example/android/persistence/viewmodel/ProductListViewModel.java public class ProductListViewModel { private final MutableLiveData<List<ProductEntity>> observableProducts = new MutableLiveData<>(); Realm realm; RealmResults<ProductEntity> results; RealmChangeListener<RealmResults<ProductEntity>> realmChangeListener = (results) -> { if(results.isLoaded() && results.isValid()) { // you probably don't need this, just making sure. observableProducts.setValue(results); } }; public ProductListViewModel() { realm = Realm.getDefaultInstance(); results = realm.where(ProductEntity.class).findAllSortedAsync("id"); // could use a Realm DAO class here results.addChangeListener(realmChangeListener); observableProducts.setValue(null); // if using async query API, the change listener will set the loaded results. } public LiveData<List<ProductEntity>> getProducts() { return observableProducts; } @Override protected void onCleared() { results.removeChangeListener(realmChangeListener); realm.close(); realm = null; } } 

or you can split them into realm viewmodel and realm liveata based on in this article :

 public class LiveRealmData<T extends RealmModel> extends LiveData<RealmResults<T>> { private RealmResults<T> results; private final RealmChangeListener<RealmResults<T>> listener = new RealmChangeListener<RealmResults<T>>() { @Override public void onChange(RealmResults<T> results) { setValue(results);} }; public LiveRealmData(RealmResults<T> realmResults) { results = realmResults; } @Override protected void onActive() { results.addChangeListener(listener); } @Override protected void onInactive() { results.removeChangeListener(listener); } } public class CustomResultViewModel extends ViewModel { private Realm mDb; private LiveData<String> mLoansResult; public CustomResultViewModel() { mDb = Realm.getDefaultInstance(); mLoansResult = RealmUtils.loanDao(mDb).getAll(); } public LiveData<String> getLoansResult() { return mLoansResult; } @Override protected void onCleared() { mDb.close(); super.onCleared(); } } 

In any case, you turned off the automatic update of Realm and the lazy loaded result in LiveData and ViewModel, separately from fragments / adapters:

 // based on https://github.com/googlesamples/android-architecture-components/blob/178fe541643adb122d2a8925cf61a21950a4611c/BasicSample/app/src/main/java/com/example/android/persistence/ProductListFragment.java public class ProductListFragment extends LifecycleFragment { private ProductAdapter productAdapter; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { //... productAdapter = new ProductAdapter(mProductClickCallback); //... } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); final ProductListViewModel viewModel = ViewModelProviders.of(this).get(ProductListViewModel.class); // <-- ! subscribeUi(viewModel); } private void subscribeUi(ProductListViewModel viewModel) { // Update the list when the data changes viewModel.getProducts().observe(this, (myProducts) -> { if (myProducts == null) { // ... } else { productAdapter.setProductList(myProducts); //... } }); } } 

But if you are not using the architectural components of Android, even then what you need to remember is this:

RealmResults is a list of proxy objects that mutate in place, and it has change listeners.

So you need to either wrap it as Flowable with the LAST backpressure, akin to

 private io.reactivex.Flowable<RealmResults<T>> realmResults() { return io.reactivex.Flowable.create(new FlowableOnSubscribe<RealmResults<T>>() { @Override public void subscribe(FlowableEmitter<RealmResults<T>> emitter) throws Exception { Realm observableRealm = Realm.getDefaultInstance(); RealmResults<T> results = realm.where(clazz)./*...*/.findAllSortedAsync("field", Sort.ASCENDING); final RealmChangeListener<RealmResults<T>> listener = _results -> { if(!emitter.isDisposed()) { emitter.onNext(_results); } }; emitter.setDisposable(Disposables.fromRunnable(() -> { observableRealm.removeChangeListener(listener); observableRealm.close(); })); observableRealm.addChangeListener(listener); emitter.onNext(observableRealm); } }, BackpressureStrategy.LATEST).subscribeOn(scheduler).unsubscribeOn(scheduler); 

Or create your own MutableLiveList interface.

 public interface MutableLiveList<T> extends List<T> { public interface ChangeListener { void onChange(MutableLiveList<T> list); } void addChangeListener(ChangeListener listener); void removeChangeListener(ChangeListener listener); } 
+2
source

Providing a different opinion, as I refused, voted for a different answer. I would like to discuss your purpose first.

if necessary, I can return to the standard SQLite-based database without overwriting most applications

Realm is not equivalent to the standard SQLite-based database. What you are looking for in this case is something like StorIO . Realm is designed in such a way that you should use RealmObject as a base for your models. If you just want to get data from a table, Realm is not for you.

Now, after the fiasco of Parse, it became necessary to abstract the third-party library. So, if this is your motivation, then you really need to wrap everything that you use from the kingdom. Something like that:

 public class Db { public class Query<M extends RealmObject> { private final RealmQuery<M> realmQuery; Query(Db db, Class<M> clazz) { this.realmQuery = RealmQuery.createQuery(db.realmInstance, clazz); } public Query<M> equalTo(String field, Integer value) { realmQuery.equalTo(field, value); return this; } public Results<M> findAll() { return new Results<>(realmQuery.findAll()); } public M findFirst() { return realmQuery.findFirst(); } } public class Results<M extends RealmObject> extends AbstractList<M> { private final RealmResults<M> results; Results(RealmResults<M> results) { this.results = results; } public void removeLast() { results.removeLast(); } @Override public M get(int i) { return results.get(i); } @Override public int size() { return results.size(); } } public interface Transaction { void execute(); abstract class Callback { public abstract void onSuccess(); public void onError(Throwable error) { } } } private Realm realmInstance; Db() { realmInstance = Realm.getDefaultInstance(); } public void executeTransaction(@NonNull Db.Transaction dbTransaction) { realmInstance.executeTransaction(realm -> dbTransaction.execute()); } public void close() { realmInstance.close(); } } 

At this point, you simply determine what operations you use in your application in the Db class. And, if you are like me, you probably are not using them all, so everything should be in order.

It's beautiful? No. It's necessary? Depends on what you want to achieve.

Edit : for a bonus point, Db implement AutoCloseable :

 try(Db db = new Db()) { db.executeTransaction(() -> /*...*/); } 
+1
source

Source: https://habr.com/ru/post/1239261/


All Articles