dagger module_android multi module dagger一步一步的实际用例

dagger module

版本号 (Versions)

介绍 (Introduction)

Before we start, I have something to tell you: this will be a journey. I will present a real use case, and together we will develop the requirements step by step while finding different solutions and, of course, problems.

在开始之前,我有件事要告诉您:这将是一段旅程。 我将展示一个实际的用例,并且我们将一起逐步开发需求,同时找到不同的解决方案,当然还有问题。

It’s a long article, but not because I want to, because it has a goal: explain, little by little, how to use Dagger to integrate different modules dependencies in our project.

这是一篇很长的文章,但并不是因为我想要,因为它有一个目标:一点一点地说明如何使用Dagger在我们的项目中集成不同的模块依赖项。

For each step we perform, I will give you a link to the commit where that step was developed. Also, I’m putting some gists along with the article, so you can get familiar with the proposed solution.

对于我们执行的每个步骤,我都会为您提供一个指向该步骤开发位置的提交的链接。 另外,我将在文章中介绍一些gists ,以便您熟悉建议的解决方案。

As prerequisites before starting to read the article, I advise you to have some minimal knowledge of:

作为开始阅读本文之前的先决条件,我建议您对以下方面有所了解:

  • Kotlin.

    Kotlin
  • What a Dagger Component is.

    什么是Dagger Component

  • What a Dagger Module is.

    什么是Dagger Module

  • What a Dagger Scope is.

    匕首Scope是什么。

Lastly, we will follow the Google’s recommendations for using Dagger. You don’t need to read them, I will quote them later ;)

最后,我们将遵循Google关于使用Dagger的建议 。 您不需要阅读它们,我稍后会引用它们;)

If you’re looking for a fast solution, you can go to the GitHub repository, but I warn you: you are going to miss the most important stuff.

如果您正在寻找一种快速的解决方案,则可以转到GitHub存储库 ,但我警告您:您将错过最重要的内容。

Last comment, I promise!

最后的评论,我保证!

  • When you see the word Module (with code format) we're talking about Dagger Modules. If you see the module word, just like that, we are talking about Android Modules.

    当您看到Module (带有代码格式)一词时,我们在谈论的是Dagger模块。 如果您看到模块字,就是这样,我们正在谈论的是Android模块。

  • As a general rule, when you see a thing like this , we're talking about Dagger classes.

    通常,当您看到thing like this ,我们正在谈论Dagger类。

Let’s start!

开始吧!

项目 (Project)

We want to develop an app that provides some tools to perform math operations, such as calculations, graphs, integrate some formula, etc.

我们要开发一个应用程序,其中提供了一些工具来执行数学运算,例如计算,图形,积分一些公式等。

In the first stage of our project, we will develop a module that can perform sums, easy right?

在我们项目的第一阶段,我们将开发一个可以执行求和的模块,容易吗?

We love the hype so we’re going to make a modularized app. We are going to create two modules:

我们喜欢炒作,因此我们将制作一个模块化的应用程序。 我们将创建两个模块:

  • The app module, the application’s main module, whose goal is to create the application using other modules.

    应用程序模块,应用程序的主要模块,其目标是使用其他模块创建应用程序。

  • The calculator module, whose goal is to perform calculations (at this stage, just sums).

    计算器模块,其目标是执行计算(在此阶段,仅求和)。

Let’s keep moving!

让我们继续前进!

创建计算器模块 (Creating the calculator module)

The app module is created automatically, so we will ignore that part.

app模块是自动创建的,因此我们将忽略该部分。

The calculator module will perform sums. Also, we will create a very basic UI, where the user can put two numbers and obtain the result of adding them.

计算器模块将执行求和。 同样,我们将创建一个非常基本的UI,用户可以在其中放置两个数字并获得将它们相加的结果。

So, let’s start:

因此,让我们开始:

  • We create 1 Activity, for the user input.

    我们为用户输入创建1 Activity

  • We create 1 Use Case, responsible for adding two numbers.

    我们创建1个用例,负责将两个数字相加。

Let’s clarify that, just for simplifying the development, we’re not going to use a presentation pattern to create layers. Just 1 Activity that uses 1 Use Case. Later we will see that the presentation pattern is less important than understanding what we’re trying to solve.

让我们澄清一下,为了简化开发,我们将不使用表示模式来创建图层。 仅有1个活动使用1个用例。 稍后,我们将看到演示模式比理解我们要解决的问题重要。

The Use Case and the Activity will look like this:

用例和活动将如下所示:

CalculatorActivity (calculator module)
CalculatorActivity(计算器模块)
SumUseCase (calculator module)
SumUseCase(计算器模块)

