dagger使用_使用Dagger-Hilt简化顶级目的地之间的Jetpack导航

dagger使用

Google created Jetpack Navigation to simplify navigation between destinations (such as fragment destinations) and allow defining logical scopes between these destinations using shared navigation graphs.

Google创建了Jetpack Navigation,以简化目标(例如片段目标)之间的导航,并允许使用共享导航图在这些目标之间定义逻辑范围。

We can scope ViewModels to the associated NavBackStackEntry of these navigation graphs, and even persist their state across process death using the SavedStateHandle.

我们可以将ViewModel的范围限定为这些导航图的关联NavBackStackEntry,甚至可以使用SavedStateHandle在整个进程死亡期间保持其状态。

However, as a NavController instance is scoped to an Activity, the prevalent pattern for using Jetpack Navigation from a ViewModel has been historically verbose. For each navigation event a ViewModel wanted to emit, it used to define a “LiveData event wrapper”, which was observed by a Fragment that would actually talk to the NavController.

但是,由于NavController实例的作用域是Activity,因此从历史模型使用Jetpack Navigation的流行模式历来是冗长的。 对于ViewModel要发出的每个导航事件,它都定义了一个“ LiveData事件包装器 ”,由实际上与NavController进行通信的Fragment观察到。

Can we do better? Can we decrease the amount of code needed to trigger navigation from a ViewModel, and move the “responsibility” of “actually executing navigation actions” from the Fragments themselves?

我们可以做得更好吗? 我们是否可以减少从ViewModel触发导航所需的代码量,并从Fragments本身移走“实际执行导航操作”的“职责”?

And considering the answer is yes, what does it have to do with Hilt? 😏

考虑到答案是肯定的,它与Hilt有什么关系? 😏

什么是匕首击剑? (What is Dagger-Hilt?)

Dagger-Hilt is Google’s new recommended dependency injection framework built on top of Dagger2.

Dagger-Hilt是Google在Dagger2之上构建的新的推荐依赖注入框架

It’s still alpha, but it’s already quite powerful. The general idea is that each component you might have defined in the past (which is most commonly the ApplicationComponent) already exists as a “pre-defined” component, into which you must “install” your existing modules.

它仍然是Alpha,但已经非常强大。 通常的想法是,您过去可能已定义的每个组件( 最常见的是 ApplicationComponent )已经作为“预定义”组件存在,您必须在其中“安装”现有模块。

The most notable components are ApplicationComponent and ActivityRetainedComponent.

最值得注意的组件是ApplicationComponentActivityRetainedComponent

Any class marked with @Singleton inside a module installed in the ApplicationComponent will have its provider scoped to the app component.

ApplicationComponent安装的模块中任何带有@Singleton标记的类,其提供者的作用域都应为应用程序组件。

Any class marked with @ActivityRetainedScoped inside a module installed in the ActivityRetainedComponent will have its provider scoped to the activity-retained component.

ActivityRetainedComponent安装的模块内任何标有@ActivityRetainedScoped类,其提供者的作用域都将保留在Activity- @ActivityRetainedScoped组件中。

What is the ActivityRetainedComponent? Internally, it uses an Activity-scoped ViewModel to scope an instance to the Activity’s retained scope (and keep it across configuration changes).

什么是ActivityRetainedComponent ? 在内部,它使用活动范围的ViewModel将实例的范围限定为活动的保留范围(并使其保留在配置更改中)。

The dependencies bound to either the ApplicationComponent or the ActivityRetainedComponent will be available for ViewModels injected via @ViewModelInject from Hilt’s own ViewModelFactory (which is automatically the “default” when using @AndroidEntryPoint on an Activity/Fragment).

通过Hilt自己的ViewModelFactory通过@ViewModelInject注入的ViewModel可以使用绑定到ApplicationComponentActivityRetainedComponent的依赖项(在Activity / Fragment上使用@AndroidEntryPoint时,它自动为“默认”)。

设置Dagger-Hilt (Setting up Dagger-Hilt)

Currently, we have to add the following dependencies:

当前,我们必须添加以下依赖项:

// dagger
implementation 'com.google.dagger:dagger:2.28'
kapt 'com.google.dagger:dagger-compiler:2.28'


// hilt
implementation 'com.google.dagger:hilt-android:2.28-alpha'
kapt 'com.google.dagger:hilt-android-compiler:2.28-alpha'
kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.28-alpha'
kaptTest 'com.google.dagger:hilt-android-compiler:2.28-alpha'
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha01'
kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha01'


