我有一个Android片段,它为数据绑定注入了一个模型。更具体地说,我注入一个ViewModel(通过标签在Fragment的xml中定义),并调用ViewDataBinding.setViewModel()来启动onCreateView()中的绑定。
通过现场注入将片段注入活动中,并且 ViewModel也通过场注入注入Fragment。但是,ViewModel本身通过构造函数注入注入其依赖项。
当Fragment首次实例化时 - 当savedInstanceState为null时,这可以正常工作。但是,在恢复Fragment时它不起作用:当前,ViewModel为null,因为我在保存Fragment状态时没有对它进行分区。
存储ViewModel状态应该不是问题,但我很难看到以后如何恢复它。状态将在Parcel中,但不是(构造函数)注入的依赖项。
作为示例,请考虑一个简单的登录表单,其中包含两个字段,用户名和密码。 LoginViewModel状态只是两个字符串,但它也具有相关职责的各种依赖关系。下面我提供了Activity,Fragment和ViewModel的简化代码示例。
到目前为止,我还没有提供保存片段时保存ViewModel状态的任何方法。我正在研究这个,使用基本的Parcelable模式,当我意识到概念上我没有看到如何注入ViewModel的依赖项。当通过Parcel接口恢复ViewModel时 - 特别是Parcelable.Creator<>接口---似乎我必须直接实例化我的ViewModel。但是,通常会注入此对象,更重要的是,它的依赖项将在构造函数中注入。
这似乎是一个特定的Android案例,实际上是一个更普遍的Dagger2案例:注入的对象有时会从保存状态恢复,但仍需要通过构造函数注入其依赖项。
这是LoginActivity ...
public class LoginActivity extends Activity {
@Inject /* default */ Lazy<LoginFragment> loginFragment;
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.login_activity);
ActivityComponent.Creator.create(getAppComponent(), this).inject(this);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.activity_container, loginFragment.get())
.commit();
}
}
}
这是LoginFragment ...
public class LoginFragment extends Fragment {
@Inject /* default */ LoginViewModel loginViewModel;
@Nullable
@Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
final LoginFragmentBinding binding = setViewDataBinding(LoginFragmentBinding.inflate(inflater, container, false));
binding.setViewModel(loginViewModel);
// ... call a few methods on loginViewModel
return binding.getRoot();
}
}
,最后,这是LoginViewModel的抽象版本......
public class LoginViewModel {
private final Dependency dep;
private String userName;
private String password;
@Inject
public LoginViewModel(final Dependency dep) {
this.dep = dep;
}
@Bindable
public String getUserName() {
return userName;
}
public void setUserName(final String userName) {
this.userName = userName;
notifyPropertyChanged(BR.userName);
}
// ... getter / setter for password
}
答案 0 :(得分:1)
在您的特定用例中,最好在Fragment中注入,而不是将ViewModel从Activity传递给Fragment,并在其中包含依赖项。您希望这样做的原因是为了更好地协调ViewModel与Fragment的生命周期。
public class LoginFragment extends Fragment {
@Inject /* default */ LoginViewModel loginViewModel;
@Nullable
@Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
final LoginFragmentBinding binding = setViewDataBinding(LoginFragmentBinding.inflate(inflater, container, false));
return binding.getRoot();
}
@Override
public void onActivityCreated(View v) {
FragmentComponent.Creator.create((LoginActivity) getActivity(), this).inject(this);
binding.setViewModel(loginViewModel);
}
}
这意味着每次创建Fragment时,都会注入一个新的ViewModel
。
但是,我怀疑仅凭这一点对于您的特定用例来说还不够。在某个阶段,您可能必须提取一个轻量级工厂类来创建ViewModel,以将其与依赖项分离,并允许saveInstanceState
。{/ p>
这样的事情很可能会成功:
public class LoginViewModelFactory {
private final Dependency dependency;
public LoginViewModelFactory(Dependency dependency) {
this.dependency = dependency;
}
public LoginViewModel create() {
return new LoginViewModel(dependency);
}
}
然后你只需要在Fragment中注入工厂:
public class LoginFragment extends Fragment {
@Inject LoginViewModelFactory loginViewModelFactory;
private LoginViewModel loginViewModel;
@Override
public void onActivityCreated(Bundle b) {
FragmentComponent.Creator.create((LoginActivity) getActivity(), this).inject(this);
loginViewModel = loginViewModelFactory.create();
binding.setViewModel(loginViewModel);
}
}
因为ViewModel现在与依赖项分离,所以您可以轻松实现Parcelable:
public class LoginViewModel {
private String userName;
private String password;
public LoginViewModel(Parcel in) {
userName = in.readString();
password = in.readString();
}
@Bindable
public String getUserName() {
return userName;
}
public void setUserName(final String userName) {
this.userName = userName;
notifyPropertyChanged(BR.userName);
}
// ... getter / setter for password
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(userName);
dest.writeString(password);
}
public static final Creator<LoginViewModel> CREATOR = new Creator<LoginViewModel>() {
@Override
public LoginViewModel createFromParcel(Parcel in) {
return new LoginViewModel(in) {};
}
@Override
public LoginViewModel[] newArray(int size) {
return new LoginViewModel[size];
}
};
}
由于它现在是可以分割的,你可以将它保存在片段的分类中:
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(LoginViewModel.PARCELABLE_LOGIN_VIEW_MODEL, loginViewModel);
}
然后你需要检查它是否在你的一个创建方法中被恢复:
@Override
public void onActivityCreated(Bundle b) {
FragmentComponent.Creator.create((LoginActivity) getActivity(), this).inject(this);
loginViewModel = bundle.getParcelable(LoginViewModel.PARCELABLE_LOGIN_VIEW_MODEL);
if (loginViewModel == null) {
loginViewModel = loginViewModelFactory.create();
}
binding.setViewModel(loginViewModel);
}
答案 1 :(得分:0)
LoginActivity保持不变......
public class LoginActivity extends Activity {
@Inject /* default */ Lazy<LoginFragment> loginFragment;
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.login_activity);
ActivityComponent.Creator.create(getAppComponent(), this).inject(this);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.activity_container, loginFragment.get())
.commit();
}
}
}
然而,对LoginFragment的主要更改是它选择性地注入其依赖项,即LoginViewModel。这是基于如果savedInstanceState为null(或不是)---尽管可能还可以检查一个(或所有)依赖项是否为null。我使用前一张支票,因为语义可以说更清楚。请注意onCreate()和onCreateView()中的显式检查。
当savedInstanceState为null时,则假设片段是通过注入从头开始实例化的; LoginViewModel不会为null。相反,当savedInstanceState为非null时,则正在重建该类而不是注入该类。在这种情况下,Fragment必须自己注入依赖项,反过来,这些依赖项需要使用savedInstanceState重新表示自己。
在我最初的询问中,我没有打扰保存状态的示例代码,但我在此解决方案中包含了完整性。
public class LoginFragment extends Fragment {
private static final String INSTANCE_STATE_KEY_VIEW_MODEL_STATE = "view_model_state";
@Inject /* default */ LoginViewModel loginViewModel;
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
ActivityComponent.Creator.create(((BaseActivity) getActivity()).getAppComponent(),
getActivity()).inject(this);
}
}
@Nullable
@Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
final LoginFragmentBinding binding = setViewDataBinding(LoginFragmentBinding.inflate(inflater, container, false));
if (savedInstanceState != null) {
loginViewModel.unmarshallState(
savedInstanceState.getParcelable(INSTANCE_STATE_KEY_VIEW_MODEL_STATE));
}
binding.setViewModel(loginViewModel);
// ... call a few methods on loginViewModel
return binding.getRoot();
}
@Override
public void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(INSTANCE_STATE_KEY_VIEW_MODEL_STATE, loginViewModel.marshallState());
}
}
然后,最后的改变是让ViewModel从Fragment按需保存/恢复其状态。有很多方法可以解决这个问题,但都遵循标准的Android方法。
在我的情况下,因为我有越来越多的ViewModel--每个都有(注入)依赖项,状态和行为---我决定创建一个单独的ViewModelState类,它只封装将要的状态保存并恢复到片段中的Bundle。然后,我将相应的编组方法添加到ViewModels。在我的实现中,我有基类来处理所有ViewModel,但下面是一个没有基类支持的简化示例。
为了简化实例状态的保存/恢复,我使用了Parceler。这是我的示例LoginViewModelState类。是的,没有样板!
@Parcel
/* default */ class LoginViewModelState {
/* default */ String userName;
/* default */ String password;
@Inject
public LoginViewModelState() { /* empty */ }
}
这里是更新的LoginViewModel示例,主要显示了LoginViewModelState的使用以及引擎盖下的Parceler帮助方法......
public class LoginViewModel {
private final Dependency dep;
private LoginViewModelState state;
@Inject
public LoginViewModel(final Dependency dep,
final LoginViewModelState state) {
this.dep = dep;
this.state = state;
}
@Bindable
public String getUserName() {
return state.userName;
}
public void setUserName(final String userName) {
state.userName = userName;
notifyPropertyChanged(BR.userName);
}
// ... getter / setter for password
public Parcelable marshallState() {
return Parcels.wrap(state);
}
public void unmarshallState(final Parcelable parcelable) {
state = Parcels.unwrap(parcelable);
}
}