Eventually, I came up with the following solution:
each feature consists of the following gradle-modules:
api
impl
and fake
data:api
data:impl1
… data:implN
and data:fake
data:wiring
ui
demo
So, here api
, impl
and fake
as usual, but I’ve my data layers separated. I bought myself that I need multiple different implementation of data layers sometimes, for example – if I develop Stock-Charts App, I could rely on Finnhub Open API or MBOUM API or provide fake implementation.
Thus I have data:api
, data:implX
. Indeed, data:api
defines FeatureRepository interface (one or many) and data:implX
provides actual implementation for them. In order to bind interface and implementation, I use data:wiring
, which defines Dagger modules and component(s). In addition, I keep the same package names within each data:implX
module in order to “write-once” the data:wiring
module. And to replace one implementation with another, I just change a single line in data:wiring/build.gradle which states a sort of:
implementation project(":data:implA")
to
implementation project(":data:implB")
Also, to break the confusion mentioned in my original question, I introduce ui
module, which contains some Views
of a particular feature. Fragments go in demo
(a standalone app to test feature) or in ui
, they refer to viewModel
which have some bindings ctor-injected from Dagger component of a feature. But the UI and library are separated here. Fragment instantiates a dedicated Dagger component that uses component dependencies to refer to feature’s library bindings, such as interactor or repository etc.
So, to wrap up – separation between UI and business logic implementation (a “library”) for each feature makes it possible to solve the issue. Feature’s api
declares an entry point to it’s functionality as a library, and it’s global access via Dagger multibindings from :app
. So it can be used further in any :demo
, :ui
and :dynamic-feature
.
CLICK HERE to find out more related problems solutions.