ViewModel воссоздается после поворота; если впрыснуть напрямую кинжалом2

Возможный дубликат этого

Я изучаю API для инъекций Android с помощью dagger2. Итак, в моем примере приложения я внедрил ViewModel непосредственно в активность; взгляните на следующие фрагменты кода.

class SampleApp : Application(), HasActivityInjector {

    @Inject
    lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>

    override fun activityInjector(): AndroidInjector<Activity> =
            dispatchingAndroidInjector

    override fun onCreate() {
        super.onCreate()

        DaggerApplicationComponent.builder()
                .application(this)
                .build()
                .inject(this)
    }
}

@Component(modules = [
    AndroidInjectionModule::class,
    ActivityBindingModule::class,
    AppModule::class
    /** Other modules **/
])
@Singleton
interface ApplicationComponent {

    @Component.Builder
    interface Builder {

        @BindsInstance
        fun application(application: Application): Builder

        fun build(): ApplicationComponent
    }

    fun inject(sampleApp: SampleApp)
}

@Module
public abstract class ActivityBindingModule {

    @ContributesAndroidInjector(modules = MainModule.class)
    public abstract MainActivity contributeMainActivityInjector();
}

class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var mainViewModel: mainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        AndroidInjection.inject(this)

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_dashboard)
    }
}

@Module
public class MainModule {
    @Provides
    public static MainViewModelProviderFactory provideMainViewModelProviderFactory(/** some dependencies **/) {
        return new MainViewModelProviderFactory(/** some dependencies **/);
    }

    @Provides
    public static MainViewModel provideMainViewModel(MainActivity activity, MainViewModelProviderFactory factory) {
        return ViewModelProviders.of(activity, factory).get(MainViewModel.class);
    }
}

как видите, я внедрил MainViewModel непосредственно в активность. Теперь, если я поверну активность, инжектируемый экземпляр будет другим.

Но если я введу MainViewModelProviderFactory в MainActivity и выполню

ViewModelProviders.of(activity, factory).get(MainViewModel.class) он возвращает тот же экземпляр, что и раньше.

Я не понимаю, что не так с моей реализацией.

Любые указатели будут заметны.


person Rupesh    schedule 26.07.2018    source источник
comment
Может быть, вам стоит сделать ViewModel @Singleton? И если вы посмотрите на свою ViewModelFactory (вы ее не показали, но я предполагаю, что это типичная реализация), вы увидите, что эта фабрика может создавать модели просмотра только тогда, когда вы касаетесь ее.   -  person redlabrat    schedule 26.07.2018
comment
Вы хотите внедрить ViewModelProviders, а не саму ViewModel. См. хороший пример: proandroiddev.com/   -  person NSimon    schedule 26.07.2018
comment
Фабрика имеет типовую реализацию. Я не думаю, что создание MainViewModel Singleton — хорошая идея, поскольку объекты ViewModel автоматически сохраняются при изменении конфигурации.   -  person Rupesh    schedule 26.07.2018
comment
Идея ViewModelsProviders состоит в том, чтобы хранить экземпляры ViewModels между изменениями конфигурации в ViewModelStores. Я думаю, что ваш код работает так: когда активность уничтожается из-за изменений конфигурации, MainModule также уничтожается, и после создания нового экземпляра MainActivity dagger создаст для него новый MainModule, и поэтому вы получаете новый экземпляр ViewModelProvider и новый экземпляр ViewModel.   -  person redlabrat    schedule 26.07.2018
comment
@redlabrat думал в том же направлении, но модуль здесь не имеет состояния, вы можете видеть, что все методы статичны ... кроме того, если я введу фабрику и использую эту фабрику для создания ViewModel, я получу старый экземпляр после поворота .. как это работает?   -  person Rupesh    schedule 26.07.2018
comment
Как мне жаль, фабрика ничего не делает, фабрика предоставляет только экземпляр ViewModel, и в этом случае (когда вы предоставляете фабрику) ваш ViewModelProvider остается прежним, а провайдер просто берет сохраненный экземпляр вашей ViewModel из ViewModelStore.   -  person redlabrat    schedule 26.07.2018
comment
Взгляните на этот вопрос: stackoverflow.com/questions/37848563/   -  person redlabrat    schedule 26.07.2018
comment
хм.. выглядит убедительно.. спасибо.. поищу еще..   -  person Rupesh    schedule 26.07.2018
comment
@redlabrat проблема была связана с AndroidInjection и сохранением состояния .. пожалуйста, посмотрите мой ответ .. спасибо .. !!   -  person Rupesh    schedule 26.07.2018


