I have an scope-related error in Dagger 2 and I\'m trying to understand how I can solve it.
I have a CompaniesActivity
that shows companies. When the us
There are a couple of issues here and that are only obliquely related to Dagger 2 scopes.
Firstly, the fact that you have used the term "ViewModel" suggests you are trying to use MVVM architecture. One of the salient features of MVVM is separation of layers. However, your code has not achieved any separation between model and view-model.
Let's take a look at this definition of model from Eric Evans:
A domain model is a system of abstractions that describes selected aspects of a sphere of knowledge, influence, or activity (a domain).2
Here your sphere of knowledge is a company and its employees. Looking at your EmployeesViewModel
, it contains at least one field that is probably better isolated in the model layer.
class EmployeesViewModel {
List employees; //model layer
Employee selected;
}
Perhaps it is merely an unfortunate choice of name, but I think your intention is to create proper view-models so any answer to this question should address that. While selection is associated with the view, the class doesn't really qualify as an abstraction of the view. A real view-model would probably somehow match the way the Employee is displayed on the screen. Let's say you have "name" and "date of birth" TextViews. Then a view-model would expose methods that provide the text, visibility, color etc. for those TextViews.
Secondly, what you are proposing is using (singleton) Dagger 2 scopes to communicate between Activities. You want the company selected in the CompaniesActivity
to be communicated to the EmployeesActivity
and the employee selected in EmployeesActivity
to be communicated to the EmployeeDetailActivity
.
You are asking how to achieve this by making them all use the same shared global object.
While this may be technically possible using Dagger 2, the correct approach in Android to communicate between Activities is to use intents, rather than shared objects. The answers to this question are a really good explanation of this point.
Here is a proposed solution:
It's not clear what you are doing to actually get the List
. Maybe you are getting from a db, maybe you are getting from a cached web request. Whatever it is, encapsulate this in an object CompaniesRepository
. Likewise for EmployeesRepository
.
So you will have something like:
public abstract class EmployeesRepository {
List getAll();
Employee get(int id);
int getId(Employee employee);
}
Do something similar for a CompaniesRepository
class. These two retrieval classes can be singletons and be initialised in your module.
@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
}
}
Your EmployeesActivity now looks something like this:
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));
}
}
Extend this example to your other two activities and you will be approaching the standard architecture for an Android app and you will be using Dagger 2 without mixing your model layer, view layer etc.