Home > front end >  How to pass runtime arguments to a viewmodel?
How to pass runtime arguments to a viewmodel?

Time:10-17

I have a viewmodel that looks like this:

 class FilterByCategoryViewModel @ViewModelInject constructor(
    private val dataManager: AppDataManager,
    private val networkHelper: NetworkHelper,
    private val category: String
) : ViewModel() { ...
 }

I will get dataManager and networkHelper from the ApplicationModule. But I need to pass category as a runtime parameter. I tried the below approach but I got the error Could not resolve AssistedModule.

https://stackoverflow.com/a/65375442/5742365

I have also tried creating a viewmodel factory as below:

val factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory() {
                @NonNull
                override fun <T : ViewModel?> create(@NonNull modelClass: Class<T>): T {
                    return FilterByCategoryViewModel(
                        category
                    ) as T
                }
            }
filterByCategoryViewModel = ViewModelProvider(requireActivity(), factory).get(FilterByCategoryViewModel::class.java)
        

But then I had to edit the viewmodel to have the category parameter in the constructor and the networkHelper and dataManager parameters injected using field injection (I'm not sure whether it works that way or not). But it still didn't work. The Viewmodel now looks as follows:

    class FilterByCategoryViewModel @ViewModelInject constructor(
        private val category: String
    ) : ViewModel() {
        @Inject
        lateinit var dataManager: AppDataManager
        @Inject
        lateinit var networkHelper: NetworkHelper
...
}

Now I get this build error when I run the app:

D:\Workspace\AndroidProjects\RecipeApp\app\build\tmp\kapt3\stubs\debug\com\neeraja\recipeapp\ui\viewmodel\FilterByCategoryViewModel.java:20: error: incompatible types: NonExistentClass cannot be converted to Annotation
    @error.NonExistentClass()
          ^D:\Workspace\AndroidProjects\RecipeApp\app\build\tmp\kapt3\stubs\debug\com\neeraja\recipeapp\ui\viewmodel\FilterByCategoryViewModel.java:34: error: incompatible types: NonExistentClass cannot be converted to Annotation
    @error.NonExistentClass()
          ^
FAILURE: Build completed with 2 failures.

1: Task failed with an exception.
-----------
* What went wrong:
Execution failed for task ':app:checkDebugDuplicateClasses'.
> A failure occurred while executing com.android.build.gradle.internal.tasks.CheckDuplicatesRunnable
   > Duplicate class android.support.v4.app.INotificationSideChannel found in modules core-1.3.2-runtime (androidx.core:core:1.3.2) and support-compat-26.1.0-runtime (com.android.support:support-compat:26.1.0)
     Duplicate class android.support.v4.app.INotificationSideChannel$Stub found in modules core-1.3.2-runtime (androidx.core:core:1.3.2) and support-compat-26.1.0-runtime (com.android.support:support-compat:26.1.0)
     Duplicate class android.support.v4.app.INotificationSideChannel$Stub$Proxy found in modules core-1.3.2-runtime (androidx.core:core:1.3.2) and support-compat-26.1.0-runtime (com.android.support:support-compat:26.1.0)
     Duplicate class android.support.v4.os.IResultReceiver found in modules core-1.3.2-runtime (androidx.core:core:1.3.2) and support-compat-26.1.0-runtime (com.android.support:support-compat:26.1.0)
     Duplicate class android.support.v4.os.IResultReceiver$Stub found in modules core-1.3.2-runtime (androidx.core:core:1.3.2) and support-compat-26.1.0-runtime (com.android.support:support-compat:26.1.0)
     Duplicate class android.support.v4.os.IResultReceiver$Stub$Proxy found in modules core-1.3.2-runtime (androidx.core:core:1.3.2) and support-compat-26.1.0-runtime (com.android.support:support-compat:26.1.0)
     Duplicate class android.support.v4.os.ResultReceiver found in modules core-1.3.2-runtime (androidx.core:core:1.3.2) and support-compat-26.1.0-runtime (com.android.support:support-compat:26.1.0)
     Duplicate class android.support.v4.os.ResultReceiver$1 found in modules core-1.3.2-runtime (androidx.core:core:1.3.2) and support-compat-26.1.0-runtime (com.android.support:support-compat:26.1.0)
     Duplicate class android.support.v4.os.ResultReceiver$MyResultReceiver found in modules core-1.3.2-runtime (androidx.core:core:1.3.2) and support-compat-26.1.0-runtime (com.android.support:support-compat:26.1.0)
     Duplicate class android.support.v4.os.ResultReceiver$MyRunnable found in modules core-1.3.2-runtime (androidx.core:core:1.3.2) and support-compat-26.1.0-runtime (com.android.support:support-compat:26.1.0)
     
     Go to the documentation to learn how to <a href="d.android.com/r/tools/classpath-sync-errors">Fix dependency resolution errors</a>.

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.
==============================================================================

2: Task failed with an exception.
-----------
* What went wrong:
Execution failed for task ':app:kaptDebugKotlin'.
> A failure occurred while executing org.jetbrains.kotlin.gradle.internal.KaptExecution
   > java.lang.reflect.InvocationTargetException (no error message)

I'm learning Hilt, MVVM, Kotlin by myself and feel so stuck with this. Any suggestions on achieving this?

CodePudding user response:

I am not sure about hilt, but I have done this same thing with Dagger2 using AssistedInject. Here is my implementation,

class MyViewModel @AssistedInject constructor(
    @Assisted private val savedStateHandle: SavedStateHandle,
    dataSource: RemoteDataSource 
) : ViewModel() {

    @AssistedInject.Factory
    interface Factory : AssistedSavedStateViewModelFactory<MyViewModel> {
        override fun create(savedStateHandle: SavedStateHandle): MyViewModel
    }    
    val groupId = savedStateHandle.getLiveData("groupId", "")
    val searchType = savedStateHandle.getLiveData("searchType", 1)
    }

In fragment/activit

val defArgs = bundleOf("groupId" to groupId, "searchType" to searchType)        
val factory = viewModelFactory.create(this, defArgs)
viewModel = ViewModelProvider(this, factory)[MyViewModel::class.java]

CodePudding user response:

I have finally used ViewModelProvider.Factory as follows:

class FilterByTypeViewModelFactory constructor(val dataManager: AppDataManager, 
                                               val networkHelper: NetworkHelper, 
                                               val category: String) : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return FilterByCategoryViewModel(dataManager , networkHelper , category) as T
    }
}

The ViewModel now looks as follows:

class FilterByCategoryViewModel @ViewModelInject constructor(
    val dataManager: AppDataManager,
    val networkHelper: NetworkHelper,
    val category: String
) : ViewModel() {
...
}

And in the Fragment:

@AndroidEntryPoint
class FilterByTypeFragment : Fragment() {
    @Inject lateinit var dataManager: AppDataManager
    @Inject lateinit var networkHelper: NetworkHelper
    ...
     override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        if (arguments != null) {
            category = arguments?.get("categoryId") as String
            val factory = FilterByTypeViewModelFactory(dataManager, networkHelper, category)
            filterByCategoryViewModel = ViewModelProvider(requireActivity(), factory).get(FilterByCategoryViewModel::class.java)
        }
        setupUI()
        setupObserver()
    }
...
}

This is working for me. I have just been learning hilt. I was confused with the options like using ViewModelProvider.Factory or AssistedInject. Although this solution worked for me, I would like to know more about AssistedInject and the situations where we need to choose one over the other.

  • Related