36

I want to implement a Navigation View with many fragments that depend totally on a value defined in the MainActivity. I know that variables in MainActivity can be accessed using method defined in MainActivity from other Fragments to get the value, but the catch here is that the value of the variable in MainActivity may change (which runs on an AsyncThread). Now, I either change the code such that my Fragments update their value based on some event in the fragment itself or use SharedPreference. But I don't want to use SharedPreferences, neither have to check for change in the value unnecessarily many times.

I know in RxJS, we use Observable that runs Asynchronously and works in a fashion similar to a conveyor belt. A bit of googling through the official docs : Observable confirmed my suspicion of something similar being available in Android, but couldn't find any proper Tutorial or explanation on how to implement it. So, am looking for a simple code snippet that might give clarity to how Observable works in Android and if it is Async and if its based similar to RxJS implementation of it. (No, I don't want RxJS implementation)

Test Case:

MainActivity : int a, b (need observable for both variables)
Frag1 : int a1 , b1, a1changed(),b1changed()
Frag2 : int a2 , b2, a2Changed(), b2changed()

MainActivity contains integers whose value when changed should reflect in corresponding integers across the Fragments and calls separate function for each Fragment on the change being noticed.

Kaushik NP
  • 6,733
  • 9
  • 31
  • 60
  • 1
    You can use either Java's Observable as per the docs you've linked to or implement your own. Personally I recommend to use your own implementation of the pattern in order to learn it well. There are tons of tutorials on the net about using it. If it helps, you might look [this answer](http://stackoverflow.com/questions/39948709/android-custom-listener-for-an-event/39948968#39948968) of mine where I tried to explain it as simple as I could :) – Onik Mar 23 '17 at 20:06
  • 1
    Of course there are tutorials online, but the problem with them is that they usually refer to certain cases that are not required. Am sure if I sit for half a day tracing it out, I would get the Answer. But since I cannot right now, I asked for a simpler example on its implementation. It'll certainly help others who too like me don't want to go through the whole thing just yet and are looking for a simpler explanation for Android behavior with Observables. – Kaushik NP Mar 24 '17 at 02:16

4 Answers4

40

Here is a simple example with an Activity and a single Fragment but it will be the same with other fragments.

First you need to create a class standing for the value you want to observe, in your case it's a simple int so create a class containing this int and that extends Observable (it implements Serializable to simplify exchange between activity and fragment):

...
import java.util.Observable;

public class ObservableInteger extends Observable implements Serializable {

    private int value;

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
        this.setChanged();
        this.notifyObservers(value);
    }
}

Then use this observable int in an activity (activity layout contains a Button and a FrameLayout used to display a Fragment):

public class MainActivity extends Activity {

    private ObservableInteger a;
    private Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Create observable int
        a = new ObservableInteger();
        // Set a default value to it
        a.setValue(0);

        // Create frag1
        Frag1 frag1 = new Frag1();
        Bundle args = new Bundle();
        // Put observable int (That why ObservableInteger implements Serializable)
        args.putSerializable(Frag1.PARAM, a);
        frag1.setArguments(args);

        // Add frag1 on screen
        getFragmentManager().beginTransaction().add(R.id.container, frag1).commit();

        // Add a button to change value of a dynamically
        button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Set a new value in a
                a.setValue(a.getValue() + 1);
            }
        });
    }
}

Finally, create a Fragment that listen a value change:

...
import java.util.Observer;

public class Frag1 extends Fragment {
    public static final String PARAM = "param";

    private ObservableInteger a1;
    private Observer a1Changed = new Observer() {
        @Override
        public void update(Observable o, Object newValue) {
            // a1 changed! (aka a changed)
            // newValue is the observable int value (it's the same as a1.getValue())
            Log.d(Frag1.class.getSimpleName(), "a1 has changed, new value:"+ (int) newValue);
        }
    };

    public Frag1() {
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            // Get ObservableInteger created in activity
            a1 = (ObservableInteger) getArguments().getSerializable(PARAM);
            // Add listener for value change
            a1.addObserver(a1Changed);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_blank, container, false);
    }
}

I try to name my variables the same as yours, I hope it will help you.

Gaëtan Maisse
  • 12,208
  • 9
  • 44
  • 47
  • Nice example. Few doubts there. What if the observable had another int into called 'b' and you needed that observed along with 'a'. And what is the use of serializable? – Kaushik NP Mar 27 '17 at 01:50
  • If your `Observable` object has multiple properties you can create getter/setter for each of them and add a call to `notifyObservers()` in setters, it will tell to every Observer that object has changed (but not which property). `Serializable` is only here to pass data from Activity to Fragment using a Bundle with `putSerializable` method. – Gaëtan Maisse Mar 27 '17 at 07:10
  • @KaushikNP, if my answer fits your needs do not forget to accept it ;) – Gaëtan Maisse Apr 02 '17 at 18:22
  • 1
    Sorry, checked in now. Of course, the bounty goes to you, and the answer was good, but other answers may show up and it is for the larger audience too. – Kaushik NP Apr 03 '17 at 09:14
  • Hello, I was following your code but have to extend this a little bit. What if `ObservableInteger` has got another field which is class let say `ObservableCharacter`. I want to observe this one and notify the main class about changes in `ObservableCharacter`. How can I make the correct notification? – shurrok Apr 13 '18 at 09:17
  • Maybe this is just my pet peeve, but for the love of GOD could people please include import statements in code snippets? Otherwise it is mad frustrating to figure out what classes are actually being used. For example, what "Observable" and "Observer" implementations are being used here? – robross0606 Jun 27 '18 at 14:28
