Android ViewModel 附加参数

除了应用程序上下文之外,是否有方法向我的自定义 AndroidViewModel构造函数传递附加参数。 例如:

public class MyViewModel extends AndroidViewModel {
private final LiveData<List<MyObject>> myObjectList;
private AppDatabase appDatabase;


public MyViewModel(Application application, String param) {
super(application);
appDatabase = AppDatabase.getDatabase(this.getApplication());


myObjectList = appDatabase.myOjectModel().getMyObjectByParam(param);
}
}

当我想要使用我的定制 ViewModel类时,我在我的片段中使用这段代码:

MyViewModel myViewModel = ViewModelProvider.of(this).get(MyViewModel.class)

因此,我不知道如何传递额外的参数 String param到我的自定义 ViewModel。我只能传递 Application 上下文,但不能传递其他参数。我真的很感激你的帮助。谢谢你。

编辑: 我添加了一些代码。我希望它现在更好了。

98632 次浏览

您需要为 ViewModel 创建一个工厂类。

public class MyViewModelFactory implements ViewModelProvider.Factory {
private Application mApplication;
private String mParam;




public MyViewModelFactory(Application application, String param) {
mApplication = application;
mParam = param;
}




@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
return (T) new MyViewModel(mApplication, mParam);
}
}

在实例化视图模型时,您可以这样做:

MyViewModel myViewModel = ViewModelProvider(this, new MyViewModelFactory(this.getApplication(), "my awesome param")).get(MyViewModel.class);

对于 kotlin,您可以使用委托属性:

val viewModel: MyViewModel by viewModels { MyViewModelFactory(getApplication(), "my awesome param") }

还有另一个新选项——实现 HasDefaultViewModelProviderFactory并用工厂的实例化覆盖 getDefaultViewModelProviderFactory(),然后在没有工厂的情况下调用 ViewModelProvider(this)by viewModels()

对于一个在多个不同视图模型之间共享的工厂,我会扩展 mlyko 的回答如下:

public class MyViewModelFactory extends ViewModelProvider.NewInstanceFactory {
private Application mApplication;
private Object[] mParams;


public MyViewModelFactory(Application application, Object... params) {
mApplication = application;
mParams = params;
}


@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
if (modelClass == ViewModel1.class) {
return (T) new ViewModel1(mApplication, (String) mParams[0]);
} else if (modelClass == ViewModel2.class) {
return (T) new ViewModel2(mApplication, (Integer) mParams[0]);
} else if (modelClass == ViewModel3.class) {
return (T) new ViewModel3(mApplication, (Integer) mParams[0], (String) mParams[1]);
} else {
return super.create(modelClass);
}
}
}

并实例化视图模型:

ViewModel1 vm1 = ViewModelProviders.of(this, new MyViewModelFactory(getApplication(), "something")).get(ViewModel1.class);
ViewModel2 vm2 = ViewModelProviders.of(this, new MyViewModelFactory(getApplication(), 123)).get(ViewModel2.class);
ViewModel3 vm3 = ViewModelProviders.of(this, new MyViewModelFactory(getApplication(), 123, "something")).get(ViewModel3.class);

使用具有不同构造函数的不同视图模型。

我编写了一个库,它应该使这个过程更简单、更干净,不需要多绑定或工厂样板,同时可以无缝地使用 ViewModel 参数,这些参数可以作为依赖项由 Dagger 提供: Https://github.com/radutopor/viewmodelfactory

@ViewModelFactory
class UserViewModel(@Provided repository: Repository, userId: Int) : ViewModel() {


val greeting = MutableLiveData<String>()


init {
val user = repository.getUser(userId)
greeting.value = "Hello, $user.name"
}
}

观点:

class UserActivity : AppCompatActivity() {
@Inject
lateinit var userViewModelFactory2: UserViewModelFactory2


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user)
appComponent.inject(this)


val userId = intent.getIntExtra("USER_ID", -1)
val viewModel = ViewModelProviders.of(this, userViewModelFactory2.create(userId))
.get(UserViewModel::class.java)


viewModel.greeting.observe(this, Observer { greetingText ->
greetingTextView.text = greetingText
})
}
}

(KOTLIN)我的解决方案使用一点点的反射。

假设您不希望每次创建需要一些参数的新 ViewModel 类时都创建相同的外观的 Factory 类。您可以通过反射来实现这一点。

例如,您将有两个不同的活动:

class Activity1 : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)


val args = Bundle().apply { putString("NAME_KEY", "Vilpe89") }
val viewModel = ViewModelProviders
.of(this, ViewModelWithArgumentsFactory(args))
.get(ViewModel1::class.java)
}
}


class Activity2 : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)


val args = Bundle().apply { putInt("AGE_KEY", 29) }
val viewModel = ViewModelProviders
.of(this, ViewModelWithArgumentsFactory(args))
.get(ViewModel2::class.java)
}
}

以及这些活动的视图模型:

class ViewModel1(private val args: Bundle) : ViewModel()