There are some things that I will not show in this article, but that are done in the code:

有一些事情我不会在本文中显示,但是在代码中已经完成:

  • I added some behaviour to the UI and admit some interaction by the user.

    我向UI添加了一些行为,并允许用户进行一些交互。
  • I added communication between the Activity and the Use Case.

    我在活动和用例之间添加了交流。

  • I added a dependency on the app module, the calculator module, so we can use the Activity from the MainActivity (later we will see the last one).

    我在app模块(计算器模块)上添加了一个依赖项,因此我们可以使用MainActivity中Activity (稍后将看到最后一个)。

  • I added a simple button on MainActivity, and by tapping it the CalculatorActivity (later we will see this one too) will be launched.

    我在MainActivity上添加了一个简单的按钮,然后点击它,将会启动CalculatorActivity (以后也会看到这一点)。

This code is not in this article because I assume that you know how to create an Activity, an OnClickListener and how to launch some Activity. These are not things that I want to mention in this article.

该代码不在本文中,因为我假设您知道如何创建一个Activity ,一个OnClickListener以及如何启动一些Activity 。 这些不是我想在本文中提及的内容。

The changes so far can be seen in this commit.

到目前为止的更改可以在此commit中看到。

在计算器模块中添加Dagger2 (Adding Dagger2 in calculator module)

Adding Dagger dependencies:

添加Dagger依赖项:

build.gradle (calculator module)
build.gradle(计算器模块)

Let’s begin with the injection. Google says that:

让我们从注射开始。 Google说:

Create Dagger components in modules if you need to perform field injection in that module or you need to scope objects for a specific flow of your application.

如果需要在该模块中执行字段注入,或者需要为应用程序的特定流程确定对象范围,请在模块中创建Dagger组件。

Well, we need an Activity for UI and user inputs, and we need that Activity to be injected by a Component, so we will create a Component and a Module:

好吧,我们需要一个用于UI和用户输入的Activity ,并且我们需要将该Activity由Component注入,因此我们将创建一个Component 和一个 Module

CalculatorComponent (calculator module)
CalculatorComponent(计算器模块)
CalculatorModule (calculator module)
CalculatorModule(计算器模块)

Then, we use Dagger to inject the Use Case into the Activity:

然后,我们使用Dagger将用例注入到Activity中

CalculatorActivity (calculator module)
CalculatorActivity(计算器模块)

And that’s it! We added Dagger in calculator module to inject our dependencies.

就是这样! 我们在计算器模块中添加了Dagger以注入依赖关系。

The changes so far can be seen in this commit.

到目前为止的更改可以在此commit中看到。

向用例添加验证 (Adding validations to the Use Case)

Taking the Use case requirement, it says that to sum two numbers, they both must be positive, greater than zero. If that condition is not fulfilled, we must show an error telling the user this mistake.

按照用例要求,它说要对两个数字求和,两个数字都必须为正,大于零。 如果不满足该条件,我们必须显示一个错误,告诉用户该错误。

For the success or failure result of the Use Case, I will use a Sealed Class. I will not dive deep on this topic, because is not the goal of the article. The code, now, will look like this:

对于用例的成功或失败结果,我将使用Sealed Class 。 我不会深入探讨这个主题,因为这不是本文的目标。 现在,代码将如下所示:

SumUseCase (calculator module)
SumUseCase(计算器模块)

In the Activity:

活动中

  • If I receive Success, I show the result on the UI.

    如果收到Success ,则在UI上显示结果。

  • If I receive Failure, I show the message associated with that failure.

    如果收到失败消息,则显示与该失败相关的消息。

Again, I will not show that handling, you can see it in the code by yourself ;)

同样,我不会显示该处理,您可以自己在代码中看到它;)

The changes so far can be seen in this commit.

到目前为止的更改可以在此commit中看到。

硬编码字符串不是一个好习惯 (Hardcoded Strings aren’t a good practice)

We get our first problem!

我们遇到了第一个问题!

The string that I’m returning to show the data validation is hardcoded, but we know that it’s not a good practice, and that it should be at strings.xml.

我要返回以显示数据验证的字符串是硬编码的,但是我们知道这不是一个好习惯,应该在strings.xml上

To access resources in strings.xml we need a Context, but I don’t allow the Use Case to have a dependency with Context.

要访问strings.xml中的资源,我们需要一个Context ,但是我不允许用例与Context具有依赖关系。

To solve this, I’m creating a StringsProvider, that can retrieve a string by giving it an ID:

为了解决这个问题,我创建了一个StringsProvider ,可以通过给它一个ID来检索一个字符串

