71 votes

Partage de données entre fragments à l'aide du nouveau composant d'architecture ViewModel

Sur la Dernière Google IO, Google a publié un aperçu de quelques nouvelles de l'arche de composants, dont l'un ViewModel.

Dans les docs google illustre l'une des utilisations possibles pour ce composant:

Il est très fréquent que deux ou plusieurs fragments dans une activité nécessaire à communiquer les uns avec les autres. Ce n'est jamais anodin que les deux fragments nécessité de définir une description de l'interface, et le propriétaire de l'activité doit lier les deux ensemble. En outre, les deux fragments doivent gérer le cas où l'autre fragment n'est pas encore créé ou pas visible.

Cette douleur commune point peut être abordé en utilisant ViewModel objets. Imaginez un cas commun de maître-détail des fragments, où nous avons une 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 ViewModel à l'aide de leur champ d'intervention à la poignée de cette 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é à propos de la possibilité de ne pas avoir besoin des interfaces utilisées pour des fragments de communiquer par le biais de l'activité.

Mais Google l'exemple de ne pas montrer exactement comment pourrais-je appeler le détail fragment de maître.

J'avais toujours utiliser une interface qui sera mis en œuvre par l'activité, qui va appeler fragmentManager.remplacer(...), ou il y a un autre moyen de le faire à l'aide de la nouvelle architecture?

64voto

Long Ranger Points 1817

Mise à jour sur 6/12/2017,

Android Officielle de fournir un moyen simple, précise un exemple à la façon dont le ViewModel fonctionne sur le Maître-modèle de Détails, vous devriez jeter un oeil sur elle d'abord.Partager des données entre des fragments

Comme @CommonWare, @Quang Nguyen methioned, il n'est pas le but de Yigit pour faire un appel à partir du maître de détail mais de mieux utiliser l'homme du Milieu modèle. Mais si vous voulez faire quelque fragment de la transaction, il doit être fait dans l'activité. À ce moment, le ViewModel de la classe doit être aussi statique de la classe de l'Activité et peut contenir une certaine Laid de Rappel de rappel à l'activité de faire le fragment de la transaction.

J'ai essayé de mettre en œuvre et faire un simple projet à ce sujet. Vous pouvez prendre un coup d'oeil il. La plupart du code est référencé à partir de Google IO 2017, aussi la structure. https://github.com/charlesng/SampleAppArch

Je n'utilise pas Maître de Détail Fragment de mettre en œuvre la composante, mais l'ancien ( de la communication entre le fragment dans ViewPager.) La logique devrait être la même.

Mais j'ai trouvé quelque chose est important d'utiliser ces composants

  1. Ce que vous voulez envoyer et de recevoir de l'homme du Milieu, ils devraient être envoyés et reçus dans le Modèle de Vue seulement
  2. La modification ne semble pas trop dans le fragment de la classe. Depuis il de modifier la mise en œuvre de "l'Interface de rappel" à "l'Écoute et de répondre ViewModel"
  3. Modèle de vue initialiser semble important et susceptible d'être appelé à l'activité.
  4. À l'aide de la MutableLiveData de faire la source synchronisées dans l'activité.

1.Pager Activité

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 cela)

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

}

20voto

Amir jodat Points 196

J'ai trouvé une solution similaire, comme les autres, selon google codelabs exemple. J'ai deux fragments où l'un d'eux d'attendre une modification de l'objet dans l'autre et poursuit son processus avec la mise à jour de l'objet.

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 programme de mise à jour fragment 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 de mentionner que le programme de mise à jour fragment peut être toute forme de fragments(pas DialogFragment seulement) et pour l'utilisation de ces composants de l'architecture, vous devriez avoir ces lignes de code dans votre application construire.gradle fichier. source

dependencies {
  def lifecycle_version = "1.1.1"
  implementation "android.arch.lifecycle:extensions:$lifecycle_version"
}

6voto

Quang Nguyen Points 1807

Avant de vous êtes en utilisant une fonction de callback qui s'attache à l'Activité qui est considérée comme un conteneur.
Cette fonction de rappel est un homme du milieu entre les deux Fragments. Les mauvaises choses à propos de cette solution sont:

  • L'activité a faire le rappel, cela signifie beaucoup de travail pour De l'activité.
  • Deux Fragments sont couplés hermétiquement, il est difficile de mettre à jour ou de modifier la logique plus tard.

Avec la nouvelle ViewModel (avec le soutien de LiveData), vous avez une solution élégante. Elle joue aujourd'hui un rôle d'homme au milieu de laquelle vous pouvez attacher son cycle de vie de l'Activité.

  • La logique et les données entre les deux Fragments se trouvaient à présent dans le ViewModel.
  • Deux Fragment obtient des données/de l'état de ViewModel, de sorte qu'ils n'ont pas besoin de connaître les uns les autres.
  • En outre, avec la puissance de LiveData, vous pouvez modifier les détails Fragment basés sur la variation de maître Fragment dans l'approche réactive à la place de la précédente fonction de rappel manière.

Maintenant vous débarrasser complètement de rappel qui hermétiquement les couples à la fois de l'Activité et liées à des Fragments.
Je recommande fortement de vous par le biais de Google code lab. Dans l'étape 5, vous pouvez trouver un bon exemple à ce sujet.

5voto

Alex Points 2578

J'ai mis en place quelque chose de similaire à ce que vous voulez, mon viewmodel contient LiveData objet qui contient des Énumérations, et lorsque vous souhaitez modifier le fragment de maître de détails (ou en arrière) vous appelez le ViewModel fonctions que la modification de la livedata valeur, et de l'activité à savoir modifier le fragment, parce que c'est l'observation des 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();
    }
}

2voto

alexpfx Points 1528

J'arrive à la fin à l'aide de la propre ViewModel pour tenir jusqu'à l'auditeur qui va déclencher l'Activité de la méthode. Similaire à l' ancienne manière , mais comme je l'ai dit, en passant de l'auditeur ViewModel à la place du fragment. Donc, mon ViewModel 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 auditeur:

StepMasterActivity.class:

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 je viens de récupérer le ViewModel

stepViewModel = ViewModelProviders.of(getActivity()).get("step", SharedViewModel.class);

et composez le:

stepViewModel.select(step);

Je l'ai testé superficiellement et cela a fonctionné. Que je vais sur la mise en œuvre de la d'autres fonctions liées à cela, je vais être conscient de tous les problèmes qui peuvent survenir.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X