web-dev-qa-db-ja.com

ViewModel onchangeがFragmentから戻ったときに複数回呼び出される

私はAndroidアーキテクチャコンポーネントを使用しています。ユーザーがEdittextに「0」と入力し、ボタンをクリックしてFragmentを新しいものに置き換えた場合、および何か他のタイプを入力した場合はToastエラーメッセージを投稿します。問題は、新しいFragment(BlankFragment)から戻ってボタンをもう一度クリックし、もう一度「0」を入力してクリックすると、onchange()が複数回呼び出されるため、フラグメントが複数回作成される

FragmentExample.class:

     @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        manager = getActivity().getSupportFragmentManager();
        viewmModel = ViewModelProviders.of(getActivity(), viewModelFactory)
                .get(VModel.class);

        View v = inflater.inflate(R.layout.fragment_list, container, false);   
        b = (Button) v.findViewById(R.id.b);
        et = (EditText) v.findViewById(R.id.et);

        viewmModel.observeData().observe(getActivity(), new Observer<String>() {
            @Override
            public void onChanged(@Nullable String s) {

                if(s.equals("0")) {
                    BlankFragment fragment = (BlankFragment) manager.findFragmentByTag(DETAIL_FRAG);
                    if (fragment == null) {
                        fragment = BlankFragment.newInstance();
                    }
                    addFragmentToActivity(manager,
                            fragment,
                            R.id.root_activity_detail,
                            DETAIL_FRAG
                    );
                } else {
                    Toast.makeText(getContext(), "Wrong text", Toast.LENGTH_SHORT).show();
                }
            }
        });

        b.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                viewmModel.setData(et.getText().toString());
            }
        });
        return v;
    }
    private void addFragmentToActivity(FragmentManager fragmentManager, BlankFragment fragment, int root_activity_detail, String detailFrag) {
        Android.support.v4.app.FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.replace(root_activity_detail, fragment, detailFrag).addToBackStack(detailFrag);
        transaction.commit();
    }

リポジトリクラス:


    public class Repository {
    MutableLiveData<String> dataLive = new MutableLiveData<>();  

    public Repository() {

    }

    public void setListData(String data) {
       dataLive.setValue(data);
    }

    public MutableLiveData<String> getData() {
        return dataLive;
    }
}

BlankFragment.class:

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        listItemViewModel = ViewModelProviders.of(this, viewModelFactory)
                .get(VModel.class);
        listItemViewModel.setData("");
        return inflater.inflate(R.layout.fragment_blank, container, false);
    }
15
Nikolas Bozic

ここでの問題は、フラグメントをアクティビティからデタッチすると、フラグメントとそのビューモデルの両方が破棄されないことです。戻ってきたときに、同じフラグメントに古いオブザーバーがまだ存在しているときに、livedataに新しいオブザーバーを追加します(onCreateView()でオブザーバーを追加した場合)。 記事 があります(実際にSOスレッドでも))それについて話します(ソリューション付き)。

それを修正する簡単な方法(記事でも)は、オブザーバーを追加する前に、オブザーバーをライブデータから削除することです。

更新:サポートライブラリv28では、ViewLifeCycleOwnerと呼ばれる新しいLifeCycleOwnerが here の詳細情報を修正する必要があります

10
Julien Neo

LifecycleOwnerとしてgetActivityを使用する代わりに、フラグメントを使用する必要があります。

変化する