14

There is an good example about using Observable of Android (java.util.Observable) here: https://andhradroid.wordpress.com/2012/04/05/object-observer-pattern-in-android/

And another example about using Observer pattern in Java: http://www.journaldev.com/1739/observer-design-pattern-in-java.

Generally, there are two ways:

  • Use java.util.Observable.
  • Design your own Observable (more flexible, help us understand more deeply).

I like the second way more, for example: (Sorry, I want to make sure it works so I make a complete example)

The Observable:

public interface MyObservable {
    void addObserver(MyObserver myObserver);
    void removeObserver(MyObserver myObserver);
    void notifyObserversAboutA();
    void notifyObserversAboutB();
}

The Observer:

public interface MyObserver {
    void onAChange(int newValue);
    void onBChange(int newValue);
}

The MainActivity:

public class MainActivity extends AppCompatActivity implements MyObservable {

    private List<MyObserver> myObservers;
    private int a, b;
    private EditText etA;
    private EditText etB;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        myObservers = new ArrayList<>();

        ViewPager vpContent = (ViewPager) findViewById(R.id.activity_main_vp_content);
        etA = (EditText) findViewById(R.id.et_a);
        etB = (EditText) findViewById(R.id.et_b);
        Button btnOk = (Button) findViewById(R.id.btn_ok);

        //add fragments to viewpager
        List<Fragment> fragments = new ArrayList<>();
        Fragment1 fragment1 = new Fragment1();
        addObserver(fragment1);
        Fragment2 fragment2 = new Fragment2();
        addObserver(fragment2);

        fragments.add(fragment1);
        fragments.add(fragment2);
        MyFragmentPagerAdapter fragmentPagerAdapter
                = new MyFragmentPagerAdapter(getSupportFragmentManager(), fragments);
        vpContent.setAdapter(fragmentPagerAdapter);

        btnOk.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String a = etA.getText().toString().trim();
                String b = etB.getText().toString().trim();

                if (!a.equals("") && !b.equals("")) {
                    setA(Integer.parseInt(a));
                    setB(Integer.parseInt(b));
                }
            }
        });
    }

    private void setA(int value) {
        a = value;
        notifyObserversAboutA();
    }

    private void setB(int value) {
        b = value;
        notifyObserversAboutB();
    }

    @Override
    public void addObserver(MyObserver myObserver) {
        myObservers.add(myObserver);
    }

    @Override
    public void removeObserver(MyObserver myObserver) {
        myObservers.remove(myObserver);
    }

    @Override
    public void notifyObserversAboutA() {
        for (MyObserver observer : myObservers) {
            observer.onAChange(this.a);
        }
    }

    @Override
    public void notifyObserversAboutB() {
        for (MyObserver observer : myObservers) {
            observer.onBChange(this.b);
        }
    }
}

The Fragment1:

public class Fragment1 extends Fragment implements MyObserver {

    private TextView tvA;
    private TextView tvB;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {

        View contentView = inflater.inflate(R.layout.fragment_basic, container, false);

        tvA = (TextView) contentView.findViewById(R.id.tv_a);
        tvB = (TextView) contentView.findViewById(R.id.tv_b);

        return contentView;
    }

    @Override
    public void onAChange(int newValue) {
        tvA.setText(String.valueOf("New value of a: " + newValue));
    }

    @Override
    public void onBChange(int newValue) {
        tvB.setText(String.valueOf("New value of b: " + newValue));
    }
}

The Fragment2:

public class Fragment2 extends Fragment implements MyObserver {

    private TextView tvA;
    private TextView tvB;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {

        View contentView = inflater.inflate(R.layout.fragment_basic, container, false);

        tvA = (TextView) contentView.findViewById(R.id.tv_a);
        tvB = (TextView) contentView.findViewById(R.id.tv_b);

        return contentView;
    }

    @Override
    public void onAChange(int newValue) {
        tvA.setText(String.valueOf("New value of a: " + newValue));
    }

    @Override
    public void onBChange(int newValue) {
        tvB.setText(String.valueOf("New value of b: " + newValue));
    }
}

The Adapter:

public class MyFragmentPagerAdapter extends FragmentPagerAdapter {

    private List<Fragment> fragments;

