我在我的项目中使用了Android AAC库和Android数据绑定库。我有AuthActivity和AuthViewModel扩展了android的ViewModel类。在某些情况下,我需要让Activity为ViewModel调用一些方法。 例如,当用户点击Google Auth或Facebook Auth按钮时,它在Activity类中初始化(因为初始化GoogleApiClient我需要Activity上下文,我无法传递给ViewModel,视图模型无法存储Activity字段)。 在Activity类中实现了Google Api和Facebook API的所有逻辑:
//google api initialization
googleApiClient = new GoogleApiClient.Builder(this)
.enableAutoManage(this, this)
.addApi(Auth.GOOGLE_SIGN_IN_API, gso)
.build();
//facebook login button
loginButton.setReadPermissions(Arrays.asList("email", "public_profile"));
loginButton.registerCallback(callbackManager,
此外,我还需要调用登录意图,这也需要活动上下文:
Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(googleApiClient);
startActivityForResult(signInIntent, GOOGLE_AUTH);
我无法请求facebook登录和google登录,或者从视图模型类请求startActivity intent,所以我创建了类接口AuthActivityListener:
public interface AuthActivityListener {
void requestSignedIn();
void requestGoogleAuth();
void requestFacebookAuth();
void requestShowDialogFragment(int type);
}
在活动类中实现侦听器:
AuthActivityRequester authRequestListener = new AuthActivityRequester() {
@Override
public void requestSignedIn() {
Intent intent = new Intent(AuthActivity.this, ScanActivity.class);
startActivity(intent);
AuthActivity.this.finish();
}
@Override
public void requestGoogleAuth() {
Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(googleApiClient);
startActivityForResult(signInIntent, GOOGLE_AUTH);
}
...
并在视图模型类中将此侦听器分配给调用活动方法:
// in constructor
this.authRequester = listener;
// call activity method
public void onClickedAuthGoogle() {
authRequester.requestGoogleAuth();
}
在google或facebook认证通过后,我从活动中调用了视图模型方法:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
callbackManager.onActivityResult(requestCode, resultCode, data);
if (requestCode == GOOGLE_AUTH) {
GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
if (result.isSuccess()) {
GoogleSignInAccount acct = result.getSignInAccount();
if (acct != null) {
viewModel.onGoogleUserLoaded(acct.getEmail(), acct.getId());
} else {
viewModel.onGoogleUserLoaded("", "");
}
}
}
}
任何人都可以解释一下,视图模型和活动之间的这种沟通方式是对的,还是我需要找到另一种从视图模型中调用活动方法的方法?
答案 0 :(得分:0)
MVVM最困难的部分是视图模型一定不了解视图并引用它们
这是一个很强的限制。
您对此有一些选择
1。查看包含上下文参数的模型方法
您可以使方法从视图接收上下文(此方法从视图调用)。
可以实例化上下文相关的变量。
如果您知道内存泄漏,请在视图暂停时将其销毁,或者停止使用生命周期感知的AAC,并在恢复或启动Activity或Fragment时重新设置。
关于onActivityResult,我认为您的解决方案还不错,因为API支持就是这样。
2。通过数据绑定从视图中获取上下文
在布局xml中,您可以使用事件监听器发送视图本身。
<Button
....
android:onClick=“@{(view) -> vm.onClickFacebookLogin(view)}”
然后您可以在Viewmodel中接收视图并从视图中检索上下文
3。使用AndroidViewModel
AndroidViewModel类与ViewModel类相同,但没有Application上下文。
您可以将应用程序上下文与
一起使用gerApplication()
谢谢
答案 1 :(得分:0)
关于如何执行此操作,有几种不同的方法。 在这里,我想与您分享我的方法。我认为哪种方法最适合MVVM模式思想。
如前所述-“ 视图模型必须对视图一无所知并引用它”。对于视图模型如何调用Activity方法,这没有太多选择。首先,想到的是 Listener 方法。但是我认为这种方法有几个缺点:
View
应该订阅/取消订阅ViewModel
,因为它的生命周期很可能比ViewModel
短。ViewModel
应该调用View
的方法,但是View
介于订阅/取消订阅之间; ViewModel
还应注意空听众的情况,因为它可能是null
ViewModel
,Activity
和Listener
界面中进行更改。因此 Listener 方法不太适合。它看起来更像是MVP方法。为了消除上述缺陷(或至少其中一些缺陷),我创建了我称之为 ViewModel Events 的方法。在这种方法中,ViewModel
“发出”(或生成)事件,并让View
对其进行观察。让我看看我在说什么。
首先,我们需要对ViewModel
事件进行某种表示。
abstract class ViewModelEvent {
var handled: Boolean = false
private set
fun handle(activity: BaseActivity) {
handled = true
}
}
正如您已经看到的,handle()
方法将发挥神奇作用。 Activity
处理收到的事件时,会将其实例作为参数传递给handle()
方法。在此方法内部,我们可以调用任何Activity
方法(或将其安全地强制转换为某些特定的Activity
)。 handled
属性旨在不让Activity
两次处理此ViewModelEvent
。
此外,我们需要为ViewModel
创建某种机制来发出其事件。 LiveData
最适合这些需求。它将取消对生命周期事件的观察者订阅,并将存储最后发出的事件(这就是ViewModelEvent
应该具有上述handled
属性的原因)。
abstract class BaseViewModel: ViewModel() {
private val observableEvents = MutableLiveData<ViewModelEvent>()
fun observeViewModelEvents(): LiveData<ViewModelEvent> = observableEvents
protected fun postViewModelEvent(event: ViewModelEvent) {
observableEvents.postValue(event)
}
}
这里没有什么复杂的。只是一个MutableLiveData
(公开为LiveData
)和一种发出事件的方法。顺便说一下,在postViewModelEvent
内部,我们可以检查调用此方法的线程并使用MutableLiveData.postValue
或MutableLiveData.setValue
。
最后是Activity
本身。
abstract class BaseActivity: Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
// ...
viewModel.observeViewModelEvents().observe(this, Observer {
val event = it.takeUnless { it == null || it.handled } ?: return@Observer
handleViewModelAction(event)
})
}
protected open fun handleViewModelAction(event: ViewModelEvent) {
event.handle(this)
}
}
如您所见,一般事件可以在BaseActivity
中处理,而某些特定事件可以通过覆盖handleViewModelAction
方法来处理。
可以针对特定需求更改此方法。例如,ViewModelEvent
不必与Activity
实例一起使用,可以用作“标记”事件,也可以为所需的操作传递一些特定的参数,等等。
ViewModel事件方法使 ViewModel-Activity 通信功能强大且无缝。 Activity
只需订阅一次,就不会错过最新的ViewModel
的活动。
答案 2 :(得分:-1)
你的方法很好。但不知何故,你的界面取决于活动意味着如果你重复使用你的视图这些界面没有用,或者可能是那个场景,你必须创建新的界面来解决你的问题。
但是如果您创建一个Activity实例,那么您可以控制它。