viewmModel.observeData().observe(getActivity(), new Observer<String>() {

viewmModel.observeData().removeObservers(this);
viewmModel.observeData().observe(this, new Observer<String>() {
6
Cheok Yan Cheng

viewmModelonCreateViewに作成するのではなく、onCreateに作成する必要があります。ビューが作成されるたびにデータにリスナーを追加しないでください。

2
Samuel Eminet

これがあなたが間違っていることです...

  viewmModel.observeData().observe(getActivity(), new Observer<String>() {
    @Override
    public void onChanged(@Nullable String s) {

        if(s.equals("0")) {
            BlankFragment fragment = (BlankFragment) manager.findFragmentByTag(DETAIL_FRAG);
            if (fragment == null) {
                fragment = BlankFragment.newInstance();
            }
            addFragmentToActivity(manager,
                    fragment,
                    R.id.root_activity_detail,
                    DETAIL_FRAG
            );
        } else {
            Toast.makeText(getContext(), "Wrong text", Toast.LENGTH_SHORT).show();
        }
    }
});

上記のコードでは、「getActivity()」の代わりに「this」または「viewLifecycleOwner」を使用できます。

なぜならば、observeメソッドでgetActivity()を渡すので、フラグメントを開くときは常に、フラグメントではなくアクティビティでオブザーバーの新しいインスタンスをアタッチしているからです。したがって、フラグメントを殺してもオブザーバーは生き続けるでしょう。そのため、livedataが値をポストすると、livedataを監視するオブザーバーが多すぎるため、すべてのオブザーバーにデータが送信され、すべてに通知されます。このため、オブザーバーが何度も呼び出されます。そのため、このようなフラグメントでライブデータを観察する必要があります。

  viewmModel.observeData().observe(this, new Observer<String>() {
    @Override
    public void onChanged(@Nullable String s) {

        if(s.equals("0")) {
            BlankFragment fragment = (BlankFragment) manager.findFragmentByTag(DETAIL_FRAG);
            if (fragment == null) {
                fragment = BlankFragment.newInstance();
            }
            addFragmentToActivity(manager,
                    fragment,
                    R.id.root_activity_detail,
                    DETAIL_FRAG
            );
        } else {
            Toast.makeText(getContext(), "Wrong text", Toast.LENGTH_SHORT).show();
        }
    }
});  

しかし、それでもonchangedメソッドは2回呼び出されます。
onchangedメソッド内の1つの条件をチェックすることでこれを停止できます。

    dash_viewModel.getDashLiveData().observe(viewLifecycleOwner, object : Observer<AsyncResponse> {
        override fun onChanged(t: AsyncResponse?) {
            if(viewLifecycleOwner.lifecycle.currentState==Lifecycle.State.RESUMED){
                setData(t)
            }

        }

    })

私の調査から、対応するアクティビティのViewModelを使用してフラグメント化すると、ライブデータの監視を開始した場合でも、最初に最新のアイテムが送信されることがわかりました。あなたがあなたのフラグメントからそれを呼ばなかったとしても。

onChangeメソッドが2回呼び出されました

  1. フラグメントが開始状態のとき-最後に発行されたアイテムを受け取る

  2. フラグメントが再開状態の場合-フラグメントのいずれかでAPIの呼び出しを受信します。

ので、私は常にこのようにviewLifecycleOwnerの助けを借りてフラグメントの状態を確認しました

   if(viewLifecycleOwner.lifecycle.currentState==Lifecycle.State.RESUMED){
      // if the fragment in resumed state then only start observing data
        }

googleがこのソリューションをサポートライブラリ28.0.0とandroidxに直接実装し、getViewLifecycleOwner()メソッドを使用したため、viewlifecycleownerはFragmentsとActivityの両方によって提供されます。 viewlifecycleownerには、コンポーネントのライフサイクルに関する情報が含まれています。

Javaでは、viewlifecycleownerの代わりにgetViewLifecycleOwner()を使用できます。

1
Aryan Dhankar
viewmModel = ViewModelProviders.of(getActivity(), viewModelFactory)
            .get(VModel.class);

viewmModelLifecycleOwnerはアクティビティであるため、オブザーバーはライフサイクルの状態が Lifecycle.State.DESTROYED の場合にのみ自動的に削除されます。
あなたの状況では、オブザーバーは自動的に削除されません。そのため、以前のオブザーバーを手動で削除するか、毎回同じオブザーバーのインスタンスを渡す必要があります。

0
wangqi060934

フラグメント内で一度だけlivedataを観察します。そのためには、onCreateView()ではなく、onCreate()でobserveメソッドを呼び出します。戻るボタンを押すと、onCreateView()メソッドが呼び出され、ビューモデルがデータを再度観察できるようになります。

 @Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

   mPatientViewModel.getGetCaseDetailLiveData().observe(this, jsonObjectResponse -> parseViewSentResponse(jsonObjectResponse));
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    // TODO: inflate a fragment view
    View rootView = super.onCreateView(inflater, container, savedInstanceState);

    return rootView;
}
0
VIVEK CHOUDHARY

オブザーバーをフィールド変数として宣言するだけで、ライフサイクルがコードのその部分を呼び出すたびに新しいオブザーバーを作成しません。 ;)

つまり、kotlinの場合:

YourFragment: Fragment() {

private val dataObserver = Observer<Data> { data ->
      manageData(data)
  }

...

//now you should subscribe your data after you instantiate your viewModel either in onCreate, onCreateView, onViewCreated, depends on your case..

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    viewModel.liveData.observe(this, dataObserver)
}

...

}
0
emirua

これが私がこの問題をどのように解決するかの例です.[TESTED AND WORKING]

 viewModel.getLoginResponse().observe(getViewLifecycleOwner(), new Observer<String>() {
        @Override
        public void onChanged(String response) {
            if(getViewLifecycleOwner().getLifecycle().getCurrentState()== Lifecycle.State.RESUMED){
                // your code here ...
            }

        }
    });
0
Biplob Das

@ Samuel-Eminetの後にいくつかの有用な情報を追加すると、onCreate(Bundle?)Fragmentの作成時に1回だけ呼び出され、押し戻すとビューは再作成されますが、フラグメントは作成されません(その理由はViewModelは同じです。ビューに影響を与えるライフサイクルのメソッドをサブスクライブすると、ビューは何度も再サブスクライブされます。オブザーバーが存在しなくなったので、尋ねたとしても、それを伝えることはできません。 liveData.hasObservers()の場合。

onCreate(Bundle?)をサブスクライブするのが最善の方法ですが、多くの人がbindingを使用しており、viewは現時点では作成されていないため、これが最適ですそれを行う方法:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    lifecycleScope.launchWhenStarted {
        subscribeUI()
    }
}

ここで、Fragmentの開始時にFragmentのライフサイクルに何かを実行するように指示し、それを1回だけ呼び出します。

0