Ответы (1)


Итак, просмотрев источник ViewModelProvider, ViewModelProviders, FragmentActivity и да, dagger2 documentation, у меня есть ответ..

Не стесняйтесь, поправьте меня, если я ошибаюсь..

Мы не должны внедрять ViewModel напрямую, вместо этого мы должны внедрять фабрики.

Я столкнулся с этой проблемой из-за этой строки AndroidInjection.inject(this).

По мнению авторов кинжала

Крайне важно вызывать AndroidInjection.inject() перед super.onCreate() в Activity

Давайте посмотрим, что здесь не так на очень высоком уровне.

Активность сохранит свою ViewModel при ротации с помощью onRetainNonConfigurationInstance и восстановит это в onCreate()

Поскольку мы делаем инъекцию перед вызовом super.onCreate(), мы получим не сохраненный объект MainViewModel, а новый.

Если хотите подробностей, читайте дальше..


Когда кинжал пытается внедрить MainViewModel, он вызывает метод provideMainViewModel() MainModule, который вызывает следующее выражение (имейте в виду, что super.onCreate() еще не вызвано)

ViewModelProviders.of(activity, factory).get(MainViewModel.class)

ViewModelProviders.of вернет ViewModelProvider, который содержит ссылки для ViewModelStore соответствующей активности и ViewModelProviderFactory.

public static ViewModelProvider of(@NonNull FragmentActivity activity,
        @Nullable Factory factory) {
    .
    .

    return new ViewModelProvider(ViewModelStores.of(activity), factory);
}

ViewModelStore.of(activity) в конечном итоге вызовет активность getViewModelStore(), поскольку в этом случае активностью является AppCompatActivity, которая реализует ViewModelStoreOwner

AppCompatActivity создает новый ViewModelStore, если он равен нулю, и содержит ссылку на него. ViewModelStore является оберткой над Map<String, ViewModel> с дополнительным методом clear()

@NonNull
public ViewModelStore getViewModelStore() {
    if (this.getApplication() == null) {
        throw new IllegalStateException("Your activity is not yet attached to the Application instance. You can't request ViewModel before onCreate call.");
    } else {
        if (this.mViewModelStore == null) {
            this.mViewModelStore = new ViewModelStore();
        }

        return this.mViewModelStore;
    }
}

Всякий раз, когда устройство ротируется, активность сохраняет состояние экземпляра без конфигурации с помощью onRetainNonConfigurationInstance и восстанавливает его в onCreate. (например, mViewModelStore)

ViewModelProvider.get попытается получить ViewModel из активности ViewModelStore

public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    ViewModel viewModel = mViewModelStore.get(key);

    if (modelClass.isInstance(viewModel)) {
        //noinspection unchecked
        return (T) viewModel;
    } else {
        //noinspection StatementWithEmptyBody
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }

    viewModel = mFactory.create(modelClass);
    mViewModelStore.put(key, viewModel);
    //noinspection unchecked
    return (T) viewModel;
}

В этом конкретном примере; поскольку мы еще не вызвали метод super.onCreate(), реализация попросит factory создать его и обновит соответствующий ViewModelStore.

Таким образом, у нас получилось два разных объекта MainViewModel.

person Rupesh    schedule 26.07.2018