Использование Observable в Android
Я хочу реализовать Navigation View
со многими фрагментами, которые полностью зависят от значения, определенного в MainActivity
. Я знаю, что переменные в MainActivity могут быть доступны с помощью метода, определенного в MainActivity, из других фрагментов, чтобы получить значение, но здесь вы поймете, что значение переменной в MainActivity может изменить (которое выполняется на AsyncThread). Теперь я либо изменяю код так, чтобы мои Фрагменты обновляли свое значение на основе какого-либо события в самом фрагменте или использовали SharedPreference
. Но я не хочу использовать SharedPreferences, и не нужно многократно проверять изменение значения без необходимости.
Я знаю, что в RxJS мы используем Observable, который работает асинхронно и работает так же, как лента конвейера. Немного о поиске по официальным документам
Ответы
Ответ 1
Вот простой пример с Activity
и одним Fragment
но это будет то же самое с другими фрагментами.
Сначала вам нужно создать класс, соответствующий значению, которое вы хотите наблюдать, в вашем случае это простой int
поэтому создайте класс, содержащий этот int
и расширяющий Observable
(он реализует Serializable
для упрощения обмена между действием и фрагментом):
...
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);
}
}
Затем используйте это наблюдаемое int в FrameLayout
(макет действия содержит Button
и FrameLayout
используемый для отображения 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);
}
});
}
}
Наконец, создайте Fragment
который прослушивает изменение значения:
...
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 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);
}
}
Я пытаюсь назвать мои переменные так же, как ваши, надеюсь, это поможет вам.
Ответ 2
Вот хороший пример использования Observable для Android (java.util.Observable) здесь: https://andhradroid.wordpress.com/2012/04/05/object-observer-pattern-in-android/
И еще один пример использования шаблона Observer в Java: http://www.journaldev.com/1739/observer-design-pattern-in-java.
Как правило, есть два способа:
- Используйте java.util.Observable.
- Создайте свой собственный Observable (более гибкий, помогите нам глубже понять).
Мне больше нравится второй способ, например: (Извините, я хочу убедиться, что он работает, поэтому приведу полный пример)
Наблюдаемое:
public interface MyObservable {
void addObserver(MyObserver myObserver);
void removeObserver(MyObserver myObserver);
void notifyObserversAboutA();
void notifyObserversAboutB();
}
Наблюдатель:
public interface MyObserver {
void onAChange(int newValue);
void onBChange(int newValue);
}
Основная деятельность:
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);
}
}
}
Фрагмент1:
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));
}
}
Фрагмент2:
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));
}
}
Адаптер:
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();
}
}
Основной макет 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>
Компоновка фрагмента frag_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>
Ответ 3
Reactive не является частью Android, но вы, вероятно, ищете эту библиотеку:
https://github.com/JakeWharton/RxBinding
На целевой странице отсутствует вводный пример, поэтому вам нужно посмотреть на javadoc. Этот пост должен дать вам хорошее начало: Как создать Observable из OnClick Event Android? Вот пример кода от Matt, чтобы вы начали.
RxView.clicks(myButton)
.subscribe(new Action1<Void>() {
@Override
public void call(Void aVoid) {
/* do something */
}
});
Ответ 4
Android теперь предлагает ViewModels с LiveData для ваших целей. Модель представления привязана к объекту с жизненным циклом (Fragment, Activity) и живет столько же времени, сколько этот объект. В вашем случае вы бы создали модель представления, привязанную к действию, которая доступна для всех фрагментов.
Из документации Android:
Очень часто два или более фрагмента в упражнении должны общаться друг с другом. Представьте себе общий случай фрагментов основной детали, когда у вас есть фрагмент, в котором пользователь выбирает элемент из списка, а другой фрагмент, который отображает содержимое выбранного элемента. Этот случай никогда не бывает тривиальным, поскольку оба фрагмента должны определять описание интерфейса, а действие владельца должно связывать их вместе. Кроме того, оба фрагмента должны обрабатывать сценарий, в котором другой фрагмент еще не создан или не виден.
Эта общая болевая точка может быть решена с помощью объектов ViewModel. Эти фрагменты могут совместно использовать ViewModel, используя их область действия для обработки этого взаимодействия.
Чтобы использовать модель представления для обмена данными между фрагментами, вам необходимо:
- создать модель представления, которая наследуется от ViewModel() и содержит поля MutableLiveData
- получить доступ к модели представления из фрагментов, вызвав ViewModelProviders.of (активность).get(YourViewModel :: class.java)
- изменить значения модели представления, вызвав setValue для полей MutableLiveData
- зарегистрировать изменения полей MutableLiveData, вызвав .observe()
Вот центральный фрагмент кода из документации Android о том, как использовать ViewModels для представления 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
})
}
}
Ответ 5
Как заставить observables ждать, пока он не выполнит некоторую задачу пользовательского интерфейса?
Мне нужна помощь по следующему сценарию: допустим, у меня есть 5 событий в наблюдаемых, и в процессе 3-го события мне нужно дать некоторый ввод, используя диалоговое окно, вводить пин-код и отправлять до тех пор, пока мне нужны наблюдаемые, чтобы подождать, прежде чем перейти к 4-му событию.
Просто, как приостановить процесс наблюдения и возобновить.
Как я могу достичь этого. Заранее спасибо.
С уважением, Prathap