36

I am learning Observer pattern, I want my observable to keep track of a certain variable when it changes it's value and do some operations, I've done something like :

public class Test extends MyChildActivity {

   private int VARIABLE_TO_OBSERVE = 0;

   Observable<Integer> mObservable = Observable.just(VARIABLE_TO_OBSERVE);  

   protected void onCreate() {/*onCreate method*/
       super();
       setContentView();
       method();
       changeVariable();
   }

   public void changeVariable() {
       VARIABLE_TO_OBSERVE = 1;
   }

   public void method() {
       mObservable.map(value -> {
            if (value == 1) doMethod2();
            return String.valueOf(value);
       }).subScribe(string -> System.out.println(string));
   }

   public void doMethod2() {/*Do additional operations*/}

}

But doMethod2() doesn't get called

Karate_Dog
  • 1,265
  • 5
  • 20
  • 37
  • I left Java long time ago. Here is my thought. Because `VARIABLE_TO_OBSERVE` is copied to Observable. Hence, it isn't observed. How about `private Integer VARIABLE_TO_OBSERVE = 0;`? – Danh Aug 03 '16 at 09:21

3 Answers3

48

Nothing is magic in the life : if you update a value, your Observable won't be notified. You have to do it by yourself. For example using a PublishSubject.

public class Test extends MyChildActivity {

    private int VARIABLE_TO_OBSERVE = 0;

    Subject<Integer> mObservable = PublishSubject.create();  

   protected void onCreate() {/*onCreate method*/
        super();
        setContentView();
        method();
        changeVariable();
    }

    public void changeVariable() {
        VARIABLE_TO_OBSERVE = 1;
        // notify the Observable that the value just change
        mObservable.onNext(VARIABLE_TO_OBSERVE);
    }

   public void method() {
       mObservable.map(value -> {
           if (value == 1) doMethod2();
           return String.valueOf(value);
       }).subScribe(string -> System.out.println(string));
   }

   public void doMethod2() {/*Do additional operations*/}

 }
dwursteisen
  • 11,435
  • 2
  • 39
  • 36
  • 1
    I was under the impression that an observer would immediately notify all its subscribers every time the object it observes changes. So basically we need to keep telling all those subscribers so they listen to any changes ? – Karate_Dog Aug 03 '16 at 15:08
  • 3
    You need to keep telling all your Observable that something changes. Then all subscribers will be notified. – dwursteisen Aug 04 '16 at 09:46
  • I was wondering what if I would like to asynchronously return something from the `map` inside `method`. I mean `return String.valueOf(value);` is an asynchronous call. – Zizheng Wu Jan 16 '17 at 21:05
  • I think the requirement to track 'variable' changes has been interpreted too literally here... the result does not read easily at all. if we allow ourselves the use of regular Java accessors to a value, things become neater. See my answer. – Chris Hatton Apr 28 '17 at 12:28
45

If interested here a Kotlin version of Variable class, which lets subscribers to be updated after every variable change.

class Variable<T>(private val defaultValue: T) {
var value: T = defaultValue
    set(value) {
        field = value
        observable.onNext(value)
    }
val observable = BehaviorSubject.createDefault(value)
}

Usage:

val greeting = Variable("Hello!")
greeting.observable.subscribe { Log.i("RxKotlin", it) }
greeting.value = "Ciao!"
greeting.value = "Hola!"

This will print:

"Hello!"
"Ciao!"
"Hola!"
Dario Pellegrini
  • 1,652
  • 14
  • 15
  • 1
    How to unsubscribe from `greeting.observable`? – Bogdan Stolyarov Nov 09 '18 at 11:12
  • 4
    @BogdanStolyarov I usually dispose them. private val compositeDisposable = CompositeDisposable() ... greeting.subscribe {}.addTo(compositeDisposable) ... compositeDisposable.dispose() Hope this helps. – Dario Pellegrini Nov 12 '18 at 09:59
  • @Bogdan Stolyarov you can use library Auto Dispose from uber. – Jetwiz Feb 07 '20 at 07:28
  • It is really helpful. Thank u – J.Dragon Oct 01 '20 at 15:14
  • For `observable.onNext(value)` I'm getting the following warning: `Type mismatch: type parameter with nullable bounds is used T is used where T was expected. This warning will become an error soon` - do you have any idea how to fix this? – Micer Feb 17 '21 at 11:27
  • Ok simply checking null by `value?.let {` seems to be enough. – Micer Feb 17 '21 at 13:25
13

@dwursteisen Nothing is magic, no, but I think we can get it a little more magic than that...

How about using an Rx BehaviourSubject in this way:

import rx.functions.Action1;
import rx.subjects.BehaviorSubject;    

public class BehaviourSubjectExample {

    public BehaviourSubjectExample() {
        subject.skip(1).subscribe(new Action1<Integer>() {
            @Override
            public void call(Integer integer) {
                System.out.println("The value changed to " + integer );
            }
        });
    }

    public final BehaviorSubject<Integer> subject = BehaviorSubject.create(0);

    public int  getValue()          { return subject.getValue(); }
    public void setValue(int value) { subject.onNext(value);     }
}

Remove the .skip(1) if you want the observing code to see the initial value.

The variable backing remains with the BehaviourSubject and can be accessed through conventional Java Getter/Setter. This is a toy example of course: If your use case were really this simple there'd be no excuse for not just writing:

private int value = 0;

public int  getValue() { return value; }
public void setValue(int value) {
    this.value = value;
    System.out.println("The value changed to " + value );
}

...but the use of BehaviourSubject lets you bridge changes to other Rx data-streams inside your class for composing more advanced behaviours.

Chris Hatton
  • 787
  • 9
  • 17