StringsProvider (calculator module)
StringsProvider(计算器模块)

Why this class receives the Application, instead of a Context? Well, because with this implementation I can use the StringsProvider like a Singleton, and I erase the possibility of this Component to be Activity or Fragment dependent.

为什么此类接收Application而不是Context ? 好吧,因为有了这种实现,我可以像使用Singleton一样使用StringsProvider ,并且消除了此Component依赖于ActivityFragment的可能性。

Now, let’s add the StringsProvider dependency in the Use Case:

现在,让我们在用例中添加StringsProvider依赖项:

SumUseCase (calculator module)
SumUseCase(计算器模块)

Of course, to build the project I need to modify the Module that provides the Use Case. No problem! With Dagger is easy:

当然,要构建项目,我需要修改提供用例的Module 。 没问题! 使用Dagger很容易:

  • First, I add a constructor for CalculatorModule, where it receives the Application object.

    首先,我为CalculatorModule添加一个构造函数,该构造函数接收Application对象。

  • Then, I add two @Provides methods to the Module, one to provide the Application and another one to provide StringsProvider.

    然后,我向Module添加两个@Provides方法,一个方法提供Application ,另一个方法提供StringsProvider

  • I modify the SumUseCase @Provides method, because now it receives the StringsProviders.

    我修改了SumUseCase @Provides方法,因为现在它接收到StringsProviders。

  • Finally, I have to modify the way I’m obtaining the CalculatorComponent, because now I need to tell Dagger how to construct CalculatorModule.

    最后,我必须修改获取CalculatorComponent的方式 ,因为现在我需要告诉Dagger如何构造CalculatorModule

After the changes, the code will look like this:

更改后,代码将如下所示:

CalculatorModule (calculator module)
CalculatorModule(计算器模块)
CalculatorActivity (calculator module)
CalculatorActivity(计算器模块)

The changes so far can be seen in this commit.

到目前为止的更改可以在此commit中看到。

为什么不使用核心模块? (Why not a core module?)

Thinking about the StringsProvider, it can be useful for other modules too, not only for the calculator one. String usage is one of the most common use cases in the Android world, right?

考虑到StringsProvider ,它对其他模块也很有用,不仅对计算器一个有用。 字符串用法是Android世界中最常见的用例之一,对吗?

So, can we create a core module and place StringsProvider inside it? Thus, we avoid each module to have its StringsProvider, solving several problems:

那么,我们可以创建一个核心模块并将StringsProvider放在其中吗? 因此,我们避免每个模块都有其StringsProvider ,从而解决了几个问题:

  • There will not be a Module for each module of our application that duplicates the StringsProvider provisioning.

    不会有一个Module为复制的StringsProvider配置我们的应用程序的每个模块。

  • The access to strings.xml will be centralized. Now, we aren’t going to have StringsProviders in the entire application, and maybe we are solving a Context related bug (Activities or Fragments coupling, for example).

    strings.xml的访问将集中进行。 现在,我们将不在整个应用程序中使用StringsProviders ,也许我们正在解决一个与上下文相关的错误(例如, ActivitiesFragments耦合)。

Let’s create the core module and move the StringsProvider there (so, we’re removing it from calculator module).

让我们创建核心模块并将StringsProvider移到那里(因此,我们正在将其从计算器模块中删除)。

This brings us a new problem: the calculator module cannot get StringsProvider reference anymore, so Dagger injection will fail.

这给我们带来了一个新问题:计算器模块无法再获取StringsProvider引用,因此Dagger注入将失败。

How dos we solve this issue? Well, following Google recommendations:

我们如何解决这个问题? 好吧,遵循Google的建议:

For Gradle modules that are meant to be utilities or helpers and don’t need to build a graph (that’s why you’d need a Dagger component), create and expose public Dagger modules with @Provides and @Binds methods of those classes that don’t support constructor injection.

对于本打算用作实用程序或帮助程序且无需构建图形的Gradle模块(这就是为什么需要Dagger组件的原因),请使用不提供此类的@Provides和@Binds方法创建并公开公共Dagger模块不支持构造函数注入。

So, we only need the core module to provides us with the StringsProvider service, so we just create a Module, no Component here.

因此,我们只需要核心模块为我们提供StringsProvider服务,因此我们只创建一个Module ,而没有Component

CoreModule (core module)
CoreModule(核心模块)

Note: we must add Dagger to the core module, like we previously did with the other modules.

注意:我们必须像以前使用其他模块一样,将Dagger添加到核心模块。

Now, to obtain StringsProvider, we have to use CoreModule inside CalculatorComponent. So, we keep moving:

