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()
1 Like

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)
}
2 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.

1 Like

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.