class ViewModel2(private val args: Bundle) : ViewModel()

然后神奇的部分,工厂类的实现:

class ViewModelWithArgumentsFactory(private val args: Bundle) : NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
try {
val constructor: Constructor<T> = modelClass.getDeclaredConstructor(Bundle::class.java)
return constructor.newInstance(args)
} catch (e: Exception) {
Timber.e(e, "Could not create new instance of class %s", modelClass.canonicalName)
throw e
}
}
}

为什么不这样做:

public class MyViewModel extends AndroidViewModel {
private final LiveData<List<MyObject>> myObjectList;
private AppDatabase appDatabase;
private boolean initialized = false;


public MyViewModel(Application application) {
super(application);
}


public initialize(String param){
synchronized ("justInCase") {
if(! initialized){
initialized = true;
appDatabase = AppDatabase.getDatabase(this.getApplication());
myObjectList = appDatabase.myOjectModel().getMyObjectByParam(param);
}
}
}
}

然后像这样分两步使用:

MyViewModel myViewModel = ViewModelProvider.of(this).get(MyViewModel.class)
myViewModel.initialize(param)

我将其设置为一个类,在该类中传递已创建的对象。

private Map<String, ViewModel> viewModelMap;


public ViewModelFactory() {
this.viewModelMap = new HashMap<>();
}


public void add(ViewModel viewModel) {
viewModelMap.put(viewModel.getClass().getCanonicalName(), viewModel);
}


@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
for (Map.Entry<String, ViewModel> viewModel : viewModelMap.entrySet()) {
if (viewModel.getKey().equals(modelClass.getCanonicalName())) {
return (T) viewModel.getValue();
}
}
return null;
}

然后

ViewModelFactory viewModelFactory = new ViewModelFactory();
viewModelFactory.add(new SampleViewModel(arg1, arg2));
SampleViewModel sampleViewModel = ViewModelProviders.of(this, viewModelFactory).get(SampleViewModel.class);

使用依赖注入

这对于生产代码来说是更高级和更好的。

Dagger2 ,Square 的 协助注射为 ViewModel 提供了一个生产就绪的实现,可以注入必要的组件,比如处理网络和数据库请求的存储库。它还允许在活动/片段中手动注入参数/参数。这里是一个简明扼要的代码基础上的 实施的步骤瓦拉迪的详细职位,匕首小贴士的代码要点。

Dagger Hilt 是下一代解决方案,在7/12/20版本中采用 alpha 版本,一旦库处于发布状态,就会以更简单的设置提供相同的用例。

在 Kotlin 实施 ABc0

传递参数/参数

// Override ViewModelProvider.NewInstanceFactory to create the ViewModel (VM).
class SomeViewModelFactory(private val someString: String): ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T = SomeViewModel(someString) as T
}


class SomeViewModel(private val someString: String) : ViewModel() {
init {
//TODO: Use 'someString' to init process when VM is created. i.e. Get data request.
}
}


class Fragment: Fragment() {
// Create VM in activity/fragment with VM factory.
val someViewModel: SomeViewModel by viewModels { SomeViewModelFactory("someString") }
}

使用参数/参数启用保存状态

class SomeViewModelFactory(
private val owner: SavedStateRegistryOwner,
private val someString: String) : AbstractSavedStateViewModelFactory(owner, null) {
override fun <T : ViewModel?> create(key: String, modelClass: Class<T>, state: SavedStateHandle) =
SomeViewModel(state, someString) as T
}


class SomeViewModel(private val state: SavedStateHandle, private val someString: String) : ViewModel() {
val feedPosition = state.get<Int>(FEED_POSITION_KEY).let { position ->
if (position == null) 0 else position
}
        

init {
//TODO: Use 'someString' to init process when VM is created. i.e. Get data request.
}
        

fun saveFeedPosition(position: Int) {
state.set(FEED_POSITION_KEY, position)
}
}


class Fragment: Fragment() {
// Create VM in activity/fragment with VM factory.
val someViewModel: SomeViewModel by viewModels { SomeViewModelFactory(this, "someString") }
private var feedPosition: Int = 0
     

override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
someViewModel.saveFeedPosition((contentRecyclerView.layoutManager as LinearLayoutManager)
.findFirstVisibleItemPosition())
}
        

override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
feedPosition = someViewModel.feedPosition
}
}

基于@vilpe89的上述 Kotlin androidViewModel 个案解决方案

class ExtraParamsViewModelFactory(
private val application: Application,
private val myExtraParam: String
): ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
SomeViewModel(application, myExtraParam) as T
}

然后一个片段可以将 viewModel 初始化为

class SomeFragment : Fragment() {
    

// ...


private val myViewModel: SomeViewModel by viewModels {
ExtraParamsViewModelFactory(this.requireActivity().application, "some string value")
}


// ...


}

然后是实际的 ViewModel 类

class SomeViewModel(application: Application, val myExtraParam:String) : AndroidViewModel(application) {
// ...
}

