2

I'm setting up an Android project, where I've separated the code into different modules to have a bit of a cleaner structure. The modules are presentation, domain, data, di and app. I am including a diagram of the dependencies between them.

Modules and dependencies in the project

When I write a new screen using Compose in the presentation layer, I am including a preview. Of course, these screens need a viewmodel, and since Compose previews do not start the application, Koin is not running to inject this. Thus, the preview fails to inflate.

Koin provides a solution for this, allowing you to run it in the preview with whatever modules you want, so you can include a fake/dummy one if you want to. It looks something like this:

@Preview
@Composable
fun ConnectAccountScreenPreview() {
    KoinApplicationPreview(application = { modules(onboardingModule) }) {
        ConnectAccountScreen(onNavigateBack = {}, onNavigateToNext = {})
    }
}

The issue I have with this is that I cannot create a fully fake module in the presentation layer, because it can't see the data layer, so the implementation of the repositories and the datasources are not available.

I could of course create a fake module in the DI layer, or use the real one, but the presentation layer has no visibility of the DI layer, and I cannot reference it, because it would create a circular dependency.

What is the cleanest way to solve this? Does my modularization setup just suck? Should I get rid of the previews? Can I include a fake module or a dependency that I'm not thinking of? If you need any further code to assist, just let me know. Thanks!

1 Answer 1

2

The usual way to do this is to remove the need for the viewmodel to preview.

@Composable 
fun MyComposable() {
   val viewModel = ....
   MyInnerComposable(viewModel.foo, viewModel.bar)
}

@Composable
private fun MyInnerComposable(val foo: String, val bar: String) {
   //actual implementation
}

@Preview
fun MyPreview() {
    MyInnerComposable("my foo value", "my bar value")
}

There you remove the dependency on the View Model from the actual test. This also makes it easier to write unit tests for your composable.

Sign up to request clarification or add additional context in comments.

3 Comments

If I need to call viewmodel methods from the composables (an action when tapping a button, for example), should I pass those as parameters to the inner composable as well? The list of parameters is going to be long then, since there's a tad of functionality there.
You can bundle them into a data class if you prefer, to reduce the number. You'll need to pass something in for a function call, but it doesn't need to be the actual function (Previews are rarely functional, so a noop would be enough)
Alright, got it. I don't know why I thought it would be enough not to pass the viewmodel as a parameter, when what you explained is the state hoisting I was supposed to do. Thanks a lot for the small example, it really helped, appreciate it a lot.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.