Использование 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, используя их область действия для обработки этого взаимодействия.

Чтобы использовать модель представления для обмена данными между фрагментами, вам необходимо:

  1. создать модель представления, которая наследуется от ViewModel() и содержит поля MutableLiveData
  2. получить доступ к модели представления из фрагментов, вызвав ViewModelProviders.of (активность).get(YourViewModel :: class.java)
  3. изменить значения модели представления, вызвав setValue для полей MutableLiveData
  4. зарегистрировать изменения полей 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