i have an Android Fragment that injects a model for data binding. more specifically, i inject a ViewModel (defined in the Fragment's xml via a tag) and, call ViewDataBinding.setViewModel() to initiate the binding in onCreateView().
the Fragment is injected in the Activity via field injection, and the ViewModel is injected into the Fragment also via field injection. however, the ViewModel itself injects its dependencies via constructor injection.
this works fine when the Fragment is first instantiated --- when savedInstanceState is null. however, it doesn't work when the Fragment is being restored: currently, the ViewModel is null because i haven't parceled it when the Fragment state is being saved.
storing the ViewModel state shouldn't be an issue, but i'm having difficulty seeing how to restore it afterward. the state will be in the Parcel but not the (constructor) injected dependencies.
as an example, consider a simple Login form, which contains two fields, User Name and Password. the LoginViewModel state is simply two strings, but it also has various dependencies for related duties. below i provide a reduced code example for the Activity, Fragment, and ViewModel.
as of yet, i haven't provided any means of saving the ViewModel state when the Fragment is saved. i was working on this, with the basic Parcelable pattern, when i realized that conceptually i did not see how to inject the ViewModel's dependencies. when restoring the ViewModel via the Parcel interface --- particularly the Parcelable.Creator<> interface --- it seems i have to directly instantiate my ViewModel. however, this object is normally injected and, more importantly, its dependencies are injected in the constructor.
this seems like a specific Android case that is actually a more general Dagger2 case: an injected object is sometimes restored from saved state but still needs its dependencies injected via the constructor.
here is the 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();
}
}
}
here is the 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();
}
}
and, finally, here is an abstracted version of the 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
}