现在,要获取StringsProvider ,我们必须在CalculatorComponent内部使用CoreModule 。 因此,我们继续前进:

  • Add a core module dependency on calculator module.

    在计算器模块上添加核心模块依赖性。
  • Add CoreModule in CalculatorComponent.

    CalculatorComponent中添加CoreModule

  • Modify the CalculatorModule, so it can be properly integrated with CoreModule.

    修改CalculatorModule ,以便可以与CoreModule正确集成。

  • Modify the DaggerCalculatorComponent instantiation.

    修改DaggerCalculatorComponent实例化。

CalculatorModule (calculator module)
CalculatorModule(计算器模块)
CalculatorComponent (calculator module)
CalculatorComponent(计算器模块)
CalculatorActivity (calculator module)
CalculatorActivity(计算器模块)

Excellent! We have our first multi-module Dagger integration.

优秀的! 我们有第一个多模块Dagger集成。

The changes so far can be seen in this commit.

到目前为止的更改可以在此commit中看到。

附加要求:用户订阅 (Additional requirement: User Subscriptions)

Note here: I made a typo with Subscriptions word in all the code I will show (I used Suscriptions instead). Sorry for the inconvenience.

请注意:在我将显示的所有代码中,我都用Subscriptions单词打错了字(我改用Suscriptions)。 抱歉给你带来不便。

The unique thing we are sure about is that, soon, the users will have the possibility to buy an app’s subscription and have some premium features.

我们确定的独特之处在于,不久之后,用户将有可能购买应用程序的订阅并具有一些高级功能。

We don’t have too much information, so we don’t want to start a new module for that feature. Remember: we must always try to not make the solution more complex.

我们没有太多的信息,因此我们不想为此功能启动一个新模块。 请记住:我们必须始终不使解决方案变得更加复杂。

So, we can use the core module, right? We’re going to use it and make a very basic implementation of subscriptions.

所以,我们可以使用核心模块,对吗? 我们将使用它并进行非常基本的订阅实现。

Let’s create an AppSuscription class, that can tell us which the user subscription is. Then, we will use AppSuscription class in MainActivity (app module), just for showing the current user subscription on the UI.

让我们创建一个AppSuscription类,它可以告诉我们哪个用户订阅。 然后,我们将在MainActivity (应用程序模块)中使用AppSuscription类,仅用于在UI上显示当前用户订阅。

As we know, I repeat myself, in the future we will have a subscription module to handle subscription stuff. So, for sure, we will have a SuscriptionModule, right? Well, let’s create it. Wait… if this AppSuscription is in the core module, why can’t we use CoreModule? There is a valid reason: if now we couple the subscription stuff to CoreModule, later it will be more difficult to decouple from it (maybe, we will break some CoreModule dependencies).

众所周知,我再说一遍,将来我们将有一个订阅模块来处理订阅内容。 因此,可以肯定的是,我们将拥有SuscriptionModule ,对吗? 好吧,让我们创建它。 等待…如果此AppSuscription位于核心模块中,为什么我们不能使用CoreModule ? 有一个合理的理由:如果现在将订阅内容耦合到CoreModule ,以后将很难与之分离(也许,我们将打破一些CoreModule依赖关系)。

That said, let’s create the SuscriptionModule:

就是说,让我们创建SuscriptionModule

SuscriptionModule (core module)
SuscriptionModule(核心模块)

Once we created the Module, we are going to use AppSuscription. As I said, we're going to use it on MainActivity, so we have to:

创建Module ,我们将使用AppSuscription 。 正如我所说,我们将在MainActivity上使用它,因此我们必须:

  • Add Dagger dependencies on app module.

    在应用程序模块上添加Dagger依赖项。
  • Add the core module dependency on app module.

    在应用程序模块上添加核心模块依赖项。

As I said before too, I’m not showing this stuff.

就像我之前说过的,我没有展示这些东西。

Once those dependencies are configured, we have to create a Component to perform inject services on MainActivity. This Component will use SuscriptionModule, so we can inject AppSuscription:

配置完这些依赖​​项后,我们必须创建一个Component来在MainActivity上执行注入服务。 该Component将使用SuscriptionModule ,因此我们可以注入AppSuscription:

ApplicationComponent (app module)
ApplicationComponent(应用程序模块)

Once the Component is created, we can inject AppSuscription on MainActivity:

创建Component ,我们可以在MainActivity上注入AppSuscription

MainActivity (app module)
MainActivity(应用程序模块)

Done! Let’s build to test it… and:

做完了! 让我们来对其进行测试……并:

What? If I’m only using AppSuscription, why I need StringsProvider? Ok… let’s do some adjusts get a successful compilation. We add CoreModule, who provides StringsProvider:

