了解Dagger 2中的范围

时间:2017-01-24 09:42:16

标签: android dependency-injection dagger-2

我在Dagger 2中遇到与范围相关的错误,我试图了解如何解决它。

我有一个显示公司的CompaniesActivity。当用户选择项目时,所选公司的员工将显示在EmployeesActivity中。当用户选择员工时,她的详细信息显示在EmployeeDetailActivity

class Company {
    List<Employee> employees;
}

班级CompaniesViewModel包含公司和所选公司(或null):

class CompaniesViewModel {
    List<Company> companies;
    Company selected;
}

CompaniesActivity引用CompaniesViewModel

class CompaniesActivity extends Activity {

    @Inject
    CompaniesViewModel viewModel;

    @Override
    protected void onCreate(Bundle b) {
        //more stuff
        getComponent().inject(this);
        showCompanies(viewModel.companies);
    }

    //more stuff

    private onCompanySelected(Company company) {
        viewModel.selected = company;
        startActivity(new Intent(this, EmployeesActivity.class));
    }

}

班级EmployeesViewModel包含员工和所选员工(或null):

class EmployeesViewModel {
    List<Employee> employees;
    Employee selected;
}

EmployeesActivity引用EmployeesViewModel

  class EmployeesActivity extends Activity {

        @Inject
        EmployeesViewModel viewModel;

        @Override
        protected void onCreate(Bundle b) {
            //more stuff
            getComponent().inject(this);
            showEmployees(viewModel.employees);
        }

        //more stuff

        private onEmployeeSelected(Employee emp) {
            viewModel.selected = emp;
            startActivity(new Intent(this, EmployeeDetailActivity.class));
        }

    }

最后,在EmployeeDetailActivity中,我从视图模型中选择了Employee并显示了她的详细信息:

  class EmployeeDetailActivity extends Activity {

        @Inject
        EmployeesViewModel viewModel;

        @Override
        protected void onCreate(Bundle b) {
            //more stuff
            getComponent().inject(this);
            showEmployeeDetail(viewModel.selected); // NullPointerException
        }
    }

我得到NullPointerException,因为EmployeesViewModel中的EmployeesActivity个实例与EmployeeDetailActivity不同,而在第二个中,viewModel.selected是{{1} }}

这是我的匕首模块:

null

请注意@Module class MainModule { @Provides @Singleton public CompaniesViewModel providesCompaniesViewModel() { CompaniesViewModel cvm = new CompaniesViewModel(); cvm.companies = getCompanies(); return cvm; } @Provides public EmployeesViewModel providesEmployeesViewModel(CompaniesViewModel cvm) { EmployeesViewModel evm = new EmployeesViewModel(); evm.employees = cvm.selected.employees; return evm; } } 是单身(CompaniesViewModel),但@Singleton不是,因为每次用户选择公司时都必须重新创建(员工列表将包含其他项目)。< / p>

每次用户选择公司时,我都可以EmployeesViewModel公司的员工set,而不是创建新的实例。但我希望EmployeesViewModel是不可变的。

我该如何解决这个问题?任何建议将不胜感激。

3 个答案:

答案 0 :(得分:9)

不幸的是,我认为你在这种情况下滥用DI框架,而你遇到的问题是&#34;代码闻起来&#34; - 这些问题暗示你做错了什么。

应使用DI框架将关键依赖项(协作者对象)注入顶级组件,执行这些注入的逻辑应完全独立于应用程序的业务逻辑。

从第一眼看,一切都很好 - 你使用Dagger将CompaniesViewModelEmployeesViewModel注入Activity。这可能没问题(虽然我不会这样做)如果这些是真实的&#34;对象&#34;。但是,在您的情况下,这些是&#34;数据结构&#34; (因此你希望它们是不可变的)。

对象和数据结构之间的区别并非易事,但非常重要。 This blog post很好地总结了它。

现在,如果您尝试使用DI框架注入数据结构,最终将框架转换为数据提供程序&#34;应用程序,因此将部分业务功能委托给它。例如:看起来EmployeesViewModel独立于CompaniesViewModel,但它是一个&#34;谎言&#34; - @Provides方法中的代码在逻辑上将它们联系在一起,因此&#34;隐藏&#34;依赖。好的&#34;经验法则&#34;在这种情况下,如果DI代码依赖于注入对象的实现细节(例如调用方法,访问字段等) - 它通常表示关注点分离不足。

两项具体建议:

  1. 不要将业务逻辑与DI逻辑混合在一起。在您的情况下 - 不要注入数据结构,但注入提供数据访问权限(坏)的对象,或者在抽象数据时更好地公开所需的功能(更好)。
  2. 我认为您在多个屏幕之间共享视图模型的尝试并不是一个非常强大的设计。最好为每个屏幕设置一个单独的View-Model实例。如果你需要&#34;分享&#34;屏幕之间的状态,然后,根据具体要求,你可以这样做1)意图额外2)全局对象3)共享首选4)SQLite

