我正在用Dagger 2创建一个基本的Android应用程序。在遇到this great talk by Jake Wharton之前,我很难理解如何正确使用它。在其中,他演示了使用Dagger 2和“Tweeter”应用程序。在~22:44,他表明应用程序的@Inject
字段可以满足注入方法。他后来展示了一个简单的Android实现。
我的应用程序的ViewModel依赖于存储库类。我正在使用Dagger 2通过Application类将此存储库注入ViewModel,如下所示:
//In my Dagger 2 component
@Singleton
@Component(module = {MyRepositoryModule.class})
public interface MyRepositoryComponent{
void inject(MyViewModel viewModel);
}
//In MyApplication
public class MyApplication extends Application{
private MyRepositoryComponent repoComponent;
//Instantiate the component in onCreate...
public MyRepositoryComponent getMyRepositoryComponent(){
return repoComponent;
}
}
//Finally, in my ViewModel
public MyViewModel extends AndroidViewModel{
@Inject
public MyRepository repo;
public MyViewModel(@NonNull MyApplication app){
repo = app.getMyRepositoryComponent().inject(this);
}
}
我采用这种方法是因为我可以覆盖MyApplication类并使用假组件进行测试(这是我的主要目标之一)。以前,我能够注入依赖关系的唯一方法是在ViewModel中构建我的组件,这使得用fakes替换它是不可能的。
对于像这样的简单应用程序,我知道我可以取消使用inject方法并在MyApplication类中保存对存储库的引用。但是,假设有更多的依赖关系需要担心,这是一种常见/良好/测试友好的方法,为Android中的活动和ViewModel注入依赖项吗?
在EpicPandaForce's answer和一些研究(见this article)的灵感之后,我找到了一个我很满意的解决方案。
我决定从我的项目中删除Dagger 2,因为我过度设计了它。我的应用程序依赖于一个存储库类,现在是一个ViewModelProvider.Factory
实现,只要应用程序运行就需要它们。我对Dagger的了解非常满意,所以我很自然地将它从这个特定项目中删除,并在Application
类中创建了两个依赖项。这些类看起来像这样:
我的应用程序类创建了我的ViewModel
工厂,它给它的存储库,并向我的活动公开了一个getViewModelFactory()
方法:
public class JourneyStoreApplication extends Application {
private final JourneyStoreViewModelFactory journeyStoreViewModelFactory;
{
// Instantiate my viewmodel factory with my repo here
final JourneyRepository journeyRepository = new JourneyRepositoryImpl();
journeyStoreViewModelFactory = new JourneyStoreViewModelFactory(journeyRepository);
}
@Override
public void onCreate() {
super.onCreate();
}
public JourneyStoreViewModelFactory getViewModelFactory(){
return journeyStoreViewModelFactory;
}
}
我的ViewModel
工厂,用存储库参考创建新的ViewModel
s。随着我添加更多Activity
类和ViewModel
s,我将扩展它:
public class JourneyStoreViewModelFactory implements ViewModelProvider.Factory {
private final JourneyRepository journeyRepository;
JourneyStoreViewModelFactory(JourneyRepository journeyRepository){
this.journeyRepository = journeyRepository;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
if(modelClass == AddJourneyViewModel.class){
// Instantiates the ViewModels with their repository reference.
return (T) new AddJourneyViewModelImpl(journeyRepository);
}
throw new IllegalArgumentException(String.format("Requested class %s did not match expected class %s.", modelClass, AddJourneyViewModel.class));
}
}
我的AddJourneyActivity
类,使用AddJourneyViewModel
:
public class AddJourneyActivity extends AppCompatActivity {
private static final String TAG = AddJourneyActivity.class.getSimpleName();
private AddJourneyViewModel addJourneyViewModel;
private EditText departureTextField;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_journey);
JourneyStoreApplication app = (JourneyStoreApplication) getApplication();
addJourneyViewModel = ViewModelProviders
// Gets the ViewModelFactory instance and creates the ViewModel.
.of(this, app.getViewModelFactory())
.get(AddJourneyViewModel.class);
departureTextField = findViewById(R.id.addjourney_departure_addr_txt);
}
//...
}
但这仍然存在测试问题,这是我的主要问题之一。旁注:我将所有ViewModel
类抽象化(只用方法),然后我为我的真实应用程序和测试代码实现了它们。这是因为我发现它比extend
ing我的ViewModel
s更容易,然后试图覆盖他们的方法并影响他们的状态来创建一个假的版本。
无论如何,我扩展了我的JourneyStoreApplication
课程(我知道这与我自己相矛盾,但这是一个很小的课程因此很容易管理)并用它创建一个地方来提供我的假ViewModel
s:
public class FakeJourneyStoreApplication extends JourneyStoreApplication {
private final JourneyStoreViewModelFactory fakeJourneyStoreViewModelFactory;
{ // Create my fake instances here for my tests
final JourneyRepository fakeJourneyRepository = new FakeJourneyRepositoryImpl();
fakeJourneyStoreViewModelFactory = new FakeJourneyStoreViewModelFactory(fakeJourneyRepository);
}
@Override
public void onCreate() {
super.onCreate();
}
public JourneyStoreViewModelFactory getViewModelFactory(){
return fakeJourneyStoreViewModelFactory;
}
}
我做了我的ViewModel
s的假实现,并从FakeJourneyStoreViewModelFactory
返回它们的实例。我可能稍后会简化这一点,因为可能存在比需要更多的“假”样板。
离开this guide(第4.9节),我扩展了AndroidJUnitRunner
,为我的测试提供我的假Application
:
public class CustomTestRunner extends AndroidJUnitRunner {
@Override
public Application newApplication(ClassLoader cl, String className, Context context)
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
return super.newApplication(cl, FakeJourneyStoreApplication.class.getName(), context);
}
}
最后,我将自定义测试运行器添加到我的build.gradle
文件中:
android {
defaultConfig {
// Espresso
testInstrumentationRunner "com.<my_package>.journeystore.CustomTestRunner"
}
}
我打算将这个问题再打开24小时以防万一有任何有用的东西要添加,然后我会选择这个作为答案。