什么? 如果仅使用AppSuscription ,为什么需要StringsProvider ? 好吧...让我们进行一些调整以使编译成功。 我们添加CoreModule ,后者提供StringsProvider

ApplicationComponent (app module)
ApplicationComponent(应用程序模块)

Now, because we added CoreModule to ApplicationComponent, we must give Dagger a CoreModule instance to instantiate ApplicationComponent, so we do:

现在,因为我们向ApplicationComponent添加了CoreModule ,所以我们必须给Dagger一个CoreModule实例以实例化ApplicationComponent ,所以我们这样做:

MainActivity (app module)
MainActivity(应用程序模块)

Now it works perfectly!

现在,它可以完美运行了!

The changes so far can be seen in this commit.

到目前为止的更改可以在此commit中看到。

好吧,别再忽略气味了 (Well, stop ignoring the smells)

The first stage of our project is finished. The app can sum two numbers, show error messages and display the user subscription.

我们项目的第一阶段已经完成。 该应用程序可以对两个数字求和,显示错误消息并显示用户订阅。

However, there are some smells in the code we wrote. Maybe some of you have already noticed them, but in case you haven’t, there are two big issues here.

但是,我们编写的代码中有些气味。 也许有些人已经注意到了它们,但是如果您没有注意到它们,这里有两个大问题。

第一期,所有应用程序Modules知识 (First issue, knowledge of all the application Modules)

The first problem comes with the solution we did just a moment ago. The AppSuscription service couldn’t be injected using by only using SuscriptionModule, because it needed CoreModule too (StringsProvider injection).

第一个问题来自我们刚才所做的解决方案。 仅通过使用SuscriptionModule不能注入AppSuscription服务,因为它也需要CoreModule ( StringsProvider注入)。

In this case, we solved the issue quickly, but that was only because I was the only developer who codes, and I know which Modules provides the services Dagger needs to compile. What if:

在这种情况下,我们Swift解决了这个问题,但这仅仅是因为我是唯一编码的开发人员,而且我知道哪个Modules 提供Dagger需要编译的服务。 如果:

  • I’m working with a team: the team probably don’t know that CoreModule provides StringsProvider. Because of that, the will maybe create a new @Provides method in some existing Module (or, even worst, they will create a new Module). This actions will cause different modules to have different StringsProviders, and that will, possibly, bring us compilation problems and, surely, bring us maintenance problems.

    我正在与一个团队合作:该团队可能不知道CoreModule提供了StringsProvider 。 因此,可能会在某些现有Module创建一个新的@Provides方法(或更糟糕的是,他们将创建一个新Module )。 此操作将导致不同的模块具有不同的StringsProviders ,这可能会给我们带来编译问题,并且肯定会给我们带来维护问题。

  • My application begins to have more modules: if my application begins to grow, new modules will emerge. In all those modules my team and I will start doing hacks to solve compilation problems. That’s not even a possibility.

    我的应用程序开始具有更多的模块:如果我的应用程序开始增长,则将出现新的模块。 在所有这些模块中,我的团队和我将开始进行破解以解决编译问题。 那甚至是不可能的。

第二期,整个应用程序中的重复Modules (Second issue, duplicated Modules across the application)

Maybe some of you noticed that there is a duplication of Modules in the code.

也许有些人注意到代码中有重复的Modules

  • For DaggerCalculatorComponent usage, we need to instantiate CoreModule.

    对于DaggerCalculatorComponent的用法,我们需要实例化CoreModule

  • For DaggerApplicationComponent usage, we also need to instantiate CoreModule.

    对于DaggerApplicationComponent的用法,我们还需要实例化CoreModule

DaggerCalculatorComponent (calculator module)
DaggerCalculatorComponent(计算器模块)
DaggerApplicationComponent (app module)
DaggerApplicationComponent(应用模块)

Those instantiations are against our proposal of StringsProvider usage: a centralized management for strings. With this approach, each module has its StringsProvider.

这些实例违背了我们对StringsProvider用法的建议:对字符串的集中管理 通过这种方法,每个模块都有其StringsProvider

In the case of StringsProvider, this maybe isn’t a big problem. But let’s think about a Module that provides services whose responsibilities are handling user session data. Do we want to duplicate (at least) that Module across the entire application?

对于StringsProvider ,这可能不是一个大问题。 但是,让我们考虑一个提供服务的Module ,该Module的职责是处理用户会话数据。 我们是否要在整个应用程序中复制(至少)该Module

我们必须解决这些问题 (We must solve these issues)

We must solve both problems. Some possible solutions are:

我们必须解决这两个问题。 一些可能的解决方案是:

First possible solution

第一个可能的解决方案

The easiest way to solve this is: let it be. It’s always an alternative to let the things work in this way, we can procrastinate and don’t take over these problems. In my case, and I hope that yours is the same case, we’ll not consider this solution.

解决此问题的最简单方法是:顺其自然。 让事物以这种方式工作始终是一种替代选择,我们可以拖延而不承担这些问题。 就我而言,希望您的情况相同,我们不会考虑这种解决方案。

Second possible solution

第二种可能的解决方案

Maybe we can have a centralized storage where we put different Modules, and then we get them when needed. This solution has, at least, three problems:

也许我们可以有一个集中的存储 ,在其中放置不同的Modules ,然后在需要时获取它们。 此解决方案至少具有三个问题:

  • It doesn’t solve the issue with Modules inter-dependencies that we saw before (@Provide methods in other Modules implicitly needed).

    它不能解决我们之前看到的Modules相互依赖的问题(其他Modules @Provide方法 隐式需要)。

  • When we create and store a Module? How do we tell the core module that it has to create and store its Modules in the centralized storage?

    当我们创建和存储Module ? 我们如何告诉核心模块必须创建其Modules并将其存储集中式存储中

  • Some Modules are not easy to instantiate. For example, CoreModule needs an Application, and the core module doesn't have easy access to that object.

    一些Modules不容易实例化。 例如, CoreModule需要一个Application ,而核心模块无法轻松访问该对象。

Third possible solution

第三种可能的解决方案

Maybe… we can create a Module with @Provides methods that provide… other Components ?

也许……我们可以使用提供……其他Components @Provides方法创建一个Module

Well, first, I don’t even know if that’s possible. Second, it would be too much tricky and a very nasty hack that, I daresay, forces Dagger to work in a way it’s not prepared.

好吧,首先,我什至不知道这是否可能。 第二,我敢说,这太麻烦了,而且非常讨厌,迫使Dagger以一种尚未准备好的方式工作。

So, that’s not a possibility, but… maybe we can create a Module with @Provides methods that provide other Modules? Honestly, I didn't try it. I think that it would have some issues with Scoping and dependencies between Components, so that's a good reason not to try this.

所以,这不是一个可能性,但...也许我们可以创建一个Module@Provides方法,以便为其他Modules ? 老实说,我没有尝试过。 我认为这将对ScopingComponents之间的依赖项产生一些问题,因此,这是一个很好的理由,不要尝试这样做。

那么我们该怎么办? (So, what do we do?)

Let’s face the problem as it was presented: by parts. Using this technique, it will be easier to solve them. First, we will seek for the Modules inter-dependency issue.

让我们面对提出的问题:分部分进行。 使用此技术,将更容易解决它们。 首先,我们将寻找Modules 相互依赖的问题。

解决Modules相互依赖性 (Solving Modules inter-dependency)

As I mentioned before, we don’t want to know which is the exact dependency of a Modules to use it, at least not so explicitly (with errors like @Provides missing).

如前所述,我们不想知道使用Modules的确切依赖关系,至少不那么明确(缺少@Provides之类的错误)。

The @Module annotation has an attribute that indicates which Modules are used to satisfy the service dependencies of the @Module that we're taking into account. This attribute is the includes attribute.

@Module 批注具有一个属性,该属性指示使用哪些Modules来满足@Module的服务依赖性 我们正在考虑在内。 此属性是include属性。

With this attribute, we can, in our application:

有了这个属性,我们可以在我们的应用程序中:

  • Add the CoreModule dependency to SuscriptionModule, so it can provide its services.

    CoreModule依赖项添加到SuscriptionModule ,以便它可以提供其服务。

  • Remove the CoreModule usage in the ApplicationComponent.

    ApplicationComponent中删除CoreModule的用法。

SuscriptionModule (core module)
SuscriptionModule(核心模块)
ApplicationComponent (app module)
ApplicationComponent(应用程序模块)

Be careful! There’s something that doesn’t change: Dagger still asks us which is the CoreModule dependency that it has to use.

小心! 有些事情没有改变: Dagger仍然问我们它必须使用哪个CoreModule依赖项。

DaggerApplicationComponent (app module)
DaggerApplicationComponent(应用模块)

So, we don’t solve the entire issue. But, unlike the previous situation, we can see that SuscriptionModule uses CoreModule by looking at its implementation.

因此,我们无法解决整个问题。 但是,与以前的情况不同,我们可以通过查看SuscriptionModule的实现来看到它使用CoreModule