// add plugin
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'


// apply plugin
apply plugin: 'dagger.hilt.android.plugin'

Afterwards, we need to annotate the application class with @HiltAndroidApp, and any injected Activity/Fragment with @AndroidEntryPoint.

之后,我们需要注释与应用类@HiltAndroidApp ,任何注射活性/片段与@AndroidEntryPoint

We must also add the @InstallIn(SomeComponent::class) annotation on our modules.

我们还必须在模块上添加@InstallIn(SomeComponent::class)批注。

Dagger-Hilt和ViewModel注入 (Dagger-Hilt and ViewModel injection)

Hilt allows injecting ViewModels directly with singleton, unscoped and activity-retained instances. It also supports providing the SavedStateHandle to a ViewModel as an assisted parameter, out of the box.

Hilt允许直接向单例,无作用域和活动保留实例注入ViewModels。 它还支持开箱即用地将SavedStateHandle作为辅助参数提供给ViewModel

Any ViewModel annotated with @ViewModelInject constructor() can be provided from Hilt’s ViewModelFactory, and they can receive the SavedStateHandle using @Assisted.

可以从Hilt的ViewModelFactory提供使用@ViewModelInject constructor()注释的任何ViewModel,并且它们可以使用@Assisted接收@Assisted

Then, we can get a reference to these viewModels in the simplest way possible (via ktx):

然后,我们可以以最简单的方式(通过ktx)获得对这些viewModel的引用:

