Partage de données entre fragments à l'aide d'un nouveau modèle D'affichage de composants d'architecture
Sur Le Dernier Google IO, Google a publié un aperçu de quelques nouveaux composants arch, dont L'un, ViewModel.
Dans le docs google affiche une des utilisations possibles de ce composant:
il est très courant que deux ou plusieurs fragments dans une activité doivent communiquer les uns avec les autres. Ce n'est jamais insignifiant comme les deux fragments besoin de définir une certaine description de l'interface, et l'activité du propriétaire doit lier les deux ensemble. En outre, les deux les fragments doivent gérer le cas où l'autre fragment n'est pas encore créé ou pas visible.
ce point douloureux commun peut être résolu en utilisant des objets ViewModel. Imaginez un cas courant de fragments de détails de maître, où nous avons 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é.
ces fragments peuvent partager un modèle de vue en utilisant leur champ d'activité pour gérer ce communication.
Et montre un exemple de mise en œuvre:
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Item> selected = new MutableLiveData<Item>();
public void select(Item item) {
selected.setValue(item);
}
public LiveData<Item> getSelected() {
return selected;
}
}
public class MasterFragment extends Fragment {
private SharedViewModel model;
public void onActivityCreated() {
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
public class DetailFragment extends LifecycleFragment {
public void onActivityCreated() {
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, { item ->
// update UI
});
}
}
j'étais très excité par la possibilité de ne pas avoir besoin de ces interfaces utilisées pour les fragments pour communiquer à travers l'activité.
mais L'exemple de Google ne montre pas exactement comment j'appellerais le fragment de détail de master.
je devrais quand même utiliser une interface qui sera mis en œuvre par l'activité, qui feront appel fragmentManager.remplacer.(..), ou il y a un autre moyen de le faire à l'aide de la nouvelle architecture?
6 réponses
mise à Jour sur 6/12/2017,
Android Official fournir un exemple simple et précis de la façon dont le ViewModel fonctionne sur le modèle Master-Detail, vous devriez y jeter un oeil en premier. partage de données entre fragments
comme @CommonWare, @Quang Nguyen méthioned, ce n'est pas le but pour Yigit de faire l'appel du maître au détail, mais être mieux d'utiliser le modèle de L'homme moyen. Mais si tu veux faire un fragment transaction, il doit être fait dans l'activité. À ce moment, la classe ViewModel devrait être comme classe statique en activité et pourrait contenir un mauvais rappel pour rappeler l'activité pour effectuer la transaction de fragment.
j'ai essayé de mettre en oeuvre ceci et de faire un projet simple à ce sujet. Vous pouvez prendre un coup d'oeil. La plupart du code est référencé à partir de Google IO 2017, également la structure. https://github.com/charlesng/SampleAppArch
Je n'utilise pas Master Détail Fragment pour implémenter le composant, mais l'ancien (communication entre fragment dans ViewPager. La logique devrait être la même.
Mais j'ai trouvé quelque chose est important d'utiliser ces composants
- ce que vous voulez envoyer et recevoir dans L'homme moyen, ils devraient être envoyés et reçus en vue Modèle seulement
- la modification ne semble pas trop importante dans la classe fragment. Depuis qu'il change seulement l'implémentation de "Interface callback" en "D'écouter et de répondre ViewModel"
- voir modèle initialiser semble important et susceptible d'être appelé dans l'activité.
- en utilisant les données MutableLiveData pour synchroniser la source en activité seulement.
1.Activité Du Pager
public class PagerActivity extends LifecycleActivity {
/**
* The pager widget, which handles animation and allows swiping horizontally to access previous
* and next wizard steps.
*/
private ViewPager mPager;
private PagerAgentViewModel pagerAgentViewModel;
/**
* The pager adapter, which provides the pages to the view pager widget.
*/
private PagerAdapter mPagerAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pager);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
mPager = (ViewPager) findViewById(R.id.pager);
mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
mPager.setAdapter(mPagerAdapter);
pagerAgentViewModel = ViewModelProviders.of(this).get(PagerAgentViewModel.class);
pagerAgentViewModel.init();
}
/**
* A simple pager adapter that represents 5 ScreenSlidePageFragment objects, in
* sequence.
*/
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
...Pager Implementation
}
}
2.PagerAgentViewModel (Il méritait un meilleur nom, plutôt que de ce)
public class PagerAgentViewModel extends ViewModel {
private MutableLiveData<String> messageContainerA;
private MutableLiveData<String> messageContainerB;
public void init()
{
messageContainerA = new MutableLiveData<>();
messageContainerA.setValue("Default Message");
messageContainerB = new MutableLiveData<>();
messageContainerB.setValue("Default Message");
}
public void sendMessageToB(String msg)
{
messageContainerB.setValue(msg);
}
public void sendMessageToA(String msg)
{
messageContainerA.setValue(msg);
}
public LiveData<String> getMessageContainerA() {
return messageContainerA;
}
public LiveData<String> getMessageContainerB() {
return messageContainerB;
}
}
3.BlankFragmentA
public class BlankFragmentA extends LifecycleFragment {
public BlankFragmentA() {
// Required empty public constructor
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//setup the listener for the fragment A
ViewModelProviders.of(getActivity()).get(PagerAgentViewModel.class).getMessageContainerA().observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable String msg) {
textView.setText(msg);
}
});
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_blank_a, container, false);
textView = (TextView) view.findViewById(R.id.fragment_textA);
// set the onclick listener
Button button = (Button) view.findViewById(R.id.btnA);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ViewModelProviders.of(getActivity()).get(PagerAgentViewModel.class).sendMessageToB("Hello B");
}
});
return view;
}
}
4.BlankFragmentB
public class BlankFragmentB extends LifecycleFragment {
public BlankFragmentB() {
// Required empty public constructor
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//setup the listener for the fragment B
ViewModelProviders.of(getActivity()).get(PagerAgentViewModel.class).getMessageContainerB().observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable String msg) {
textView.setText(msg);
}
});
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_blank_b, container, false);
textView = (TextView) view.findViewById(R.id.fragment_textB);
//set the on click listener
Button button = (Button) view.findViewById(R.id.btnB);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ViewModelProviders.of(getActivity()).get(PagerAgentViewModel.class).sendMessageToA("Hello A");
}
});
return view;
}
}
avant d'utiliser un rappel qui se rattache à une activité considérée comme un conteneur.
Ce rappel est un intermédiaire entre deux Fragments.
Les mauvaises choses à propos de cette solution précédente sont les suivantes:
- Activité a faire le rappel, cela signifie beaucoup de travail pour Activité.
- deux Fragments sont couplés étroitement, il est difficile de mettre à jour ou de changer la logique plus tard.
avec le nouveau modèle de vue (avec le soutien de LiveData), vous avez une solution élégante. Il joue maintenant un rôle d'intermédiaire que vous pouvez attacher son cycle de vie à L'activité.
- la logique et les données entre deux Fragments sont maintenant présentées dans ViewModel.
- deux fragments obtiennent des données / états de ViewModel, de sorte qu'ils n'ont pas besoin de se connaître.
- en outre, avec la puissance de LiveData, vous pouvez changer Fragment de détail basé sur des changements de Fragment maître en approche réactive au lieu de rappel précédent façon.
vous vous débarrassez maintenant complètement de callback qui associe étroitement à la fois L'activité et les Fragments connexes.
Je recommande fortement de vous par le biais de Google Code lab. À l'étape 5, vous trouverez un bel exemple à ce sujet.
j'ai implémenté quelque chose de similaire à ce que vous voulez, mon viewmodel contient l'objet LiveData qui contient l'état Enum, et quand vous voulez changer le fragment du maître aux détails (ou à l'inverse) vous appelez les fonctions ViewModel qui changent la valeur livedata, et l'activité savent changer le fragment parce qu'il observe livedata objet.
TestViewModel:
public class TestViewModel extends ViewModel {
private MutableLiveData<Enums.state> mState;
public TestViewModel() {
mState=new MutableLiveData<>();
mState.setValue(Enums.state.Master);
}
public void onDetail() {
mState.setValue(Enums.state.Detail);
}
public void onMaster() {
mState.setValue(Enums.state.Master);
}
public LiveData<Enums.state> getState() {
return mState;
}
}
les Énumérations:
public class Enums {
public enum state {
Master,
Detail
}
}
TestActivity:
public class TestActivity extends LifecycleActivity {
private ActivityTestBinding mBinding;
private TestViewModel mViewModel;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding=DataBindingUtil.setContentView(this, R.layout.activity_test);
mViewModel=ViewModelProviders.of(this).get(TestViewModel.class);
mViewModel.getState().observe(this, new Observer<Enums.state>() {
@Override
public void onChanged(@Nullable Enums.state state) {
switch(state) {
case Master:
setMasterFragment();
break;
case Detail:
setDetailFragment();
break;
}
}
});
}
private void setMasterFragment() {
MasterFragment masterFragment=MasterFragment.newInstance();
getSupportFragmentManager().beginTransaction().replace(R.id.frame_layout, masterFragment,"MasterTag").commit();
}
private void setDetailFragment() {
DetailFragment detailFragment=DetailFragment.newInstance();
getSupportFragmentManager().beginTransaction().replace(R.id.frame_layout, detailFragment,"DetailTag").commit();
}
@Override
public void onBackPressed() {
switch(mViewModel.getState().getValue()) {
case Master:
super.onBackPressed();
break;
case Detail:
mViewModel.onMaster();
break;
}
}
}
MasterFragment:
public class MasterFragment extends Fragment {
private FragmentMasterBinding mBinding;
public static MasterFragment newInstance() {
MasterFragment fragment=new MasterFragment();
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mBinding=DataBindingUtil.inflate(inflater,R.layout.fragment_master, container, false);
mBinding.btnDetail.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final TestViewModel viewModel=ViewModelProviders.of(getActivity()).get(TestViewModel.class);
viewModel.onDetail();
}
});
return mBinding.getRoot();
}
}
DetailFragment:
public class DetailFragment extends Fragment {
private FragmentDetailBinding mBinding;
public static DetailFragment newInstance() {
DetailFragment fragment=new DetailFragment();
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mBinding=DataBindingUtil.inflate(inflater,R.layout.fragment_detail, container, false);
mBinding.btnMaster.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final TestViewModel viewModel=ViewModelProviders.of(getActivity()).get(TestViewModel.class);
viewModel.onMaster();
}
});
return mBinding.getRoot();
}
}
je finis par utiliser le propre modèle de vue pour maintenir l'écouteur qui déclenchera la méthode D'activité. Similaire au ancienne mais comme je l'ai dit, passer L'auditeur à ViewModel au lieu du fragment. Ainsi, mon modèle de vue ressemblait à ceci:
public class SharedViewModel<T> extends ViewModel {
private final MutableLiveData<T> selected = new MutableLiveData<>();
private OnSelectListener<T> listener = item -> {};
public interface OnSelectListener <T> {
void selected (T item);
}
public void setListener(OnSelectListener<T> listener) {
this.listener = listener;
}
public void select(T item) {
selected.setValue(item);
listener.selected(item);
}
public LiveData<T> getSelected() {
return selected;
}
}
dans StepMasterActivity-je obtenir le ViewModel et de le définir comme un port d'écoute:
StepMasterActivity.classe:
SharedViewModel stepViewModel = ViewModelProviders.of(this).get("step", SharedViewModel.class);
stepViewModel.setListener(this);
...
@Override
public void selected(Step item) {
Log.d(TAG, "selected: "+item);
}
...
dans le fragment j'ai juste récupérez le ViewModel
stepViewModel = ViewModelProviders.of(getActivity()).get("step", SharedViewModel.class);
et l'appel:
stepViewModel.select(step);
je l'ai testé superficiellement et cela a fonctionné. Au fur et à mesure que je mettrai en œuvre les autres fonctionnalités liées à ce problème, je serai au courant de tout problème qui pourrait se produire.
j'ai trouvé une solution similaire à d'autres selon Google codelabs exemple. J'ai deux fragments où l'un d'eux attend un changement d'objet dans l'autre et continue son processus avec un objet mis à jour.
pour cette approche, vous aurez besoin d'un ViewModel classe comme ci-dessous:
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.ViewModel;
import yourPackage.YourObjectModel;
public class SharedViewModel extends ViewModel {
public MutableLiveData<YourObjectModel> item = new MutableLiveData<>();
public YourObjectModel getItem() {
return item.getValue();
}
public void setItem(YourObjectModel item) {
this.item.setValue(item);
}
}
et l'auditeur fragment devrait ressembler à ceci:
public class ListenerFragment extends Fragment{
private SharedViewModel model;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.item.observe(getActivity(), new Observer<YourObjectModel>(){
@Override
public void onChanged(@Nullable YourObjectModel updatedObject) {
Log.i(TAG, "onChanged: recieved freshObject");
if (updatedObject != null) {
// Do what you want with your updated object here.
}
}
});
}
}
enfin, le fragment updater peut être comme ceci:
public class UpdaterFragment extends DialogFragment{
private SharedViewModel model;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
}
// Call this method where it is necessary
private void updateViewModel(YourObjectModel yourItem){
model.setItem(yourItem);
}
}
Il est bon pour mentionner que le fragment updater peut être n'importe quelle forme de fragments(pas DialogFragment seulement) et pour utiliser ces composants d'architecture, vous devriez avoir ces lignes de codes dans votre construction d'application.dossier gradle. source
dependencies {
def lifecycle_version = "1.1.1"
implementation "android.arch.lifecycle:extensions:$lifecycle_version"
}
Vous pouvez définir des valeurs de fragment de détail à Fragment maître comme ceci
model.selected.setValue(item)