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.
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.
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>
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 */
}
});