答案 1 :(得分:4)

根据这篇关于自定义范围的文章:

http://frogermcs.github.io/dependency-injection-with-dagger-2-custom-scopes/

短距离为我们提供“本地单身人士”,其范围与范围本身一样长。

为了清楚起见 - Dagger 2中默认没有提供@ActivityScope或@ApplicationScope注释。这只是自定义范围的最常见用法。默认情况下只有@Singleton范围可用(由Java本身提供),并且使用范围是不够的(!),您必须处理包含该范围的组件。这意味着在Application类中保留对它的引用,并在Activity更改时重用它。

public class GithubClientApplication extends Application {

    private AppComponent appComponent;
    private UserComponent userComponent;

    //...

    public UserComponent createUserComponent(User user) {
        userComponent = appComponent.plus(new UserModule(user));
        return userComponent;
    }

    public void releaseUserComponent() {
        userComponent = null;
    }

    //...
}

您可以查看此示例项目:

http://github.com/mmirhoseini/marvel

和本文:

https://hackernoon.com/yet-another-mvp-article-part-1-lets-get-to-know-the-project-d3fd553b3e21

更加熟悉MVP并了解匕首的作用范围。

答案 2 :(得分:1)

这里有几个问题,只与Dagger 2范围有关。

首先,你使用了术语&#34; ViewModel&#34;建议您尝试使用MVVM架构。 MVVM的一个显着特征是层的分离。但是,您的代码未实现模型和视图模型之间的任何分离。

让我们来看看Eric Evans的模型定义:

  

域模型是一个抽象系统,描述知识,影响或活动领域(域)的选定方面。2

在这里,您的知识领域是公司及其员工。查看您的EmployeesViewModel,它至少包含一个可能在模型层中更好地隔离的字段。

class EmployeesViewModel {
    List<Employee> employees; //model layer
    Employee selected;        
}

也许这仅仅是一个不幸的名称选择,但我认为你的目的是创建适当的视图模型,所以对这个问题的任何答案都应该解决这个问题。虽然选择与视图相关联,但该类并不具备视图的抽象的条件。真实的视图模型可能会以某种方式匹配员工在屏幕上显示的方式。让我们说你有&#34;名字&#34;和&#34;出生日期&#34; TextViews。然后,视图模型将公开为这些TextView提供文本,可见性,颜色等的方法。

其次,您提议的是使用(单例)Dagger 2范围在活动之间进行通信。您希望将CompaniesActivity中选定的公司与EmployeesActivityEmployeesActivity中选择的员工进行沟通,以便与EmployeeDetailActivity进行沟通。 您正在询问如何通过使它们使用相同的共享全局对象来实现此目的。

虽然使用Dagger 2在技术上可能是这样,但Android中用于在Activities之间进行通信的正确方法是使用意图而不是共享对象。 this question的答案是对这一点的非常好的解释。

这是一个建议的解决方案: 目前尚不清楚你在做什么获取 List<Company>。也许你是从数据库中获取的,也许你是从缓存的Web请求获得的。无论是什么,都将其封装在一个对象CompaniesRepository中。同样适用于EmployeesRepository

所以你会有类似的东西:

public abstract class EmployeesRepository {

     List<Employee> getAll();

     Employee get(int id);

     int getId(Employee employee);

}

CompaniesRepository课做类似的事情。这两个检索类可以是单例,并在模块中初始化。

@Module
class MainModule {

    @Provides
    @Singleton
    public CompaniesRepository(Dependency1 dependency1) {
        //TODO: code you need to generate the companies retrieval object
    }

    @Provides
    @Singleton
    public EmployeesRepository(Dependency2 dependency2) {
       //TODO: code you need to generate the employees retrieval object
    }
}

您的EmployeesActivity现在看起来像这样:

class EmployeesActivity extends Activity {

    @Inject CompaniesRepository companiesRepository;
    @Inject EmployeesRepository employeesRepository;       

    @Override
    protected void onCreate(Bundle b) {
        //more stuff
        getComponent().inject(this);
        //retrieve the id of the company selected in the previous activity
        //and use that to get the company model
        int selectedCompanyId = b.getIntExtra(BUNDLE_COMPANY_ID, -1);
        //TODO: handle case where no company id has been passed into the activity
        Company selectedCompany = companiesRepository.get(selectedCompanyId);
        showEmployees(selectedCompany.getEmployees);
    }

    //more stuff

    private onEmployeeSelected(Employee emp) {
        int selectedEmployeeId = employeesRepository.getId(emp);
        Intent employeeDetail = new Intent();
        employeeDetail.putExtra(BUNDLE_EMPLOYEE_ID, selectedEmployeeId);
        startActivity(employeeDetail));
    }
}

将此示例扩展到您的其他两个活动,您将接近Android应用的标准架构,您将使用Dagger 2而不会混合您的模型层,视图层等。