hilt 传递参数给HiltViewModel的构造函数

1,816 阅读1分钟

前言

有这样一种情况,这个ViewModel的构造函数有两个参数,一个参数是通过依赖注入得到的,另外一个参数是需要通过activity,fragment,或者compose 传递过来的。

@HiltViewModel
class MyHiltViewModel @Inject constructor(
    private val repo: UserRepo,
    private val userId: Long,
) : ViewModel() {
    ...
}

@Singleton
class UserRepo@Inject constructor()

方法一:@AssistedInject

这里就不贴代码了,可以移步到这里

方法二:通过SavedStateHandle

@HiltViewModel
class MyHiltViewModel @Inject constructor(
    private val repo: UserRepo,
    private val savedStateHandle: SavedStateHandle,
) : ViewModel() {

    private val userId = savedStateHandle.get<Long>("userId")?:0L
}

1.在compose中把参数传递到viewmodel的SavedStateHandle

方法一:

如果compose所在的Activity、NavBackStackEntry 中的 intent.extras 、arguments 包含有userId的话,在ViewModel中通过savedStateHandle.get<T>()方法可以得到对应的参数。

因为androidX会自动帮你把arguments的所有参数存到SavedStateHandle中去(前提是不用的自定义的ViewModelProvider.Factory去实例化viewModel)。

方法二:

我这里参考了hiltViewModel()源码写了个方法去实现:

@Composable
inline fun <reified VM : ViewModel> hiltViewModelExt(
    viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
        "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
    },
    noinline extrasProducer: (Bundle?) -> Bundle?
): VM {
    val factory = createHiltViewModelFactory(viewModelStoreOwner, extrasProducer)
    return viewModel(viewModelStoreOwner, factory = factory)
}

@Composable
fun createHiltViewModelFactory(
    viewModelStoreOwner: ViewModelStoreOwner,
    extrasProducer: (Bundle?) -> Bundle?
): ViewModelProvider.Factory? = when (viewModelStoreOwner) {
    is NavBackStackEntry -> {
        val navBackStackEntry = viewModelStoreOwner as NavBackStackEntry
        val activity = LocalContext.current.let {
            var ctx = it
            while (ctx is ContextWrapper) {
                if (ctx is Activity) {
                    return@let ctx
                }
                ctx = ctx.baseContext
            }
            throw IllegalStateException(
                "Expected an activity context for creating a HiltViewModelFactory for a " +
                        "NavBackStackEntry but instead found: $ctx"
            )
        }
        HiltViewModelFactory.createInternal(
            activity,
            navBackStackEntry,
            extrasProducer(navBackStackEntry.arguments),
            navBackStackEntry.defaultViewModelProviderFactory,
        )
    }
    is ComponentActivity -> {
        HiltViewModelFactory.createInternal(
            viewModelStoreOwner,
            viewModelStoreOwner,
            extrasProducer(viewModelStoreOwner.intent?.extras),
            SavedStateViewModelFactory(
                viewModelStoreOwner.application,
                viewModelStoreOwner,
                extrasProducer(viewModelStoreOwner.intent?.extras)
            )
        )
    }
    else -> {
        // Use the default factory provided by the ViewModelStoreOwner
        // and assume it is an @AndroidEntryPoint annotated fragment 
        null
    }
}

使用:

val viewModel: MyHiltViewModel = hiltViewModelExt {arg ->
    (arg ?: Bundle()).apply {
        putLong("userId",32465413)
        //putString("name", "hiltVMTest")
        
    }
}

2.在activity、fragment中把参数传递到viewmodel的SavedStateHandle

因为androidX会自动把arguments的所有参数存到SavedStateHandle中去,这里我就想到了一个骚操作: 直接把参数put到activity的intent或者fragment的arguments不就行了。

val viewModel:MyHiltViewModel by viewModels()

在fragment中

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    arguments = (arguments ?: Bundle()).apply {
        putString("name","Fragment HiltVMTest")
    }
}

在activity中

intent.putExtra("name","Activity HiltVMTest")

打印ViewModel的这个参数:

@HiltViewModel
class MyHiltViewModel @Inject constructor(
    private val repo: UserRepo,
    private val savedStateHandle: SavedStateHandle,
) : ViewModel() {

    private val name = savedStateHandle.get<String>("name")?:""

    fun print() {
        Log.d("hj", "name = $name")
    }
}

ok 得到传递过来的参数了