And, as a bonus, we have a prettier error :D. Before, the error was that we we were forgetting to provide StringsProvider, so… WTF? What is StringsProvider?. Now, with this approach, we see the next Runtime error:

而且,作为奖励,我们有一个漂亮的错误:D。 以前,错误是我们忘记提供StringsProvider ,所以……WTF? 什么是StringsProvider ? 现在,使用这种方法,我们将看到下一个运行时错误:

Much better, right?

好多了吧?

The changes so far can be seen in this commit.

到目前为止的更改可以在此commit中看到。

足够的Modules重复 (Enough Modules duplication)

I have to admit that the solution that I will present is not a perfect one, but, as we know, perfect is the enemy of possible ;).

我必须承认,我将提出的解决方案不是完美的解决方案,但是,正如我们所知,完美是可能的敌人;)。

All the solutions that I found while I was searching and reading some documentation implicate a coupling between all the modules that has Components and the Application object. We will see that in a moment.

我在搜索和阅读一些文档时发现的所有解决方案都暗示了具有Components应用程序对象的所有模块之间的耦合。 我们稍后会看到。

We’re going to use the Google’s approach.

我们将使用Google的方法

That said, let’s add a new cons: the solution we’re going to use has an implicit trouble. Imagine that several teams are working on the app, and that each one is working on a different module. With the existing coupling in the solution that I’m going to show, maybe we would have some troubles with intra-teams dependencies, and in the worst scenario, the teams can become stoppers.

就是说,让我们添加一个新的缺点:我们将要使用的解决方案有一个隐患。 想象一下,有几个团队正在使用该应用程序,而每个团队都在一个不同的模块上工作。 使用我将要展示的解决方案中的现有耦合,也许我们会遇到团队内部依赖的麻烦,并且在最坏的情况下,团队可能成为阻碍者。

There is a possible and partial solution to this problem, we will see it later.

这个问题可能有部分解决方案,我们将在以后看到。

Enough theory and explanations, let’s code!

足够的理论和解释,让我们编写代码!

Each module that has a Component, in other words, that will inject services on some client, will provide an interface with a method that returns the Component it needs.

每个具有Component模块(换句话说,将在某些客户端上注入服务)将为接口提供一种方法,该方法返回所需的Component

For example, in the calculator module, our Component is CalculatorComponent, so we're going to create the CalculatorComponentProvider interface:

例如,在计算器模块中,我们的ComponentCalculatorComponent ,因此我们将创建CalculatorComponentProvider接口:

CalculatorComponentProvider (calculator module)
CalculatorComponentProvider(计算器模块)

We’re going to use that interface to obtain the CalculatorComponent. Who will implement CalculatorComponentProvider? The only object that we can access from an Activity/Fragment, and that’s the same across our application: Application.

我们将使用该接口来获取CalculatorComponent 。 谁将实现CalculatorComponentProvider ? 我们可以从Activity / Fragment中访问的唯一对象,并且在我们的应用程序中是相同的: Application

Which Application? The one that I’m creating now! (yes, it didn’t exist until now).

哪个应用程序 ? 我现在正在创建的那个! (是的,直到现在还不存在)。

CustomApplication (app module)
CustomApplication(应用程序模块)

With this approach, we can obtain the CalculatorComponent using the previously created interface:

通过这种方法,我们可以使用先前创建的接口来获取CalculatorComponent

CalculatorActivity (calculator module)
CalculatorActivity(计算器模块)

Let’s do the same thing for MainActivity. We create an ApplicationComponentProvider, then we force Application to implement that interface, and finally we obtain ApplicationComponent on MainActivity:

让我们对MainActivity做同样的事情。 我们创建一个ApplicationComponentProvider ,然后强制Application实现该接口,最后在MainActivity上获得ApplicationComponent

ApplicationComponentProvider (app module)
ApplicationComponentProvider(应用程序模块)
CustomApplication (app module)
CustomApplication(应用程序模块)
MainActivity (app module)
MainActivity(应用程序模块)

Now, we have a centralized Components creation, for each module in my application. There is one thing that's missing: the CoreModule remains to be instantiated wherever it's needed, and it's not what we want. We want it to be a single instance across the application.

现在,我们为应用程序中的每个模块创建了一个集中式Components 。 缺少一件事: CoreModule仍然需要在任何需要的地方实例化,而这并不是我们想要的。 我们希望它成为整个应用程序中的单个实例。

There are a lot of approaches to solve this. In this case, the article’s case, I will choose the easiest way, and I will store the CoreModule in CustomApplication. Anyway, I strongly recommend you to choose another solution, because this one doesn’t scale at all. You could use a Map and store Modules there, or you could create a new class that takes care of that storing responsibility, or… there are infinite choices.

