/*
* Copyright 2015 Realm Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.realm.examples.rxjava.gotchas;
import android.app.Activity;
import android.os.Bundle;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.List;
import java.util.Random;
import io.realm.Realm;
import io.realm.Sort;
import io.realm.examples.rxjava.R;
import io.realm.examples.rxjava.model.Person;
import rx.Observable;
import rx.Subscription;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.schedulers.Schedulers;
import rx.subscriptions.CompositeSubscription;
/**
* This class shows some of the current obstacles when combining RxJava and Realm. 2 things are
* important to keep in mind.
*
* 1) Thread confinement: Realm objects are thread confined, so trying to access them from another thread will throw
* an exception.
*
* 2) Realm objects are live objects. This means that the same object will alter it's state automatically over time to
* automatically reflect the latest state in Realm.
*
* Both of these characteristics doesn't play well with RxJava's threading model which favor immutable thread-safe
* objects.
*
* Work is in progress to make it easier to work around these constraints. See
* - https://github.com/realm/realm-java/issues/1208
* - https://github.com/realm/realm-java/issues/931
*/
public class GotchasActivity extends Activity {
private Realm realm;
private Subscription subscription;
private ViewGroup container;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_gotchas);
container = (ViewGroup) findViewById(R.id.list);
realm = Realm.getDefaultInstance();
}
@Override
protected void onResume() {
super.onResume();
Subscription distinctSubscription = testDistinct();
Subscription bufferSubscription = testBuffer();
Subscription subscribeOnSubscription = testSubscribeOn();
// Trigger updates
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
realm.where(Person.class).findAllSorted( "name", Sort.ASCENDING).get(0).setAge(new Random().nextInt(100));
}
});
subscription = new CompositeSubscription(
distinctSubscription,
bufferSubscription,
subscribeOnSubscription
);
}
/**
* Shows how to be careful with `subscribeOn()`
*/
private Subscription testSubscribeOn() {
Subscription subscribeOn = realm.asObservable()
.map(new Func1<Realm, Person>() {
@Override
public Person call(Realm realm) {
return realm.where(Person.class).findAllSorted("name").get(0);
}
})
// The Realm was created on the UI thread. Accessing it on `Schedulers.io()` will crash.
// Avoid using subscribeOn() and use Realms `findAllAsync*()` methods instead.
.subscribeOn(Schedulers.io()) //
.subscribe(new Action1<Person>() {
@Override
public void call(Person person) {
// Do nothing
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
showStatus("subscribeOn: " + throwable.toString());
}
});
// Use Realms Async API instead
Subscription asyncSubscribeOn = realm.where(Person.class).findAllSortedAsync("name").get(0).<Person>asObservable()
.subscribe(new Action1<Person>() {
@Override
public void call(Person person) {
showStatus("subscribeOn/async: " + person.getName() + ":" + person.getAge());
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
showStatus("subscribeOn/async: " + throwable.toString());
}
});
return new CompositeSubscription(subscribeOn, asyncSubscribeOn);
}
/**
* Shows how to be careful with `buffer()`
*/
private Subscription testBuffer() {
Observable<Person> personObserver = realm.asObservable().map(new Func1<Realm, Person>() {
@Override
public Person call(Realm realm) {
return realm.where(Person.class).findAllSorted("name").get(0);
}
});
// buffer() caches objects until the buffer is full. Due to Realms auto-update of all objects it means
// that all objects in the cache will contain the same data.
// Either avoid using buffer or copy data into an unmanaged object.
return personObserver
.buffer(2)
.subscribe(new Action1<List<Person>>() {
@Override
public void call(List<Person> persons) {
showStatus("Buffer[0] : " + persons.get(0).getName() + ":" + persons.get(0).getAge());
showStatus("Buffer[1] : " + persons.get(1).getName() + ":" + persons.get(1).getAge());
}
});
}
/**
* Shows how to to be careful when using `distinct()`
*/
private Subscription testDistinct() {
Observable<Person> personObserver = realm.asObservable().map(new Func1<Realm, Person>() {
@Override
public Person call(Realm realm) {
return realm.where(Person.class).findAllSorted("name").get(0);
}
});
// distinct() and distinctUntilChanged() uses standard equals with older objects stored in a HashMap.
// Realm objects auto-update which means the objects stored will also auto-update.
// This makes comparing against older objects impossible (even if the new object has changed) because the
// cached object will also have changed.
// Use a keySelector function to work around this.
Subscription distinctItemTest = personObserver
.distinct() // Because old == new. This will only allow the first version of the "Chris" object to pass.
.subscribe(new Action1<Person>() {
@Override
public void call(Person p) {
showStatus("distinct(): " + p.getName() + ":" + p.getAge());
}
});
Subscription distinctKeySelectorItemTest = personObserver
.distinct(new Func1<Person, Integer>() { // Use a keySelector function instead
@Override
public Integer call(Person p) {
return p.getAge();
}
})
.subscribe(new Action1<Person>() {
@Override
public void call(Person p) {
showStatus("distinct(keySelector): " + p.getName() + ":" + p.getAge());
}
});
return new CompositeSubscription(distinctItemTest, distinctKeySelectorItemTest);
}
private void showStatus(String message) {
TextView v = new TextView(this);
v.setText(message);
container.addView(v);
}
@Override
protected void onPause() {
super.onPause();
subscription.unsubscribe();
}
@Override
protected void onDestroy() {
super.onDestroy();
realm.close();
}
}