或者用某种合适的方法。

override fun onActivityCreated(...){
// ...
val myViewModel = ViewModelProvider(this, ExtraParamsViewModelFactory(this.requireActivity().application, "some string value")).get(SomeViewModel::class.java)
// ...
}

在 Kotlin,由于 ViewModel的呼叫者和 ViewModel本身运行在不同的协同程序中,使用 kotlinx.coroutines.channels.Channel在它们之间传递数据更加自然和方便:

class NewViewModel : ViewModel() {
private val newData: MutableLiveData<Service.DataEntry?> by lazy {
MutableLiveData<Service.DataEntry?>().also {
viewModelScope.launch {
val channel = Service.ParamChannel   // type Channel<Params>
val params = channel.receive()
it.value = Service.postSomething(params)
}
}
}


fun getData(): LiveData<Service.DataEntry?> {
return newData
}
}


// Calling code:
val model: NewViewModel by viewModels()
model.getData().observe(this) { newData ->
if (newData != null) {
...
}
else
{
...
}
}
runBlocking {
Service.ParamChannel.send(theParams)
}

这是我为了演示目的而匿名化的工作代码的一部分。

正确的方法是使用依赖注入框架,比如 匕首柄。如果不使用 DI 框架,那么使用 ViewModelFactory。

匕首柄:

带参数的 ViewModel

@HiltViewModel
class MyViewModel @Inject constructor(
private val myRepository: MyRepository,
private val savedStateHandle: SavedStateHandle
) : ViewModel() { ... }

一个仓库

class MyRepository @Inject constructor(
private val myRemoteDataSource: MyDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) { ... }

一个模块,用于提供依赖项/参数,以便将它们注入到存储库和 ViewModel 中。

@InstallIn(ViewModelComponent::class)
@Module
object MyProvideModule {
@Provides
fun provideMyDataSource(@ApplicationContext context: Context): MyDataSource {
//code to create MyDataSource...
return MyDataSource(context)
}


@Provides
fun provideCoroutineDispatcher(): CoroutineDispatcher {
return Dispatchers.IO
}
}

用于绑定存储库的模块

@Module
@InstallIn(ViewModelComponent::class)
interface RepositoryModules {
@Binds
fun provideMyRepository(repository: MyRepository): MyRepository
}

使用@HiltAndroidApp 注释启动应用程序的 Dagger hilt。

@HiltAndroidApp
class MainApplication : Application() {


override fun onCreate() {
super.onCreate()
}


}

在活动中获取 ViewModel

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val myViewModel: MyViewModel by viewModels()
// Other code...
}

在片段中获取 ViewModel

@AndroidEntryPoint
class MyFragment : Fragment() {
private val myViewModel: MyViewModel by activityViewModels()
// Other code...
}

使用 ViewModelFactory:

具有参数 messageDataStore 的 ViewModel,其中 MessageDataStore 是 DataStore 类,或者它可以是您希望传递到 ViewModel 的任何其他内容。

class MyViewModel(
private val messageDataStore: MessageDataStore,
): ViewModel() { ... }

用于创建 ViewModel 的 ViewModel 工厂类

/**
* Factory for all ViewModels.
*/
@Suppress("UNCHECKED_CAST")
class ViewModelFactory constructor(
private val messageDataStore: MessageDataStore,
owner: SavedStateRegistryOwner,
defaultArgs: Bundle? = null
) : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
override fun <T : ViewModel> create(
key: String,
modelClass: Class<T>,
handle: SavedStateHandle
) = with(modelClass) {
when {
isAssignableFrom(MyViewModel::class.java) ->
MyViewModel(messageDataStore)
else ->
throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}")
}
} as T
}

用于创建依赖项/参数的应用程序类

class MyApp : Application() {
val messageDataStore: MessageDataStore
get() = MessageDataStore.getInstance(this)


}

用于获取活动和片段中的工厂类的扩展函数 MyExt.kt

fun AppCompatActivity.getViewModelFactory(savedInstanceState: Bundle?): ViewModelFactory {
val messageDataStore = (applicationContext as MyApp).messageDataStore
return ViewModelFactory(messageDataStore, this, savedInstanceState)
}


fun Fragment.getViewModelFactory(savedInstanceState: Bundle?): ViewModelFactory {
val messageDataStore = (requireContext().applicationContext as MyApp).messageDataStore
return ViewModelFactory(messageDataStore, this.requireActivity(), savedInstanceState)
}

在活动中获取 ViewMode

class MainActivity : AppCompatActivity() {


private lateinit var myViewModel: MyViewModel
// Other code...


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val vm by viewModels<MyViewModel> { getViewModelFactory(savedInstanceState) }
myViewModel = vm
// Other code...
}
}

以片段的形式获取视图模型。

class MyFragment : Fragment() {
private lateinit var myViewModel: MyViewModel
//Other code...


override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val vm by activityViewModels<MyViewModel> { getViewModelFactory(savedInstanceState) }
myViewModel = vm
//Other code...
}
}