@AndroidEntryPoint
class LoginFragment : Fragment(R.layout.login_fragment) {
    private val viewModel by viewModels<LoginViewModel>()

NavGraph范围的ViewModels呢? (What about NavGraph-scoped ViewModels?)

By default, the Hilt ViewModelFactory is the default ViewModelProvider.Factory of a HiltActivity or a HiltFragment.

默认情况下,Hilt ViewModelFactory是HiltActivity或HiltFragment的默认ViewModelProvider.Factory。

If we intend to scope our ViewModel to a NavGraph, we’ll still need to provide Hilt’s factory as the factory. As long as we can assume that the Fragment’s arguments are the same as the NavGraph’s own arguments, this can work correctly for providing arguments to the SavedStateHandle, too.

如果我们打算将ViewModel的范围限定为NavGraph,则仍然需要将Hilt的工厂作为工厂提供。 只要我们假设Fragment的参数与NavGraph自己的参数相同 ,这对于向SavedStateHandle提供参数也可以正常工作。

inline fun <reified T : ViewModel> Fragment.hiltNavGraphViewModels(@IdRes navGraphIdRes: Int) =
    viewModels<T>(
        ownerProducer = { findNavController().getBackStackEntry(navGraphIdRes) },
        factoryProducer = { defaultViewModelProviderFactory }
    )

使用Jetpack导航,Jetpack ViewModel和Dagger-Hilt处理导航事件 (Handling navigation events using Jetpack Navigation, Jetpack ViewModel, and Dagger-Hilt)

So how does this help us reduce the .observe(viewLifecycleOwner, EventObserver {s in our code? Quite simple, actually.

因此,这如何帮助我们减少代码中的.observe(viewLifecycleOwner, EventObserver { s?实际上非常简单。

We can create a NavigationDispatcher class that exists in the ActivityRetainedScope.

我们可以创建一个存在于ActivityRetainedScopeNavigationDispatcher类。

Then, we can ensure that pending navigation actions are handled in the Activity (but kept alive across configuration changes, thanks to the scope).

然后,我们可以确保在“活动”中处理待处理的导航操作(但由于作用域,它可以在配置更改中保持活动状态)。

By describing the pending navigation event as lambda expressions, we can make the Activity always handle the navigation event using the current instance of the NavController, even if the NavHostFragment is recreated. And thanks to the Lifecycle API (which is what LiveData uses internally), we can ensure that we get the navigation events only when the observer is active (after onStart).

通过将未决的导航事件描述为lambda表达式,即使重新创建了NavHostFragment,我们也可以使Activity始终使用NavController的当前实例处理导航事件。 借助Lifecycle API(LiveData在内部使用的API),我们可以确保仅在观察者处于活动状态时(在onStart之后)才获得导航事件。

typealias NavigationCommand = (NavController) -> Unit

Then we can use either EventEmitter<NavigationCommand> or LiveData<Event<NavigationCommand>>

然后我们可以使用EventEmitter<NavigationCommand>LiveData<Event<NavigationCommand>>

@ActivityRetainedScoped
class NavigationDispatcher @Inject constructor() {
    private val navigationEmitter: EventEmitter<NavigationCommand> = EventEmitter()
    val navigationCommands: EventSource<NavigationCommand> = navigationEmitter


    fun emit(navigationCommand: NavigationCommand) {
        navigationEmitter.emit(navigationCommand)
    }
}

And on the Activity-side:

在活动方面:

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var navigationDispatcher: NavigationDispatcher


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)


        navigationDispatcher.navigationCommands.observe(this) { command ->
            command.invoke(Navigation.findNavController(this, R.id.nav_host))
        }
    }
}

And we can use this from any of our ViewModels with minimal effort:

而且,我们可以轻松地从任何ViewModel中使用它:

class LoginViewModel @ViewModelInject constructor(
    private val navigationDispatcher: NavigationDispatcher,
    @Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
    fun onRegisterClicked() {
        navigationDispatcher.emit { navController ->
            navController.navigate(R.id.logged_out_to_registration)
        }
    }
}

We can of course instead use the safe-args plugin instead as well:

当然,我们当然也可以使用safe-args插件:

fun onLoginClicked() {
    val username = // ...
  
    navigationDispatcher.emit { navController ->
        navController.navigate(
            LoggedOutGraphDirections.loggedOutToLoggedIn(username))
        }  
    }
}

结论 (Conclusion)

Using a single ActivityRetainedScoped class that would enqueue the current pending navigation actions (similarly to either Cicerone’s CommandBuffer or Simple-Stack’s Backstack), we can eliminate all those pesky _navigateToSomeDirection live datas, and instead trigger the action that we wanted, where we wanted, the whole time: executing a navigation action directly from the ViewModel, while ensuring that it’ll work no matter what.

使用单个ActivityRetainedScoped类可以使当前挂起的导航操作入队(类似于CiceroneCommandBufferSimple-StackBackstack ),我们可以消除所有烦人的_navigateToSomeDirection实时数据,而在需要的地方触发我们想要的操作我们一直都想:直接从ViewModel执行导航操作,同时确保不管它如何工作。

For more information, you can check out the Jetpack Navigation FTUE sample’s Hilt branch, where these changes took place, and these changes can be seen in this commit.

有关更多信息,您可以检出Jetpack Navigation FTUE示例的Hilt分支 ,这些更改是在其中进行的,这些更改可以在此commit中看到。

Check out the discussion thread on /r/android_devs.

在/ r / android_devs上 查看 讨论线程

— — — —

— — — —

附录:对一个有趣问题的回答 (Addendum: A response to an interesting question)

To address one given comment, I’ll append it here:

为了解决一个给定的评论,我将其附加在这里:

Image for post

The issue here is that now the Fragment owns the subscription and therefore is responsible for managing the subscription’s lifecycle, while this should be in the ViewModel so that the asynchronous operation is not canceled on orientation changes.

这里的问题是,现在Fragment拥有订阅,并因此负责管理订阅的生命周期,而这应该在ViewModel中,以便在方向更改时不取消异步操作。

Also, now it’s your View who knows when to start the performSomeTask() call, instead of only exposing its events, such as onSomethingClicked(). The ViewModel owns the behavior, as a NavGraph-scoped ViewModel represents the flow, and as the flow controller, it should also own and be in control of the navigation behavior.

而且,现在是您的View知道何时启动performSomeTask()调用,而不是仅公开其事件,例如onSomethingClicked() 。 ViewModel拥有行为,因为NavGraph范围内的ViewModel表示流,并且作为流控制器,它还应拥有并控制导航行为。

…I typically just have to call backstack.goTo(SomeScreen()) to navigate, as also mentioned in the conclusion, but the details of this are out of scope for this article. Stay tuned for the next one if that’s what you’re curious about. 😏

…我通常只需要调用backstack.goTo(SomeScreen())进行导航,如结论中也提到的那样,但是其详细信息不在本文讨论范围之内。 如果您对此感到好奇,请继续关注下一个。 😏

翻译自: https://itnext.io/simplifying-jetpack-navigation-between-top-level-destinations-using-dagger-hilt-3d918721d91e

dagger使用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值