I have this query to update data already in my realm table;
for (MyGameEntrySquad squad : response.body().getSquad()) {
subscription = realm.where(RealmPlayer.class).equalTo("id", squad.getPlayer().getId())
.findFirstAsync()
.asObservable()
.subscribe(new Action1<RealmObject>() {
#Override
public void call(RealmObject realmObject) {
}
});
}
I would like to perform this query asynchronously then display the results on the UI.
Basically, whatever is been returned by response.body().getSquad() has an id matching a record already in the DB; and that is what am using in my equalTo method.
Based on the data received, I would like to update two columns on each of the record matching the IDs.
However, I am facing a few challenges on this:
The Action1 in subscribe is returning a RealmObject instead of a PlayerObject
How to proceed from here
Any guidance on this will be appreciated.
Thanks
Update
if (response.isSuccessful()) {
//asynchronously update the existing players records with my squad i.e is_selected
for (MyGameEntrySquad squad : response.body().getSquad()) {
realm.where(RealmPlayer.class).equalTo("id", squad.getPlayer().getId())
.findFirstAsync()
.<RealmPlayer>asObservable()
.filter(realmPlayer -> realmPlayer.isLoaded())
.subscribe(player -> {
realm.beginTransaction();
if (squad.getPlayer().getPosition().equals("GK")) {
player.setPlaygroundPosition("gk");
player.setIsSelected(true);
}
// pick the flex player
if (squad.isFlex()) {
player.setPlaygroundPosition("flex");
player.setIsSelected(true);
}
// pick the Goalie
if (squad.getPlayer().getPosition().equals("GK")) {
player.setPlaygroundPosition("gk");
player.setIsSelected(true);
}
// pick the DFs
if ((squad.getPlayer().getPosition().equals("DF")) && (!squad.isFlex())) {
int dfCounter = 1;
player.setPlaygroundPosition(String.format(Locale.ENGLISH, "df%d", dfCounter));
player.setIsSelected(true);
dfCounter++;
}
// pick the MFs
if ((squad.getPlayer().getPosition().equals("MF")) && (!squad.isFlex())) {
int mfCounter = 1;
player.setPlaygroundPosition(String.format(Locale.ENGLISH, "mf%d", mfCounter));
player.setIsSelected(true);
mfCounter++;
}
// pick the FWs
if ((squad.getPlayer().getPosition().equals("FW")) && (!squad.isFlex())) {
int fwCounter = 1;
player.setPlaygroundPosition(String.format(Locale.ENGLISH, "mf%d", fwCounter));
player.setIsSelected(true);
fwCounter++;
}
realm.copyToRealmOrUpdate(player);
realm.commitTransaction();
updateFieldPlayers();
});
}
hideProgressBar();
}
realm.where(RealmPlayer.class).equalTo("id", squad.getPlayer().getId())
.findFirstAsync()
.<RealmPlayer>asObservable()
.subscribe(new Action1<RealmPlayer>() {
#Override
public void call(RealmPlayer player) {
}
});
You should do like that.
Btw, it's bad idea to do it in a cycle - check in method of RealmQuery.
for (MyGameEntrySquad squad : response.body().getSquad()) { // btw why is this not `Observable.from()`?
subscription = realm.where(RealmPlayer.class).equalTo("id", squad.getPlayer().getId())
.findFirstAsync()
.asObservable()
This should not be on the UI thread. It should be on a background thread. On a background thread, you need to use synchronous query instead of async query.
Even on the UI thread, you'd still need to filter(RealmObject::isLoaded) because it's an asynchronous query, and in case of findFirstAsync() you need to filter for RealmObject::isValid as well.
For this case, you would not need asObservable() - this method is for observing a particular item and adding a RealmChangeListener to it. Considering this should be on a background thread with synchronous query, this would not be needed (non-looper background threads cannot be observed with RealmChangeListeners).
You should also unsubscribe from any subscription you create when necessary.
And yes, to obtain RealmPlayer in asObservable(), use .<RealmPlayer>asObservable().
In short, you should put that logic on a background thread, and listen for changes on the UI thread. Background thread logic must be done with the synchronous API. You will not need findFirstAsync for this.
Related
So room has deprecated the beginTransaction/setTransactionSuccessful/endTransaction and allows to use runnables and callables as replacement with runInTransaction() function.
But as far as i know, i can't get a return value from runnable or callable. What is the way to fix the code and remove the direct transactions without changing the functionality?
Observable.fromCallable is used for 2 reasons: to make database calls on non UI thread and to get a value returned once it finishes.
Haven't tried any other aproaches as I'm not sure what other aproaches could be.
Observable.fromCallable(//Observable.fromCallable allows to return value to subscriber
() -> {
long entryId = 0;
db.beginTransaction();
if (info.isChanged())
entryId = db.biDao().insert(info);
if (info.xChanged())
db.xDao().insert(info.getX());
db.setTransactionSuccessful();
db.endTransaction();
return entryId;//after insertion our id in to a model it should be updated since it's 0 initialized by default
})
//.subscribeOn(Schedulers.single())
.subscribeOn(Schedulers.io())
//because we can set model data only on main thread
.observeOn(AndroidSchedulers.mainThread())
.subscribe(//either way we close db eventually
entryId -> {
if (entryId > 0)
someModel.setId(entryId);
someModel.setDataChanged(false);
db.close();
},//done
throwable -> {//error
db.close();
throwable.printStackTrace();
}
);
I accept alternative sollution which:
1) removes deprecation
2) runs database queries in non UI thread
3) sets the query resuts in UI thread
what i've also tried was changing part of the code, but it looks dam ugly / wrong way of using "final long[]". Any alternatives?
Observable.fromCallable(//Observable.fromCallable allows to return value to subscriber
() -> {
final long[] entryId = {0};
db.runInTransaction(new Runnable() {
#Override
public void run() {
if (info.isChanged())
entryId[0] = db.biDao().insert(info);
if (info.xChanged())
db.xDao().insert(info.getX());
}
});
return entryId[0];//after insertion our id in settingsModel should be updated since it's 0 initialized.
})
//.subscribeOn(Schedulers.single())
.subscribeOn(Schedulers.io())
//because we can set model data only on main thread
.observeOn(AndroidSchedulers.mainThread())
.subscribe(//either way we close db eventually
entryId -> {
if (entryId > 0)
someModel.setId(entryId);
someModel.setDataChanged(false);
db.close();
},//done
throwable -> {//error
db.close();
throwable.printStackTrace();
}
);
I have been struggling on this question a few days. So what I want to do on Android Slices is create slice with information which is from the back-end service. For example:
on SliceProvider class
#Override
public Slice onBindSlice(Uri sliceUri) {
l.d(TAG,"onBindSlice called");
switch (sliceUri.getPath()) {
case "/product":
makeRequestForProduct();
return createProductSlice(sliceUri);
}
return null;
}
and
private void makeRequestForProduct() {
String url = Environment.getInstance().getCompleteUrl("etc..");
RetrofitFactory.defaultBuilder(ProductWebInterface.class)
.getProductResponse(url).enqueue(new ProductWebcallback());
}
public void onEventMainThread(ProductReceivedEvent response) {
if (response.getProduct() != null) { //do something
}
}
But I have no idea how to do it. Above code is not working. It is giving me an Exception.
According to Google Documentation here :
onBindSlice should return as quickly as possible so that the UI tied to this slice can be responsive. No network or other IO will be allowed during onBindSlice. Any loading that needs to be done should happen in the background with a call to ContentResolver.notifyChange(Uri, ContentObserver) when the app is ready to provide the complete data in onBindSlice.
You must therefore do your work in the background thread.
See an example below in Kotlin:
private fun makeRequestForProductInTheBackground(sliceUri : SliceUri) {
Completable.fromAction {
makeRequestForProduct(sliceUri)
}.subscribeOn(Schedulers.io()).subscribe()
}
After the request completes you can save your data somewhere e.g a variable or a repository.
fun onEventMainThread(response: ProductReceivedEvent) {
if (response.getProduct() != null) {
//Save your data in a variable or something depending on your needs
product == response.getProduct()
//This will call the *onBindSlice()* method again
context?.contentResolver?.notifyChange(sliceUri, null)
}
}
You can then use the product data in your createProductSlice(sliceUri) method
I'm coding an Android App. I'm using Retrofit with RxJava in my network layer. Basically the issue that I have is that I have an endpoint called /feed. This endpoint returns a collection of articles that I show in a RecyclerView. Some of those articles are Ads articles, some are Events and others are Posts. I'm using a clean architecture, so I have an Interactor per each thing that I want to do. This is how my GetFeedInteractor looks like:
#Override
public void execute(final Callback callback, int offset, int pageSize) {
final String userSipId = mUserSipid.get();
mFeedRepository.getFeed(offset, pageSize)
.subscribe(new DefaultSubscriber<List<BaseFeedItem>>() {
#Override
public void onNext(List<BaseFeedItem> baseFeedItems) {
List<BaseFeedItem> cacheCollection = new ArrayList<BaseFeedItem>();
for (BaseFeedItem baseFeedItem : baseFeedItems) {
if (!(baseFeedItem instanceof AdFeedItem)) {
cacheCollection.add(baseFeedItem);
}
}
mFeedCache.saveFeeds(cacheCollection);
callback.onFeedsFetched(baseFeedItems);
}
});
}
As you can see the Callback that I use to show the items in the UI takes all the three types of Feeds and shows them. But I need to save only the Post articles and the Event articles in a Sqlite Cache. The thing is that I don't like doing that for instanceof inside the Subscriber. Is there some better Rx-way of doing this?
Update:
BTW I use the FeedCache#saveFeed method in several places, but only here is where I can get Ads Articles. So doing the Ad filtering inside the FeedCache#saveFeed is not a good choice.
Update2:
I think that the perfect solution for this would be if I could filter the collection using some of the Rx-methods, and have a Subscriber that handles the raw list, and another that handles the ad-filtered list. Something like that.
So, basically you want to 2 streams out of one stream. One will be the raw stream (for displaying UI), and the other will be a filtered stream (for saving in database). Unfortunately, you cannot have multiple onSubscribes, but we can use the concept of Subjects.
final PublishSubject<BaseFeedItem> subject = PublishSubject.create();
subject.subscribe(o -> {
// here will be emitted all items
// update the UI
});
mFeedRepository.getFeed(offset, pageSize)
.map(item -> {
subject.onNext(item);
return item;
})
.filter(item -> !(item instanceof AdFeedItem))
.subscribe(items -> {
// no `AddFeedItem`s here
// save into database
});
I need to solve putting data to a realm database like this:
I have an object called obtained_code;
I have a realmList of Obtained codes in an object called Offer;
I download obtained codes separately, and by their offer id assign them to the lists of each object. The problem is that I can't add them because when I check the size, it's always 0.
Here is the code:
ObtainedCodes codes = response.body();
for (ObtainedCode c : codes.getObtainedCodes()) {
Offer offer = RealmController.with(SplashActivity.this).getOffer(c.getOffer_id());
if (offer != null) {
Log.d("Size", "Offer not null");
realm1.beginTransaction();
RealmList<ObtainedCode> list = offer.getObtained_codes();
if (!list) { // if the 'list' is managed, all items in it is also managed
RealmList<ObtainedCode> managedImageList = new RealmList<>();
for (ObtainedCode item : list) {
if (item) {
managedImageList.add(item);
} else {
managedImageList.add(realm1.copyToRealm(item));
}
}
list = managedImageList;
}
offer.setObtained_codes(obtainedCodes);
Log.d("Size", String.valueOf(offer.getObtained_codes().size()));
realm1.copyToRealmOrUpdate(offer);
realm1.commitTransaction();
}
offer = RealmController.with(SplashActivity.this).getOffer(c.getOffer_id());
Log.d("Size", String.valueOf(offer.getObtained_codes().size()));
}
1.) the Ravi Tamada tutorial on InfoHive is a terrible mess, please refer to my remake of that example instead.
If you managed to start using 0.82.1 because Ravi Tamada claimed that a 4 years old version is "stable", well I know that it's not. Use 1.2.0 instead (or the latest version which is 3.4.1)
And if you see a RealmController.with(), run, because it ignores thread-confinement. The moment you try to access it from a background thread, it'll crash.
On background threads, you'd need to do
#Override
public void run() {
try(Realm realm = Realm.getDefaultInstance()) {
repository.whatever(realm); // pass Realm instance to database methods
} // auto-close
// end of thread
}
2.) you are executing writes on the UI thread, that is bad, from UI thread you should use realm.executeTransactionAsync(), but in your case you should actually execute the Retrofit call on a background thread using Ęxecutors.newSingleThreadedPool() and call it with call.execute() instead of call.enqueue().
3.) You should write to Realm on the background thread, and on the UI thread you should use RealmChangeListener to listen to writes.
4.) your code doesn't work because you're setting an unmanaged list to a managed RealmObject.
You should modify the existing RealmList inside the RealmObject, and add only managed objects to it.
Executor executor = Executors.newSingleThreadExecutor(); // field variable
// ...
void someMethod() {
executor.execute(new Runnable() {
#Override
public void run() {
Response<ObtainedCodes> response = retrofitService.getObtainedCodes().execute(); // run on current thread
ObtainedCodes codes = response.body();
if(codes == null) return;
try(Realm r = Realm.getDefaultInstance()) {
r.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
for(ObtainedCode obtainedCode : codes.getObtainedCodes()) {
Offer offer = realmRepository.getOffer(realm, obtainedCode.getOfferId());
if(offer == null) {
offer = realm.createObject(Offer.class, obtainedCode.getOfferId());
// map properties to offer if possible
}
RealmList<ObtainedCode> offerCodes = offer.getObtainedCodes();
ObtainedCode managedObtainedCode = realm.where(ObtainedCode.class).equalTo("obtainedCodeId", obtainedCode.getId()).findFirst();
if(managedObtainedCode == null) {
managedObtainedCode = realm.createObject(ObtainedCode.class, obtainedCode.getId());
// map properties from obtained code to managed obtained code
}
if(!offerCodes.contains(managedObtainedCode)) {
offerCodes.add(managedObtainedCode);
}
}
}
});
}
}
});
}
I have an API call and I want to wrap it using Observable:
private Observable<RealmResults<Account>> getAccounts() {
final Observable<RealmResults<Account>> realmAccounts =
Observable.defer(new Func0<Observable<RealmResults<Account>>>() {
#Override
public Observable<RealmResults<Account>> call() {
return RealmObservable.results(getActivity(), new Func1<Realm, RealmResults<Account>>() {
#Override
public RealmResults<Account> call(Realm realm) {
return realm.where(Account.class).findAll();
}
});
}
});
return Observable
.create(new Observable.OnSubscribe<RealmResults<Account>>() {
#Override
public void call(final Subscriber<? super RealmResults<Account>> subscriber) {
DataBridge.getAccounts(Preferences.getString(Constant.ME_GUID, ""), new OnResponseListener() {
#Override
public void OnSuccess(Object data) {
Log.d("Stream", "onSuccess");
realmAccounts.subscribe(subscriber);
}
#Override
public void onFailure(Object data) {
subscriber.onError(new Exception(data.toString()));
}
});
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.startWith(realmAccounts);
}
and I use it like
Observable<Accounts> accounts = getAccounts().flatMap(
new Func1<RealmResults<Account>, Observable<Account>>() {
#Override
public Observable<Account> call(RealmResults<Account> accounts) {
return Observable.from(accounts);
}
});
How can I use the accounts observable multiple times without calling the API each time. I need to process the stream of accounts and extract different sets of data out of it.
The easiest method is to use operator cache, which internally uses ReplaySubject. It cache the source observable items and then serve the results from cache.
...
Observable<<RealmResults<Account>> cachedResult = getAccounts().cache();
Observable<Accounts> accountsObservable = cachedResult.flatMap(...);
Observable<X> xObservable = cachedResult.flatMap(...);
If you would like to avoid caching results you should use Connectable Observables. Usually it only does matter for Hot Observables. Connectable observable does not begin emitting items until its Connect method is called. You can use publish operator to convert to Connectable Observable.
ConnectableObservable<<RealmResults<Account>> connectebleObservable = getAccounts().publish();
Observable<Accounts> accountsObservable = connectebleObservable .flatMap(...);
Observable<X> xObservable = connectebleObservable .flatMap(...);
//You must subscribe before connect
accountsObservable.subsribe(...);
xObservable.subscribe(...);
//start emiting data
connectebleObservable.connect();
The important catch here is that you must subscribe before connect - to avoid data loss - otherwise you must use replay operator, which is similar to cache operator, but used for connectable observable
And what about share ?
It create ConnectableObservable and exposes it as regular Observable. First subscription automatically causes connection and emission.
Share used in your case, without replay may cause data loss or multiple executions depending on timing.
for example for 2 subscribers and one item int the stream you may have fallowing cases:
2 subscriptions created before onNext - works as expected.
second subscription created after onNext but before onComplete - second subscription gets only onComplete
second subscriptinon created after onComplete - 2 executions wihtout caching