android 使用新的体系结构组件ViewModel在片段之间共享数据

vnjpjtjt  于 2022-12-28  发布在  Android
关注(0)|答案(8)|浏览(120)

在Last Google IO上,Google发布了一些新的拱门组件的预览,其中之一是ViewModel。
在文档中,google展示了这个组件的一个可能用途:
一个Activity中的两个或多个片段需要相互通信是很常见的。这绝不是小事,因为两个片段都需要定义一些接口描述,所有者Activity必须将两者绑定在一起。此外,两个片段都必须处理另一个片段尚未创建或不可见的情况。
这个常见的痛点可以通过使用ViewModel对象来解决。想象一个常见的主从片段的情况,其中我们有一个片段,用户从列表中选择一个项目,另一个片段显示所选项目的内容。
这些片段可以共享一个ViewModel,使用它们的活动范围来处理这种通信。
并示出了一个实现示例:

public class SharedViewModel extends ViewModel {
    private final SavedStateHandle state;

    public SharedViewModel(SavedStateHandle state) {
        this.state = state;
    }

    private final MutableLiveData<Item> selected = state.getLiveData("selected");

    public void select(Item item) {
        selected.setValue(item);
    }

    public LiveData<Item> getSelected() {
        return selected;
    }
}

public class MasterFragment extends Fragment {
    private SharedViewModel model;

    @Override
    protected void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        model = new ViewModelProvider(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {
    @Override
    protected void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        SharedViewModel model = new ViewModelProvider(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, { item ->
           // update UI
        });
    }
}

我非常兴奋地想到,可能不需要那些用于片段通过活动进行通信的接口。
但是Google的例子并没有确切地说明我应该如何调用master中的detail片段。
我仍然必须使用将由活动实现的an interface,它将调用fragmentManager.replace(...),或者有另一种方法使用新的架构来实现它?

xriantvc

xriantvc1#

2017年6月12日更新,
Android Official提供了一个简单、精确的示例来说明ViewModel如何在Master-Detail模板上工作,您应该先看看它。在片段之间共享数据

正如@CommonWare,@Quang Nguyen所理解的,Yigit的目的不是从主到细节的调用,而是更好地使用中间人模式,但如果你想做一些碎片事务,它应该在Activity中完成,此时ViewModel类应该是Activity中的静态类,可能包含一些丑陋的回调,回调Activity来做碎片事务。
我尝试过实现这个,并做了一个简单的项目。你可以看看它。大部分的代码是从谷歌IO 2017引用,也结构. https://github.com/charlesng/SampleAppArch
我没有使用主细节片段来实现组件,而是使用旧的(ViewPager中片段之间的通信)。逻辑应该是相同的。
但我发现使用这些组件很重要
1.你想在中间人中发送和接收什么,它们应该只在视图模型中发送和接收
1.片段类的修改似乎不多,因为它只是将实现从“接口回调”更改为“侦听和响应ViewModel”
1.视图模型初始化看起来很重要,可能会在活动中调用。
1.使用MutableLiveData使源仅在活动中同步。

1.寻呼机活动

public class PagerActivity extends AppCompatActivity {
    /**
     * 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 = new ViewModelProvider(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 final SavedStateHandle state;
    private final MutableLiveData<String> messageContainerA;
    private final MutableLiveData<String> messageContainerB;

    public PagerAgentViewModel(SavedStateHandle state) {
        this.state = state;

        messageContainerA = state.getLiveData("Default Message");
        messageContainerB = state.getLiveData("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.空白片段A

public class BlankFragmentA extends Fragment {

    private PagerAgentViewModel viewModel;

    public BlankFragmentA() {
        // Required empty public constructor
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        viewModel = new ViewModelProvider(getActivity()).get(PagerAgentViewModel.class);

        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) {
                viewModel.sendMessageToB("Hello B");
            }
        });

        //setup the listener for the fragment A
        viewModel.getMessageContainerA().observe(getViewLifecycleOwner(), 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);
        return view;
    }

}

4.空白片段B

public class BlankFragmentB extends Fragment {
 
    public BlankFragmentB() {
        // Required empty public constructor
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        viewModel = new ViewModelProvider(getActivity()).get(PagerAgentViewModel.class);

        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) {
                viewModel.sendMessageToA("Hello A");
            }
        });

        //setup the listener for the fragment B
        viewModel.getMessageContainerB().observe(getViewLifecycleOwner(), 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);
        return view;
    }

}
zwghvu4y

zwghvu4y2#

正如在Google官方教程中所写的,现在您可以使用by activityViewModels()获得共享视图模型

// Use the 'by activityViewModels()' Kotlin property delegate
// from the fragment-ktx artifact
private val model: SharedViewModel by activityViewModels()
gdx19jrr

gdx19jrr3#

我已经找到了一个类似的解决方案,因为其他人根据谷歌codelabs的例子。我有两个片段,其中一个等待对象的变化,在另一个,并继续其进程与更新的对象。
对于这种方法,您将需要一个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文件中包含以下代码行。source

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

lmyy7pcs4#

在使用附加到被视为容器的活动的回调之前。
这个回调是两个Fragment之间的中间人。这个解决方案的缺点是:

  • Activity必须携带回调函数,这意味着Activity要做大量的工作。
  • 两个片断之间是紧密耦合的,以后很难更新或改变逻辑。

有了新的ViewModel(支持LiveData),你就有了一个优雅的解决方案,它现在扮演着中间人的角色,你可以将它的生命周期附加到Activity上。

  • 两个片段之间的逻辑和数据现在在ViewModel中布局。
  • Two Fragment从ViewModel获取数据/状态,因此它们不需要知道彼此。
  • 此外,借助LiveData的强大功能,您可以根据主Fragment的变化来更改细节Fragment,这种方式是React式的,而不是以前的回调方式。

现在,您完全摆脱了与Activity和相关片段紧密耦合的回调。
我强烈推荐你去谷歌的代码实验室,在第五步,你可以找到一个很好的例子。

cpjpxq1n

cpjpxq1n5#

我实现了一些类似于您想要的东西,我的视图模型包含LiveData对象,该对象包含Enum状态,当您想要将片段从master更改为details(或相反)时,您可以调用ViewModel函数来更改livedata值,Activity知道要更改片段,因为它正在观察livedata对象。
测试视图模型:

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

测试活动:

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

主片段:

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

细节片段:

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

kx5bkwkv6#

我最终使用自己的ViewModel来保存将触发Activity方法的侦听器。类似于old way,但正如我所说,将侦听器传递给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);

我对它进行了表面测试,它工作正常。当我着手实现与此相关的其他特性时,我会意识到可能发生的任何问题。

wgx48brx

wgx48brx7#

对于那些使用Kotlin的用户,请尝试以下方法:

  • 将androidx ViewModel and LiveData库添加到您的gradle文件
  • 在片段中调用视图模型,如下所示:
class MainFragment : Fragment() {

      private lateinit var viewModel: ViewModel

      override fun onActivityCreated(savedInstanceState: Bundle?) {
          super.onActivityCreated(savedInstanceState)

          // kotlin does not have a getActivity() built in method instead we use activity, which is null-safe
          activity?.let {
              viemModel = ViewModelProvider(it).get(SharedViewModel::class.java)
          }
      }
  }

上述方法是一种很好的做法,因为它可以避免由于空指针异常而导致的崩溃
编辑:作为btraas的补充:activity被编译到getActivity()中,在android SDK中标记为@Nullable。activity和getActivity()都是可访问的,并且是等效的。

byqmnocz

byqmnocz8#

你可以像这样设置从细节片段到主片段的值

model.selected.setValue(item)

相关问题