Solution to Deprecated method ViewModelProviders#of

As source code mentioned, the static method ViewModelProviders#of was deprecated.

     * @deprecated Use the 'by viewModels()' Kotlin property delegate or
     * {@link ViewModelProvider#ViewModelProvider(ViewModelStoreOwner)},
     * passing in the activity.
     */
    @Deprecated
    @NonNull
    @MainThread
    public static ViewModelProvider of(@NonNull FragmentActivity activity) {
        return new ViewModelProvider(activity);
    }

This solution is using ‘by viewModels()’ instead.

  1. Add lib fragment-ktx in dependencies
dependencies {
    implementation 'androidx.fragment:fragment-ktx:1.1.0'
}
  1. Define quizViewModel as follows:
    private val quizViewModel: QuizViewModel by viewModels()
2 Likes

This wasn’t showing for me. I looked into dependency versions.

If using

implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'

Then, the method is not deprecated. It was deprecated in

implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0-alpha03

I tend to not use the alpha/beta/rc versions until it goes final. Thanks for the tip moving forward.

I wonder if by viewModels() will be moved in to the lifecycle-extensions dependency at some point. The by viewModels() solution is more elegant.

1 Like

Thanks for the review. :grinning:

Bumping this back up, as now that v2.2.0 has gone live, this deprecation persists.

implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0

I will post a patch over the weekend and followup on the new style following the Factory model.

The easy solution for this app is to change remove the ViewModelProviders usage. The ViewModel can be created using the default factory directly via the ViewModelProvider.

private val quizViewModel: QuizViewModel by lazy {
  ViewModelProvider(this@QuizActivity).get(QuizViewModel::class.java)
}

However, this will not suffice for more complex ViewModels that depend upon other resources. For these situations, we will need to create our own Factory class. For this example, the factory would like follows.

// QuizViewModelFactory.kt
class QuizViewModelFactory : ViewModelProvider.Factory {
  override fun <T : ViewModel?> create(modelClass: Class<T>): T {
    return modelClass.getConstructor().newInstance()
  }
}

Now inside of QuizActivity, we get an instance of QuizViewModel by first creating the factory.

// QuizActivity.kt
private val quizViewModel: QuizViewModel by lazy {
  val factory = QuizViewModelFactory()
  ViewModelProvider(this@QuizActivity, factory).get(QuizViewModel::class.java)
}
4 Likes

Here is a better argument for using the Factory model. Consider Listing 4.6 where we make currentIndex public so we can access and assign its value via the Bundle.

// Listing 4.6 - QuizViewModel.kt
class QuizViewModel : ViewModel() {
  var currentIndex = 0
  ...
}

This is now used in Listing 4.11

// Listing 4.11 - MainActivity.kt
override fun onSaveInstanceState(savedInstanceState: Bundle) {
  super.onSaveInstanceState(savedInstanceState)
  Log.i(TAG, "onSaveInstanceState")
  savedInstanceState.putInt(KEY_INDEX, quizViewModel.currentIndex)
}

and in Listing 4.12

// Listing 4.12 - MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
  ...
  val currentIndex = savedInstanceState?.getInt(KEY_INDEX, 0) ?: 0
  quizViewModel.currentIndex = currentIndex
}

While this suffices, it publicly exposes the currentIndex to the Activity. It could be possible the Activity sets the currentIndex on our ViewModel to -1 or 100, a value out of range for our questionBank. If you have been working through the challenges and are keeping score, then this is the same issue that the Activity could set the score to any value.

We would much rather have the currentIndex value be kept private and only expose its value via a getter and not allow the Activity to set its value. Likewise with the score.

The ViewModelProvider.Factory allows us to set the currentIndex upon ViewModel creation. We would structure our classes as follows:

// QuizViewModel.kt
class QuizViewModel(private var currentQuestionIndex: Int = 0) : ViewModel() {
  ...
  val currentIndex: Int
    get() = currentQuestionIndex
  ...
  fun moveToNext() {
    currentQuestionIndex = (currentQuestionIndex + 1) % questionBank.size
  }
  fun moveToPrevious() {
    currentQuestionIndex = (currentQuestionIndex - 1 + questionBank.size) % questionBank.size
  }
}

// QuizViewModelFactory.kt
class QuizViewModelFactory(private val initialQuestionIndex: Int) : ViewModelProvider.Factory {
  override fun <T : ViewModel?> create(modelClass: Class<T>): T {
    return modelClass.getConstructor(Int::class.java).newInstance(initialQuestionIndex)
  }
}

// MainActivity.kt
class QuizActivity : AppCompatActivity() {
  private lateinit var quizViewModel: QuizViewModel

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val currentIndex = savedInstanceState?.getInt(KEY_INDEX, 0) ?: 0

    val factory = QuizViewModelFactory(currentIndex)
    quizViewModel = ViewModelProvider(this@MainActivity, factory).get(QuizViewModel::class.java)
    ...
  }

  override fun onSaveInstanceState(savedInstanceState: Bundle) {
    super.onSaveInstanceState(savedInstanceState)
    savedInstanceState.putInt(KEY_INDEX, quizViewModel.currentIndex)
  }
}

Now we have the proper protection in place that only QuizViewModel can edit the current question index AND we can store information on the bundle to persist across process death. This is also easily extendable to track current score, number of cheats remaining, and other information from the challenges in Chapters 1-7.

2 Likes

Hi, Jeff, do you have full solution for this chapter on github for example? Still confused with this factories.Thanks

No I do not have it publicly available. Is there a step that you need assistance on? I can copy more to this thread.

Or is there a concept regarding the Factory model that is confusing? Here is the Wikipedia page on the Factory pattern:

Yes, can you, please, share your desicion to this chapter. Will be awesome.

When using the by viewModels() Kotlin property delegate, I see Intellij is complaining about
Cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6

I found the fix in

android {
    ...
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_1_8.toString()
    }
}

hi, I still cannot get this to work. On rotating the device, current state is lost and the first question appears. As suggested in this thread I’m using:

fragment-ktx:1.2.5
jvmTarget is set to version 1.8
private val quizViewModel: QuizViewModel by viewModels()

I tried using older versions of fragment-ktx as well as @jpaone suggestion:
private val quizViewModel: QuizViewModel by lazy {
ViewModelProvider(this@MainActivity).get(QuizViewModel::class.java)
}
but I just can’t get the state to persist. If anyone can help me understand why, would be great. Thanks!

Hi @jpaone!
I cannot get the main idea of a

Factory

in this case:

You wrote that ViewModelProvider.Factory provides

Does the construction below give the same result… Or am I wrong? Why?

// QuizViewModel.kt
class QuizViewModel(initCurrentQuestionIndex: Int = 0) : ViewModel() {
private var currentQuestionIndex = initCurrentQuestionIndex
val currentIndex: Int
get() = currentQuestionIndex

}

thanks I had to initialize KEY_INDEX in that main program to make it work. but that was my only modification. this helped alot. more then the other explains.

If you want to just get through the example in the book, I found that stick with listing 4.2 and use
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
still works for me.

2 Likes

I made an account here just to “like” your answer rayd. It solved the immediate issue and let me continue with the book without having to deviate to much from it in later chapters. I’m sure at some point I’ll understand what happened in the updated module, and why that other way of dealing with it is better, but today, I just need to handle the bare basics.