Dagger 2 Android - 使用依赖项引用将()依赖项注入到ViewModels和Application中

问题描述 投票:1回答:1

我正在用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注入依赖项吗?

java android dependency-injection dagger-2
1个回答
0
投票

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工厂,用存储库参考创建新的ViewModels。随着我添加更多Activity类和ViewModels,我将扩展它:

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类抽象化(只用方法),然后我为我的真实应用程序和测试代码实现了它们。这是因为我发现它比extending我的ViewModels更容易,然后试图覆盖他们的方法并影响他们的状态来创建一个假的版本。

无论如何,我扩展了我的JourneyStoreApplication课程(我知道这与我自己相矛盾,但这是一个很小的课程因此很容易管理)并用它创建一个地方来提供我的假ViewModels:

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;
    }
}

我做了我的ViewModels的假实现,并从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小时以防万一有任何有用的东西要添加,然后我会选择这个作为答案。

© www.soinside.com 2019 - 2024. All rights reserved.