web-dev-qa-db-fra.com

Utilisation de Observable dans Android

Je veux implémenter un Navigation View avec de nombreux fragments qui dépendent totalement d’une valeur définie dans le MainActivity. Je sais que les variables de MainActivity peuvent être consultées à l'aide de la méthode définie dans MainActivity à partir d'autres fragments pour obtenir la valeur, mais le problème est que la valeur de la variable dans MainActivity peut change (qui tourne sur un AsyncThread). Maintenant, soit je change le code de telle sorte que mes Fragments mettent à jour leur valeur sur la base de un événement du fragment lui-même, ou bien j'utilise SharedPreference. Mais je ne veux pas utiliser SharedPreferences, ni avoir à vérifier plusieurs fois inutilement la valeur.

Je sais que dans RxJS, nous utilisons Observable qui fonctionne de manière asynchrone et fonctionne de manière similaire à un tapis roulant. Un peu de googler à travers le documentation officielle: Observable a confirmé mes soupçons quant à l'existence d'un produit similaire sur Android, mais n'a pas pu trouver de didacticiel approprié ni d'explication sur la façon de le mettre en œuvre. Je cherche donc un extrait de code simple qui pourrait clarifier le fonctionnement d’Observable dans Android et s’il est asynchrone et s’il est basé sur une implémentation similaire à RxJS. (Non, je ne le fais pas.) t veulent l'implémentation de RxJS)

Cas de test:

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

MainActivity contient des entiers dont la valeur, une fois modifiée, doit refléter les entiers correspondants dans les fragments et appelle une fonction distincte pour chaque fragment de la modification notée.

27
Kaushik NP

Voici un exemple simple avec un Activity et un seul Fragment, mais ce sera la même chose avec d’autres fragments.

Tout d’abord, vous devez créer une classe représentant la valeur que vous voulez observer. Dans votre cas, c’est un simple int, créez donc une classe contenant ce int et qui étend Observable ( il implémente Serializable pour simplifier l'échange entre activité et 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);
    }
}

Utilisez ensuite cet int observable dans une activité (la structure de l'activité contient un Button et un FrameLayout utilisé pour afficher un 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);
            }
        });
    }
}

Enfin, créez un Fragment qui écoute un changement de valeur:

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

J'essaie de nommer mes variables de la même façon que les vôtres, j'espère que cela vous aidera.

28
Gaëtan M.

Il existe un bon exemple d'utilisation de Observable de Android (Java.util.Observable) ici :https://andhradroid.wordpress.com/2012/04/05/object-observer-pattern-in-Android/

Et un autre exemple d'utilisation du modèle Observer en Java: http://www.journaldev.com/1739/observer-design-pattern- in-Java .

Généralement, il y a deux manières:

  • Utilisez Java.util.Observable .
  • Concevez votre propre observable (plus flexible, aidez-nous à comprendre plus profondément).

J'aime la deuxième manière plus, par exemple: ( Désolé, je veux m'assurer que cela fonctionne pour que je fasse un exemple complet )

L'observable :

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

L'observateur:

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

Le 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);
        }
    }
}

Le 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));
    }
}

Le 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));
    }
}

L'adaptateur:

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();
    }
}

La mise en page principale 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>

La mise en page fragment 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>
11

Reactive ne fait pas partie de Android mais vous êtes probablement à la recherche de cette bibliothèque: https://github.com/JakeWharton/RxBinding

Il manque un exemple d'introduction à la page de destination, vous devez donc consulter le javadoc. Cet article devrait vous donner un bon départ: Comment créer un observable à partir d'OnClick Event Android? Voici l'exemple de code de Matt pour vous aider à démarrer

RxView.clicks(myButton)
    .subscribe(new Action1<Void>() {
        @Override
        public void call(Void aVoid) {
            /* do something */
        }
    });
5
Benjamin Mesing

Android propose désormais ViewModels avec LiveData pour vous. Un modèle de vue est lié à un objet avec un cycle de vie (fragment, activité) et vit aussi longtemps que cet objet. Dans votre cas, vous créez un modèle de vue lié à l’activité qui est accessible par tous les fragments.

De la Android documentation :

Il est très courant que deux ou plusieurs fragments d'une activité doivent communiquer l'un avec l'autre. Imaginons un cas courant de fragments maître-détail, dans lequel vous avez un fragment dans lequel l'utilisateur sélectionne un élément dans une liste et un autre fragment qui affiche le contenu de l'élément sélectionné. Ce cas n'est jamais anodin, car les deux fragments doivent définir une description de l'interface et l'activité du propriétaire doit lier les deux. En outre, les deux fragments doivent gérer le scénario dans lequel l'autre fragment n'est pas encore créé ou visible.

Ce problème récurrent peut être résolu à l'aide d'objets ViewModel. Ces fragments peuvent partager un ViewModel en utilisant leur portée d'activité pour gérer cette communication.

Pour utiliser un modèle de vue pour partager des données entre fragments, vous devez:

  1. créer un modèle de vue qui hérite de ViewModel () et contient des champs MutableLiveData
  2. accédez au modèle de vue à partir des fragments en appelant ViewModelProviders.of (activity) .get (YourViewModel :: class.Java)
  3. changer les valeurs du modèle de vue en appelant setValue pour les champs MutableLiveData
  4. inscrivez-vous sur les modifications des champs MutableLiveData en appelant .observe ()

Voici l'extrait de code central de la Android sur l'utilisation de ViewModels pour une vue maître-détail-vue:

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
        })
    }
}
1
Benjamin Mesing