0

У меня есть Фрагмент Android, который вводит модель привязки данных. более конкретно, я добавляю ViewModel (определенный в XML-фрагменте фрагмента через тег) и вызываю ViewDataBinding.setViewModel(), чтобы инициировать привязку в onCreateView().Фрагмент Android с сохраненнымInstanceState безпараллельный Dagger2 инъецированная модель с впрыском конструктора

Фрагмент вводится в действие посредством инъекции поля, а ViewModel вводится в Фрагмент также посредством инъекции поля. однако сам ViewModel вводит свои зависимости через инъекцию конструктора.

Это работает отлично, когда первый экземпляр фрагментации --- когда savedInstanceState имеет значение null. однако он не работает, когда восстанавливается Фрагмент: в настоящее время ViewModel имеет значение null, потому что я не разделял его, когда состояние фрагмента сохраняется.

Сохранение состояния ViewModel не должно быть проблемой, но я с трудом вижу, как его восстановить позже. состояние будет находиться в Посылке, но не в зависимостях (конструкторах).

В качестве примера рассмотрим простую форму входа, содержащую два поля, имя пользователя и пароль. состояние LoginViewModel - это просто две строки, но также имеет различные зависимости для соответствующих обязанностей. ниже я предоставляю приведенный пример кода для Activity, Fragment и ViewModel.

пока что я не предоставил никаких средств для сохранения состояния ViewModel при сохранении фрагмента. Я работал над этим, имея базовый Parcelable pattern, когда понял, что концептуально я не видел, как вводить зависимости ViewModel. при восстановлении ViewModel через интерфейс Parcel - особенно интерфейс Parcelable.Creator <>, похоже, мне нужно напрямую создать экземпляр MyModelModel. однако этот объект обычно вводится и, что более важно, его зависимости вводятся в конструкторе.

Это похоже на конкретный случай 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

Если ответ был полезным, ты не против принятия Это? –

ответ

0

спасибо так много Дэвид Роусон для вашего полезный пост. Мне нужно было немного дополнительного времени, чтобы решить ваше предложение, что именно я делаю, и придумал более простое решение. что сказал, я не мог получить там без того, что вы предоставили, так что еще раз спасибо! Следующее - это решение, используя тот же пример кода i, который указан в первоначальном запросе.

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 (или нет), хотя, вероятно, также можно проверить, равна ли одна (или все) зависимостей. я пошел с прежней проверкой, так как семантика была, возможно, более понятной. обратите внимание на явные проверки в onCreate() и onCreateView().

, когда savedInstanceState имеет значение NULL, тогда предполагается, что Фрагмент создается с нуля посредством инъекции; LoginViewModel не будет null. наоборот, если savedInstanceState не имеет значения null, то класс перестраивается, а не вводится. в этом случае Фрагмент должен сам вводить свои зависимости и, в свою очередь, эти зависимости должны переформулировать себя с помощью 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 сохранить/восстановить свое состояние на спрос со стороны фрагмента. есть много способов решить эту проблему, но все они следуют стандартным подходам Android.

в моем случае, так как у меня растущее число ViewModels --- каждый из которых имеет (вложенные) зависимости, состояние и поведение --- я решил создать отдельный класс ViewModelState, который инкапсулирует только состояние, которое будет сохраняться и восстанавливаться в/из Bundle во Фрагменте. Затем я добавил соответствующие методы маршаллинга в ViewModels. в моей реализации у меня есть базовые классы, которые обрабатывают это для всех ViewModels, но ниже - упрощенный пример без поддержки базового класса.

для облегчения сохранения/восстановления состояния экземпляра, я использую 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); 
    } 
} 
1

В вашем конкретном прецеденте может быть лучше вводить внутри фрагмента, а не пропускать ViewModel f выполните операцию с фрагментом с зависимостью внутри него. Причина, по которой вы хотели бы сделать это, - лучше координировать ViewModel с жизненным циклом фрагмента.

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

Это будет означать, что каждый раз, когда ваш фрагмент получает создан, он будет введен с новым ViewModel.

Однако я подозреваю, что этого будет недостаточно для вашего конкретного случая использования. На каком-то этапе вам, вероятно, придется извлечь легкий заводский класс для создания ViewModel, чтобы отделить его от зависимости и разрешить saveInstanceState того же самого.

Нечто подобное, вероятно, сделать трюк:

public class LoginViewModelFactory { 

    private final Dependency dependency; 

    public LoginViewModelFactory(Dependency dependency) { 
     this.dependency = dependency; 
    } 

    public LoginViewModel create() { 
      return new LoginViewModel(dependency); 
    } 
} 

Тогда вам просто нужно вводить завод внутри фрагмента Сейчас:

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 теперь отделено от зависимости, вы можете легко реализовать Исходный:

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

Поскольку он сейчас является парным celable, вы можете сохранить его в outbundle фрагмента:

@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); 
    } 
Смежные вопросы