web-dev-qa-db-ja.com

新しいアーキテクチャコンポーネントViewModelを使用してフラグメント間でデータを共有する

GoogleはLast Google IOで、いくつかの新しいArchコンポーネントのプレビューをリリースしました。そのうちの1つはViewModelです。

docs googleには、このコンポーネントの可能な用途の1つが示されています。

アクティビティ内の2つ以上のフラグメントが互いに通信する必要があることは非常に一般的です。両方のフラグメントが何らかのインターフェース記述を定義する必要があるため、これは決して些細なことではありません。所有者アクティビティは2つを結合する必要があります。さらに、両方のフラグメントは、他のフラグメントがまだ作成されていないか、表示されていない場合を処理する必要があります。

この一般的な問題点は、ViewModelオブジェクトを使用して対処できます。ユーザーがリストからアイテムを選択するフラグメントと、選択したアイテムのコンテンツを表示する別のフラグメントがある、マスター/ディテールフラグメントの一般的なケースを想像してください。

これらのフラグメントは、アクティビティスコープを使用してこの通信を処理するViewModelを共有できます。

実装例を示します。

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

フラグメントを介してアクティビティを介して通信するためにこれらのインターフェイスを使用する必要がない可能性に非常に興奮していました。

しかし、Googleの例は、マスターから詳細フラグメントをどのように呼び出すかを正確に示していません。

Fragment-manager.replace(...)を呼び出すアクティビティによって実装される インターフェイス を使用する必要がありますか、または新しいアーキテクチャを使用して別の方法でそれを行うことができますか?

42
alexpfx

2017年6月12日に更新、

Android Officialは、ViewModelがMaster-Detailテンプレートでどのように機能するかを示す簡単で正確な例を提供します。まず、それを確認する必要があります。 フラグメント間でデータを共有する

@ CommonWare、@ Quang Nguyenが考案したように、Yigitがマスターからディテールへの呼び出しを行うことは目的ではありませんが、Middle manパターンを使用する方が良いでしょう。ただし、フラグメントトランザクションを作成する場合は、アクティビティで行う必要があります。その時点で、ViewModelクラスはActivityの静的クラスである必要があり、フラグメントトランザクションを作成するためにアクティビティをコールバックするUいコールバックを含めることができます。

私はこれを実装し、これについて簡単なプロジェクトを作成しようとしました。あなたはそれを見ることができます。ほとんどのコードは、Google IO 2017、また構造から参照されます。 https://github.com/charlesng/SampleAppArch

マスターディテールフラグメントを使用してコンポーネントを実装するのではなく、古いコンポーネント(ViewPagerのフラグメント間の通信)を使用します。ロジックは同じである必要があります。

しかし、これらのコンポーネントを使用して何かが重要であることがわかりました

  1. あなたがミドルマンで送受信したいものは、ビューモデルでのみ送受信する必要があります
  2. フラグメントクラスでは、変更はそれほど多くありません。実装を「インターフェースコールバック」から「Listing and responding ViewModel」のみに変更するため
  3. ビューモデルの初期化は重要と思われ、アクティビティで呼び出される可能性があります。
  4. MutableLiveDataを使用して、アクティビティのみでソースを同期させます。

1。ページャーアクティビティ

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(これよりも良い名前に値する)

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

}
39
Long Ranger

Google Codelabs example によると、他のソリューションと同様のソリューションが見つかりました。 2つのフラグメントがあり、片方がもう一方のオブジェクトの変更を待機し、更新されたオブジェクトを使用してプロセスを続行します。

このアプローチでは、次のようなViewModelクラスが必要です。

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

}

リスナーフラグメントは次のようになります。

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

最後に、アップデータフラグメントは次のようになります。

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

アップデータフラグメントは任意の形式のフラグメント(DialogFragmentのみではない)であり、これらのアーキテクチャコンポーネントを使用するには、アプリのbuild.gradleファイルに次のコード行を含める必要があります。 ソース

dependencies {
  def lifecycle_version = "1.1.1"
  implementation "Android.Arch.lifecycle:extensions:$lifecycle_version"
}
9
Amir jodat

私はあなたが望むものに似たものを実装しました、私のビューモデルにはEnum状態を含むLiveDataオブジェクトが含まれています、そしてフラグメントをマスターから詳細に(または逆に)変更したい場合、livedata値を変更するViewModel関数を呼び出し、アクティビティはlivedataオブジェクトを監視しているため、フラグメントを変更します。

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

列挙型:

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

コンテナと見なされるアクティビティにアタッチするコールバックを使用する前。
そのコールバックは、2つのフラグメントの中間です。この以前のソリューションの悪い点は次のとおりです。

  • アクティビティはコールバックを実行する必要があります。これは、アクティビティのための多くの作業を意味します。
  • 2つのフラグメントは密接に結合されているため、後でロジックを更新または変更することは困難です。

新しいViewModel(LiveDataのサポート)を使用すると、エレガントなソリューションが得られます。これは、ライフサイクルをアクティビティにアタッチできる中間者の役割を果たします。

  • 2つのフラグメント間のロジックとデータがViewModelにレイアウトされるようになりました。
  • 2つのフラグメントはViewModelからデータ/状態を取得するため、お互いを知る必要はありません。
  • さらに、LiveDataのパワーにより、以前のコールバック方法ではなく、リアクティブアプローチでマスターフラグメントの変更に基づいて詳細フラグメントを変更できます。

これで、アクティビティと関連するフラグメントの両方に密接に結合するコールバックを完全に取り除くことができます。
Googleのコードラボ をお勧めします。ステップ5で、これに関する素晴らしい例を見つけることができます。

5
Quang Nguyen

最終的に、独自のViewModelを使用して、Activityメソッドをトリガーするリスナーを保持します。 古い方法 に似ていますが、私が言ったように、フラグメントではなくViewModelにリスナーを渡します。したがって、私のViewModelは次のようになりました。

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

}

stepMasterActivityで、ViewModelを取得し、リスナーとして設定します。

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

...

フラグメントでは、ViewModelを取得するだけです

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

そして電話:

stepViewModel.select(step);

表面的にテストし、機能しました。これに関連する他の機能を実装しようとすると、発生する可能性のある問題を認識します。

2
alexpfx