I have writeToRealm method that I use very often. And I need to use it from different threads and cut boilerplate code. What is the better way for this task?
private boolean writeToRealm(String user, String id) {
SubscriberObject subscriber = new SubscriberObject();
if(mRealm.where(SubscriberObject.class)
.equalTo(SubscriberObject.ID,id).findAll().isEmpty()
&&mRealm.where(SubscriberObject.class)
.equalTo(SubscriberObject.USERNAME,user).findAll().isEmpty()) {
subscriber.setId(id);
subscriber.setUsername(user);
mRealm.beginTransaction();
mRealm.insert(subscriber);
mRealm.commitTransaction();
return true;
}
return false;
}
I am planning to use construction below (or something like this) but I can't create a correct construction:
public static Boolean writeToRealm(final String user,final String id){
Realm mRealm;
return Flowable.using(
mRealm = Realm.getDefaultInstance(),
new Function<Realm, Boolean>() {
#Override
public Boolean apply(#NonNull Realm realm) throws Exception {
SubscriberObject subscriber = new SubscriberObject();
if(realm.where(SubscriberObject.class)
.equalTo(SubscriberObject.ID,id).findAll().isEmpty()
&&realm.where(SubscriberObject.class)
.equalTo(SubscriberObject.USERNAME,user).findAll().isEmpty()) {
subscriber.setId(id);
subscriber.setUsername(user);
realm.beginTransaction();
realm.insert(subscriber);
realm.commitTransaction();
return true;
}
return false;
}
},
mRealm.close()).subscribeOn(Schedulers.io());
}
Or may be I need to create a thread class with looper for this task?
How to better integrate this method and similar methods into a clean architecture?
I think you're just looking for
private boolean writeToRealm(String user, String id) {
try(Realm realm = Realm.getDefaultInstance()) {
if(realm.where(SubscriberObject.class).equalTo(SubscriberObject.ID,id).count() <= 0L
&& realm.where(SubscriberObject.class).equalTo(SubscriberObject.USERNAME,user).count() <= 0L) {
final SubscriberObject subscriber = new SubscriberObject();
subscriber.setId(id);
subscriber.setUsername(user);
realm.executeTransaction(r -> r.insert(subscriber));
return true;
}
}
return false;
}
I think this could be way an easier solution:
public static Boolean writeToRealm(String user, String id) {
Realm realm = Realm.getDefaultInstance();
SubscriberObject subscriber = new SubscriberObject();
if (realm.where(SubscriberObject.class).equalTo("ID", id).or().equalTo("USERNAME", user).findAll().isEmpty()){
subscriber.setId(id);
subscriber.setUsername(user);
realm.beginTransaction();
realm.insert(subscriber);
realm.commitTransaction();
realm.close();
return true;
}
realm.close();
return false;
}
If you need some explainations, just tell me and I will implement it :)
PS: if I missunderstood your question, let me know!
The asynchronous transaction support works the same way as the current executeTransaction, but instead of opening a Realm on the same thread, it will give you a background Realm opened on a different thread. You can also register a callback if you wish to be notified when the transaction completes or fails.
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
Dog dog = realm.where(Dog.class).equalTo("age", 1).findFirst();
dog.setName("Fido");
}
}, new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
Log.d("REALM", "All done updating.");
Log.d("BG", t.getName());
}
}, new Realm.Transaction.OnError() {
#Override
public void onError(Throwable error) {
// transaction is automatically rolled-back, do any cleanup here
}
});
read more
First of all you can't have boolean return type if you want to do your transaction asynchronously. You will have to use either Interface pass result back to caller or you have to opt for some other means like RxJava.
just to give you example.
The RxJava way(As this would be simplest way):
public static Flowable<Boolean> writeToRealm(final String user,final String id) {
return Flowable.fromCallable(
new Callable<Boolean>() {
#Override
public Boolean call() throws Exception {
Realm realm = Realm.getDefaultInstance();
if(realm.where(SubscriberObject.class)
.equalTo(SubscriberObject.ID,id).findAll().isEmpty()
&&realm.where(SubscriberObject.class)
.equalTo(SubscriberObject.USERNAME,user).findAll().isEmpty()) {
SubscriberObject subscriber = new SubscriberObject();
subscriber.setId(id);
subscriber.setUsername(user);
realm.beginTransaction();
realm.insert(subscriber);
realm.commitTransaction();
mRealm.close();
return true;
}
mRealm.close();
return false;
}
});
}
You subscribe the returned Flowable on desired thread/schedular to perform transaction on that particular thread.
Related
I'm new in RxJava. I have currently executed three API calls parallel which is independent of each other via Retrofit using Single.Zip Operator. On getting a successful response of all three API calls, I have to insert the data from all three APIs into Room database into Different entities which takes 20 seconds.
So I need to execute database operations inside Single.Zip operator. Because the logic is written inside onSuccess method running away before Database Operation performed.
I have tried to take separate Observer for performing database operation but didn't work.
public void callOfflineDataAPIs() {
setIsLoading(true);
Single<BaseResponse<ProductResponse>> single1 = getDataManager().getOfflineProductListApiCall(getDataManager().getLastTimeStampOfflineProductCall()).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui());
Single<BaseResponse<LocationResponse>> single2 = getDataManager().getOfflineLocationListApiCall(getDataManager().getLastTimeStampOfflineLocationCall()).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui());
Single<BaseResponse<OfflineMasterData>> single3 = getDataManager().getOfflineMasterDataListApiCall(getDataManager().getLastTimeStampOfflineMasterCall()).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui());
DisposableSingleObserver<List<Boolean>> result = Single.zip(single3, single1, single2,
(offlineMasterDataBaseResponse, productResponseBaseResponse, locationResponseBaseResponse) -> {
List<Boolean> apiCalls = new ArrayList<>();
apiCalls.add(masterDataCRUDOperation(offlineMasterDataBaseResponse));
apiCalls.add(productDataCRUDOperation(productResponseBaseResponse));
apiCalls.add(locationDataCRUDOperation(locationResponseBaseResponse));
return apiCalls;
}).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui()).subscribeWith(new DisposableSingleObserver<List<Boolean>>() {
#Override
public void onSuccess(List<Boolean> apiCalls) {
setIsLoading(false);
LogHelper.e(TAG, "DisposableSingleObserver- onSuccess");
boolean isSync = true;
for (int i = 0; i < apiCalls.size(); i++) {
if (!apiCalls.get(i)) {
isSync = false;
LogHelper.e(TAG, "DisposableSingleObserver- onSuccess- apiCalls.get(i)", i);
callOfflineDataAPIs();
break;
}
}
if (isSync) {
LogHelper.e(TAG, "IF-isSync");
if (BuildConfig.IS_CLIENT_BUILD) {
LogHelper.e(TAG, "IF-isSync-IS_CLIENT_BUILD-true");
getDataManager().setCurrentWarehouseKey(1);
getNavigator().onGoButtonClick();
} else {
LogHelper.e(TAG, "ELSE-isSync-IS_CLIENT_BUILD-false");
getWarehouseList();
}
}
}
#Override
public void onError(Throwable e) {
LogHelper.e(TAG, "DisposableSingleObserver- Throwable");
setIsLoading(false);
String errorMessage = new NetworkError(e).getAppErrorMessage();
getNavigator().exitApplicationOnError(errorMessage);
}
});
}
Logic written inside onSuccess Method execute once all DB Operation performed.
You can modify your code to something like:
DisposableSingleObserver<List<Boolean>> result = Single.zip(single3, single1, single2,
(offlineMasterDataBaseResponse, productResponseBaseResponse, locationResponseBaseResponse) -> {
List<Boolean> apiCalls = new ArrayList<>();
apiCalls.add(masterDataCRUDOperation(offlineMasterDataBaseResponse));
apiCalls.add(productDataCRUDOperation(productResponseBaseResponse));
apiCalls.add(locationDataCRUDOperation(locationResponseBaseResponse));
return apiCalls;
}).subscribeOn(getSchedulerProvider().io())
.map(new Function<List<Boolean> apiCalls, List<Boolean> apiCalls>() {
#Override
public List<Boolean> apiCalls apply(List<Boolean> apiCalls) throws Exception {
// perform database operations here
return apiCalls;
}
})
.observeOn(getSchedulerProvider().ui())
.subscribe(new Observer<List<Boolean>>() {
#Override
public void onNext(User user) {
// Do something
}
#Override
public void onError(Throwable e) {
// Do something
}
#Override
public void onComplete() {
// Do something
}
});
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);
}
}
Is there any way to call Realm queries from AsyncTask?
I have so many queries that are doing join, So i want to call them from a separate One AsyncTask to avoid the load on UI Thread. For now i am using DefaultInstance of Realm everywhere. I get this error
Realm objects can only be accessed on the thread they where created
P.S I know Realm has its own Async for every query, but as i just mentioned i have alot of separate calls that are further doing joins and for loops.
EDIT
here's my code for an Async
#Override
protected Object doInBackground(Object[] params) {
//Step 1: Find All quote_taxes
Realm realm = Realm.getDefaultInstance();
listTaxData = new ArrayList<TaxData>();
try {
RealmResults<quote_taxes> listQuoteTaxes = quote_taxes.get_from_quotes(realm, quote.getId());
if (listQuoteTaxes != null && listQuoteTaxes.size() > 0) {
for (quote_taxes quoteTax : listQuoteTaxes) {
TaxData taxData = new TaxData();
taxData.setTaxName(quoteTax.getTaxName());
taxData.setAccountNumber("" + quoteTax.getAccountNumber());
taxData.setTaxRate("" + quoteTax.getTaxRate() + "%");
double total = quote_taxes.total(realm, quoteTax);
showLog("Total = " + total);
}
}
}catch (Exception ex)
{
}finally {
realm.close();
}
return null;
}
You just have to do what the docs say:
For AsyncTask this is a good pattern:
protected Void doInBackground(Void... params) {
Realm realm = null;
try {
realm = Realm.getDefaultInstance();
// ... Use the Realm instance ...
} finally {
if (realm != null) {
realm.close();
}
}
return null;
}
More importantly, you can use try-with-resources:
protected Void doInBackground(Void... params) {
try(Realm realm = Realm.getDefaultInstance()) {
// ... Use the Realm instance ...
}
return null;
}
If you are using Thread or Runnable for short-lived tasks, the follow pattern is recommended:
// Run a non-Looper thread with a Realm instance.
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
Realm realm = null;
try {
realm = Realm.getDefaultInstance();
// ... Use the Realm instance ...
} finally {
if (realm != null) {
realm.close();
}
}
}
});
thread.start();
And use a RealmChangeListener on the UI thread to be notified of successful transactions in background threads.
EDIT: Oh, you want to perform asynchronous queries.
I have so many queries that are doing join, So i want to call them from a separate One AsyncTask to avoid the load on UI Thread.
...while I truly doubt you have any "join"s considering Realm is not a relational database and the concept of joins doesn't exist in Realm; if you want asynchronous queries, you shouldn't overcomplicate your design with nonsense like AsyncTask. Just use the asynchronous query methods.
RealmResults<Something> results;
RealmChangeListener realmChangeListener = new RealmChangeListener() {
#Override
public void onChange(Object element) {
if(results != null && results.isValid() && results.isLoaded()) {
updateUI(results);
}
}
};
//...
results = realm.where(Something.class)./*...*/.findAllAsync(); // <-- async query
results.addChangeListener(realmChangeListener);
Realm has its Async load default functionality :-
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
// Use the Realm instance
}
});
Above execution done on background thread, and it's gives callback when db changes like.
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
}
}, new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
// success callback
}
}, new Realm.Transaction.OnError() {
#Override
public void onError(Throwable error) {
// error callback
}
});
I believe that you create Realm objects in doInBackground and then process results in onPostExecute? To avoid this you can use IntentService instead of AsyncTask. If you still want to use AsyncTask, you can process query results in doInBackgroundas well and then return needed data (Lists, POJOs etc.) to onPostExecute.
I try to replicate database trigger function with Realm with Rx. Once I get RealmList emitted, I do some stuff with it and save. Sadly, this results into Realm's change listener to be executed again, emitting the list over and over again.
Dummy example:
realm.where(MyRealmObject.class)
.equalTo("state", "new")
.findAll()
.asObservable()
.flatMap(new Func1<RealmResults<MyRealmObject>, Observable<MyRealmObject>>() {
#Override
public Observable<MyRealmObject> call(RealmResults<MyRealmObject> list) {
return Observable.from(list);
}
})
.subscribe(new Action1<MyRealmObject>() {
#Override
public void call(final MyRealmObject object) {
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
// do any realm change
}
});
}
});
Once I commit the transaction in subscriber, new RealmList is emited from observable. I know why this happens, I just don't see any way how to workaround this.
This takes us to my question. Is there any way how to replicate trigger functionality with realm where I will do any realm change?
Workaround can be built with helper stream determing whether next item from db should be consumed. Every data store into db should be accompanied with write into helper stream. Running test below yields:
upstream: IgnoreAction{action='start', ignoreNext=false}
result: 1
result: 2
result: 3
upstream: IgnoreAction{action='1', ignoreNext=true}
upstream: IgnoreAction{action='2', ignoreNext=true}
upstream: IgnoreAction{action='3', ignoreNext=true}
So, first data ("start") is consumed, and writes triggered in onNext are ignored.
#Test
public void rxIgnore() throws Exception {
MockDb mockDb = new MockDb();
BehaviorSubject<Boolean> ignoreNextStream = BehaviorSubject.create(false);
Observable<String> dataStream = mockDb.dataSource();
dataStream.zipWith(ignoreNextStream, Data::new)
.doOnNext(action -> System.out.println("upstream: " + action))
.filter(Data::isTakeNext)
.flatMap(__ -> Observable.just(1, 2, 3))
.subscribe(new Observer<Integer>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Integer val) {
System.out.println("result: " + val);
ignoreNextStream.onNext(true);
mockDb.data(String.valueOf(val));
}
});
mockDb.data("start");
Observable.empty().delay(1, TimeUnit.MINUTES).toBlocking().subscribe();
}
private static class Data {
private final String action;
private final boolean ignoreNext;
public Data(String action, boolean ignoreNext) {
this.action = action;
this.ignoreNext = ignoreNext;
}
public boolean isTakeNext() {
return !ignoreNext;
}
#Override
public String toString() {
return "IgnoreAction{" +
"action='" + action + '\'' +
", ignoreNext=" + ignoreNext +
'}';
}
}
private static class MockDb {
private final Subject<String, String> subj = PublishSubject.<String>create()
.toSerialized();
public void data(String action) {
subj.onNext(action);
}
Observable<String> dataSource() {
return subj;
}
}
When using Realm Observable by calling asObservable() on the query, with amb() or switchIfEmpty() cause the realm's observable to not finish its sequence. A work around to this can be done by using Observable.just() instead of Realms asObservable().
I cant figure out if this is caused by my code or a bug in rx-java or Realm.
mSubscription = getRealmObservable(params).switchIfEmpty(getNetworkObservable(params))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
}
private Observable<model> getNetworkObservable(UrlParams params) {
final api service = NetworkManager.getAPI();
return service.getModel(params.toMap())
.doOnNext(new Action1<RealmList<Model>>() {
#Override
public void call(RealmList<Model> models) {
if (models != null && models.size() > 0) {
mRealm.beginTransaction();
mRealm.copyToRealmOrUpdate(models);
mRealm.commitTransaction();
}
}
})
.flatMap(new Func1<RealmList<Model>, Observable<Model>>() {
#Override
public Observable<Model> call(RealmList<Model> models) {
return Observable.from(models);
}
});
}
private Observable<Model> getRealmObservable(final UrlParams params) {
return Observable.just(mRealm.where(Model.class).findAll())/*.asObservable()*/ <- Using this cause this sequence not to finish
.filter(new Func1<RealmResults<Model>, Boolean>() {
#Override
public Boolean call(RealmResults<Model> models) {
return models != null && models.isValid() && models.size() > 0;
}
})
.flatMap(new Func1<RealmResults<Model>, Observable<Model>>() {
#Override
public Observable<Model> call(RealmResults<Model> models) {
return Observable.from(models);
}
});
}
The observable created by calling asObservable() on RealmResults will never terminate (call onComplete). See the documentation here.