results
介绍 (Introduction)
After years of having to receive activity results directly in Activity and Fragment classes, Google finally released a new API in order to decouple this functionality from these classes.
经过多年直接在Activity和Fragment类中接收活动结果之后,Google终于发布了新的API ,以使此功能与这些类脱钩。
To demonstrate how this new API works, and how we could take advantage of its new capabilities to make our code cleaner and more testable, we are going to use a sample application. It consists of two Acitivities: ActivityA and ActivityB, where the first starts the second and waits for a success or a failure result.
为了演示此新API的工作原理,以及如何利用其新功能使我们的代码更整洁和更具测试性,我们将使用一个示例应用程序 。 它由两个活动组成:ActivityA和ActivityB,其中第一个开始第二个,然后等待成功或失败的结果。
传统方法 (Traditional approach)
The official API since the first version of Android consisted of two points:
自第一个Android版本以来的官方API包括两点:
Starting another Activity (ActivityB) with startActivityForResult
使用startActivityForResult启动另一个活动(ActivityB)
startActivityForResult(ActivityB.newIntent(this), ACTIVITY_A_REQUEST_CODE)
Receiving result (in ActivityA) from the other Activity (ActivityB) via onActivityResult
通过onActivityResult从另一个活动(ActivityB)接收结果(在ActivityA中)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { ... }
The second bullet point was what make it impossible for developers to decouple this logic from the Activity. The official explanation of why this could not be a callback (for example) on the startActivityForResult method, is that it would not work consistently after process death or configuration changes (e.g. when sending an Intent to receive a photo from the Camera, as this is a resource-intensive operation, especially on low-end devices, our Activity could be killed). A callback on the triggering method would not be saved under these conditions, but receiving the result on the onActivityResult works.
第二个要点是使开发人员无法将此逻辑与Activity分离的原因。 关于为什么它不能作为startActivityForResult方法上的回调的正式解释是,它在进程终止或配置更改后将无法持续工作(例如,当发送意图以从Camera接收照片时)资源密集型操作,尤其是在低端设备上,我们的活动可能会被终止)。 在这种情况下,不会保存触发方法的回调,但是在onActivityResult上接收结果是可行的。
You can find the simple sample for this here.
新的Results API方法 (New Results API approach)
With the new Results API, we don’t have to deal with these two methods anymore: we can use the new prepareCall method to handle the result as a callback in the originating call. No more separate methods!
使用新的Results API,我们不再需要处理这两种方法:我们可以使用新的prepareCall方法将结果作为始发调用中的回调进行处理。 没有其他单独的方法!
I will not dive into how it works, you can see for yourself on the official documentation. Here is the same sample as before, but using this new API. Compare this with the previous approach and check if you can see its benefits.
我不会深入探讨它的工作原理, 您可以在官方文档中自己查看 。 这是与以前相同的示例,但是使用了这个新的API。 将此与以前的方法进行比较,并检查您是否可以看到其好处。
val startForResult = prepareCall(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> ... }
One caveat that you must remember is that all prepareCall methods should be called sequentially and in the same order every time the Activity is created.
您必须记住的一个警告是,每次创建Activity时,应按相同的顺序依次调用所有prepareCall方法。
使用官方文档解耦New Results API (Decouple New Results API using official documentation)
One drawback with the previous approach, is that it stills is coupled with the Activity, but this is easily decoupled following the recommendations in the official docs: you can have another class implementing LifecycleObserver interface and use the corresponding ActivityResultRegistry.registerActivityResultCallback method that receives a Lifecycle owner.
前一种方法的一个缺点是,它仍然与Activity结合使用,但是可以按照官方文档中的建议轻松将其分离:您可以让另一个类实现LifecycleObserver接口,并使用接收Lifecycle的相应ActivityResultRegistry.registerActivityResultCallback方法老板 。
private lateinit var startForResult : ActivityResultLauncher<Intent>override fun onCreate(owner: LifecycleOwner) { startForResult = registry.registerActivityResultCallback(START_FOR_RESULT_KEY, owner, ActivityResultContracts.StartActivityForResult(), ActivityResultCallback { result -> ... }
}
Following the same sample, you can find the example here.
删除对LifecycleOwner的显式依赖 (Remove explicit dependency on LifecycleOwner)
Note that, while in the previous approach we are no longer starting/receiving results directly in the Activity, our “separate class” (we called it Controller here, but you can call it whatever you want in your favorite presentation layer architecture — MVC/MVP/MVVM/MVI/…) still has to implement LifeycleObserver, which could be considered to be a tight coupling to the Android framework classes.
请注意,虽然在以前的方法中,我们不再直接在Activity中直接开始/接收结果,但我们的“单独的类”(在这里我们将其称为Controller,但您可以在自己喜欢的表示层体系结构中随意调用它-MV C / MV P / MV VM / MV I /…)仍然必须实现LifeycleObserver,可以将其视为与Android框架类的紧密结合。
How can we remove this dependency? There is a hint in the official docs: when there is no LifecycleOwner, use ActivityResultRegistry.registerActivityResultCallback method that does not receives a Lifecycle owner, and remember to dispose() when no longer using it.
我们如何消除这种依赖性? 官方文档中有一个提示:当没有LifecycleOwner时,请使用不接收Lifecycle所有者的ActivityResultRegistry.registerActivityResultCallback方法 ,并记住在不再使用它时请注意dispose() 。
In order to do this, we can use, for example, LiveData: by subclassing it, we can call registerActivityResultCallback in the onActive method, and dispose the results in the onInactive method. There is one important caveat though: as we must always register when creating the Activity, we cannot use the usual observe method of LiveData, which automatically remove the observer when the view is not active. Instead, we must use the observeForever and manually remove the observer in the onDestroy method.
为此,我们可以使用LiveData,例如:通过对其进行子类化,可以在onActive方法中调用registerActivityResultCallback,然后将结果放置在onInactive方法中。 但是有一个重要的警告:由于我们必须在创建Activity时始终进行注册,因此我们无法使用LiveData的常规观察方法,该方法会在视图不活动时自动删除观察者。 相反,我们必须使用observeForever,并在onDestroy方法中手动删除观察者。
Here is the final sample code.
While in this article, we choose to use LiveData, which internally stills uses LifecycleOwner, keep in mind that a similar approach would also work with other libraries, as, for example, RxJava (or manually if you prefer). And perhaps, using other approaches would be a better idea in this case, as currently we cannot use the usual observe method of LiveData, which can be a source of bugs in real applications.
尽管在本文中,我们选择使用LiveData,而LiveData在内部仍然使用LifecycleOwner,但请记住,类似的方法也可以与其他库一起使用,例如RxJava(或者如果需要,可以手动使用)。 也许在这种情况下,使用其他方法会是一个更好的主意,因为当前我们无法使用LiveData的常规观察方法,该方法可能是实际应用程序中的错误源。
结论 (Conclusion)
As an exercise for the reader, the next steps for this POC would be:
作为读者的练习,此POC的下一步是:
- Add an example with Fragments. The resulting sample should be similar to what was shown in the article. 使用片段添加示例。 所得样本应与文章中显示的样本相似。
- Add UI and unit tests: UI tests should be straightforward for all the solutions, but for unit tests the last approach has a clear advantage 添加UI和单元测试:UI测试对于所有解决方案都应该简单明了,但是对于单元测试,最后一种方法具有明显的优势
- Add an example with RxJava for the final approach 为最终方法添加RxJava示例
- Use a custom contract instead of using the equivalent of the current API: in this article we have not touched upon another advantage of the new API, which is the ability of defining custom contracts with expected input and output, providing type-safety for our app 使用自定义合同而不是使用当前API的等效合同:在本文中,我们没有涉及新API的另一个优点,即可以使用预期的输入和输出定义自定义合同的功能,从而为我们的应用提供类型安全性
You can find the complete sample repository here.
进一步阅读 (Further reading)
Introducing the Activity Results APIs, where the author talks more about contracts, which is an important part of the new API
介绍活动结果API,作者在其中更多地讨论合同,这是新API的重要组成部分
results