有很多方法可以解决此问题。 在这种情况下,就本文的情况而言,我将选择最简单的方法,并将CoreModule存储在CustomApplication中 。 无论如何,我强烈建议您选择另一种解决方案,因为该解决方案根本无法扩展。 您可以使用Map并在其中存储Modules ,也可以创建一个负责存储责任的新类,或者…有无限的选择。

The code will finally look like this:

代码最终将如下所示:

CustomApplication (app module)
CustomApplication(应用程序模块)

The changes so far can be seen in this commit.

到目前为止的更改可以在此commit中看到。

Bonus: before, I mentioned that it was a possible and partial solution to the inter-teams dependencies, so I’m going to tell it, is very simple

奖励:之前,我提到这是解决团队间依赖关系的一种可能的方法 ,所以我要说的是,它非常简单

Each team could have their module separated in a self project, with an app module, like we have here. This app module could have a custom Application class, and that class could implement our ComponentProvider interface, returning the necessary Component. So, the teams don't depend on other team's work.

每个团队都可以将自己的模块与一个应用程序模块隔离在一个自我项目中,就像我们在这里所看到的那样。 这个应用程序模块可以有一个自定义的Application类,该类可以实现我们的ComponentProvider接口,并返回必要的Component 。 因此,团队不依赖其他团队的工作。

总结:我们如何解决问题? (Summarizing: how did we solve the issues?)

  • The Modules dependencies with other Modules will be indicated using @Module(includes = [...]).

    使用@Module(includes = [...])表示与其他ModulesModules依赖性。

  • Each module that has a Component, also will have a ComponentProvider interface, with a method that returns that Component. That interface must be implemented by our custom Application.

    每个具有Component模块,也将具有ComponentProvider接口,以及返回该Component的方法。 该接口必须由我们的自定义Application实现

  • If a single module has more than one Component, we can use the same ComponentProvider interface to provide them all. The only thing that we must make is… changing the interface name :P.

    如果一个模块具有多个Component ,我们可以使用相同的ComponentProvider接口来提供它们全部。 我们唯一要做的就是...更改接口名称:P。

  • In each, for example, Activity where we want to use a Component, we just need to cast the Application object to the ComponentProvider interface, and obtain the Component that we need.

    例如,在每个要使用Component Activity中,我们只需要将Application对象强制转换为ComponentProvider接口,并获得所需的Component

An important observation: Components with different Scopes

重要观察: 具有不同 Scopes Components

In the vast majority of Dagger implementations, we can see Components with different Scopes.

在绝大多数Dagger实现中,我们可以看到具有不同Scopes Components

For example, we may have an Activity Scoped Component, and the Module that contributes to that Component maybe has a constructor that receives a reference to the Activity that's scoped. In this case, we can't use the ComponentProvider interface to get the Component, because the Application doesn't have any reference to an Activity.

例如,我们可能有一个Activity Scoped Component ,而为该Component做出贡献的Module可能有一个构造函数,该构造函数接收对作用域的Activity的引用。 在这种情况下,我们不能使用ComponentProvider接口来获取Component ,因为Application没有任何对Activity的引用。

In these cases, we should have Father Component, that can be created from Application. The Activity Scoped Component must have the Father Component as its, well, father (using Subcomponents, for example).

在这些情况下,我们应该具有 Component ,可以从Application创建它。 活动 Scoped Component必须以 Component作为 Component (例如,使用Subcomponents )。

So, we can get the Father Component with a ComponentProvider, and with that Father Component, obtain the Activity Scoped Component.

因此,我们可以得到 ComponentComponentProvider,并与父亲 Component ,获得活动 Scoped Component

Well, after a long journey, we achieve our goal: solve the most common problems that exists between Dagger and Multi-Module Applications in Android.

好了,经过漫长的旅程,我们实现了我们的目标:解决Android中的Dagger和多模块应用程序之间存在的最常见问题。

I hope that, soon, a more optimal approach to solve the problems that we talked about will emerge. For now, I hope you enjoyed this journey and that you have learned some new stuff or, at least, open your mind a bit more.

我希望不久将出现一种解决我们所讨论问题的更优化方法。 就目前而言,我希望您喜欢这个旅程,并希望您学到了一些新知识,或者至少可以多一点思路。

If you like the article, don’t forget to share!

如果您喜欢这篇文章,别忘了分享!

Cheers, and thank you!

干杯,谢谢!

翻译自: https://proandroiddev.com/android-multi-module-dagger-a-real-use-case-step-by-step-bbc03500f2f9

dagger module

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值