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