    public MyFragmentPagerAdapter(FragmentManager fm, List<Fragment> fragments) {
        super(fm);
        this.fragments = fragments;
    }

    @Override
    public Fragment getItem(int position) {
        return fragments.get(position);
    }

    @Override
    public int getCount() {
        return fragments.size();
    }
}

The main layout activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="codeonce.thinktwice.testobserverpattern.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:id="@+id/linearLayout">


        <EditText
            android:id="@+id/et_a"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="20sp"
            android:inputType="number"
            android:hint="Type value for a"/>

        <EditText
            android:id="@+id/et_b"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="20sp"
            android:inputType="number"
            android:hint="Type value for b"/>

        <Button
            android:id="@+id/btn_ok"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20sp"
            android:layout_gravity="center_horizontal"
            android:text="OK"/>

    </LinearLayout>

    <android.support.v4.view.ViewPager
        android:id="@+id/activity_main_vp_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/linearLayout"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true">

    </android.support.v4.view.ViewPager>

</RelativeLayout>

The fragment layout fragment_basic.xml :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center_horizontal">

    <TextView
        android:layout_marginTop="20dp"
        android:id="@+id/tv_a"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Value of a will appear here"
        android:textSize="20sp"/>

    <TextView
        android:id="@+id/tv_b"
        android:layout_marginTop="20dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Value of b will appear here"
        android:textSize="20sp"/>

</LinearLayout>
Phong Nguyen
  • 6,897
  • 2
  • 26
  • 36
  • 1
    Is observer pattern may help in below situation (explained in question)? https://stackoverflow.com/questions/47448453/android-notified-when-all-async-calls-are-completed – Khushbu Shah Nov 23 '17 at 06:45
5

Reactive is not part of Android but you are probably looking for this library: https://github.com/JakeWharton/RxBinding

The landing page is missing an introductory example, so you have to look at the javadoc. This post should give you a good start: How to create an Observable from OnClick Event Android? Here is the code sample from Matt to get you started

RxView.clicks(myButton)
    .subscribe(new Action1<Void>() {
        @Override
        public void call(Void aVoid) {
            /* do something */
        }
    });
Community
  • 1
  • 1
Benjamin Mesing
  • 4,075
  • 1
  • 18
  • 22
  • Um, sorry if the question was misleading, but I want a totally local Observable implementation, not having to use RxJS or any other integration with it. Observables are provided in Android too, as seen in the link in the question above. – Kaushik NP Mar 23 '17 at 17:56
  • Well, my thinking was biased, I was doing to much Reactive lately ;-) Java Observables are a simple implementation of the GoF-Observer pattern, while RxJS-Observables are an implementation of the Reactive paradigm. There is a Java equivalent to RxJS which is RxJava. – Benjamin Mesing Mar 23 '17 at 21:43
2

Android now offers ViewModels with LiveData for your purpose. A view model is bound to an object with a lifecycle (Fragment, Activity) and lives as long as this object. In your case you would create a view model bound to the actvity which is accessible by all fragments.

From the Android documentation:

It's very common that two or more fragments in an activity need to communicate with each other. Imagine a common case of master-detail fragments, where you have a fragment in which the user selects an item from a list and another fragment that displays the contents of the selected item. This case is never trivial as both fragments need to define some interface description, and the owner activity must bind the two together. In addition, both fragments must handle the scenario where the other fragment is not yet created or visible.

This common pain point can be addressed by using ViewModel objects. These fragments can share a ViewModel using their activity scope to handle this communication

To use a view model for sharing data between fragments you need to:

  1. create a view model which inherits from ViewModel() and contains MutableLiveData-fields
  2. access the view model from the fragments by calling ViewModelProviders.of(activity).get(YourViewModel::class.java)
  3. change values of the view model by calling setValue for the MutableLiveData-fields
  4. register on changes of the MutableLiveData-fields by calling .observe()

Here is the central code snippet from the Android documentation on how to use ViewModels for a master-detail-view:

class SharedViewModel : ViewModel() {
    val selected = MutableLiveData<Item>()

    fun select(item: Item) {
        selected.value = item
    }
}

class MasterFragment : Fragment() {
    private lateinit var itemSelector: Selector
    private lateinit var model: SharedViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        model = activity?.run {
            ViewModelProviders.of(this).get(SharedViewModel::class.java)
        } ?: throw Exception("Invalid Activity")
        itemSelector.setOnClickListener { item ->
        // Update the UI
        }
    }
}

class DetailFragment : Fragment() {
    private lateinit var model: SharedViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        model = activity?.run {
            ViewModelProviders.of(this).get(SharedViewModel::class.java)
        } ?: throw Exception("Invalid Activity")
        model.selected.observe(this, Observer<Item> { item ->
            // Update the UI
        })
    }
}
Community
  • 1
  • 1
Benjamin Mesing
  • 4,075
  • 1
  • 18
  • 22