1 year ago
#16826
yozhik
Android multi module circular dependency issue
I want to ask your advice how to solve circular dependency in android multimodule project?
What I would like to receive as an answer: ideally clear answer or advice how to solve the issue I have faced, but I also would be thankful for any useful links to articles, books, video tutorial - to understand how to solve this.
Input data: at the beginning we had a monolite app based on Android clean architecture guide by Google with modules: data, domain, presentation.
Presentation layer: Model view presenter (not MVVM)
Here is the example with all it's datasources, repositories etc: https://github.com/android10/Android-CleanArchitecture
As the project was growing we decided to move to multimodule architecture, to achieve: separate feature - separate module, because previously we had it all placed together in such packages as: presenters, activities, fragments, models etc..
Currently app consists of such modules:
app -- just app
core:data -- datastores, requests to server, db etc.
core:domain -- business logic (interactors)
core:presentation -- view + presenters + all strings localizations + all graphics resources and layouts
features:feature_module_1 -- some feature with fragments, it contains data, domain, presentation layers and has dependencies for core module, because there are a lot of useful classes.
....
features:feature_module_N
Here are current situation with modules on my project I am working on: app module dependencies:
dependencies {
implementation project(':features:feature_module_1')
implementation project(':core:presentation')
implementation project(':core:data')
implementation project(':core:domain')
}
data:
implementation project(':core:domain')
presentation:
dependencies {
implementation project(':core:domain')
implementation project(':core:data')
implementation project(':features:feature_module_1') -- when I add this - it writes "circular dependency between presentation and feature_module_1."
}
feature_module_1
dependencies {
implementation project(":core:presentation")
implementation project(":core:domain")
implementation project(":core:data")
}
All application resources and all util and base code resides in core:presentation module. In ideal situation we are going to move out from there feature/code to separate modules, to make it as thin as possible. But that's in the future. Currently build time of this part takes 2min if we do some small changes there. Feature modules building quickly.
And here is the issue description: As you can see from aforementioned info feature_module_1 depends on core:presentation module. Layout xml files which related to feature_module_1 I am putting in feature_module_1, because I want all resources related to feature be in the same folder. But there are several classes, which we have created long ago, to make work with RecyclerAdapter template tasks more faster. We have there ViewTypes enum for 500+ lines of code it contains in itself referances to (viewHolder + item xml file). And in feature_module_1 I want to use this universal RecyclerView. In my feature_module_1 I have created MyCustomViewModel + MyCustomViewHolder + my_custom_layout.xml files hoping that everything would work OK, but to make everything work I have to add record to enum ViewType with enumeration of all viewHolders in core:presentation module. And core:presentation module knows nothing about feature_module_1. If I add feature_module_1 as a dependency to core:presentation module - I get cycle dependency error and project don't build.
As a solution I can take all that classes MyCustomViewModel + MyCustomViewHolder + my_custom_layout.xml and move them from feature_module_1 -> core:presentation - then everything would work, but I wouldn't like spread feature between different modules.
Another solution I see: Is to move all classes that are related to this universal RecyclerAdapter + ViewType + all files that belong in this separate module, let's name it recycler_helper_module. And these two modules would have it as a dependency: core:presentation & feature_module_1 But the issue just migrated to another place, because recycler_helper_module needs to know about layout resource files (visual items of recycler view) and if this new module would depend on core:presentation module - we would againt get Circular dependency. We can also move all *_item_layout.xml files of adapter to this recycler_helper_module as well - then everything should work, but we need to remember that we need to add such files every time we make anything else in different modules. I have heared also about the idea of moving all resources to separate module, but I didn't catch that idea how to make it correctly and how to manage that module properly and where to place resources for other modules.
Write me in comments if you need more data. Thanks in advance!
Circular dependency between the following tasks:
:core:presentation:compileDebugAidl
\--- :features:myFeature1:compileDebugAidl
\--- :core:presentation:compileDebugAidl (*)
And here is what I have on presentation layer classes:
public class RecyclerAdapter extends RecyclerView.Adapter<BaseViewHolder>{}
public interface RecyclerItem<T extends BaseViewHolder> {
@LayoutRes
int getType();
/**
* Maximum number of ViewHolders to hold in the pool before discarding.
* @return max number
*/
default int getMaxSizeInPool() {
return DEFAULT_MAX_SCRAP;
}
default long getItemId() {
return getType();
}
default void onBindViewHolder(@NonNull T holder, int position, RecyclerItemListener listener) {
}
default void onBindViewHolder(@NonNull T holder, int position) {
}
default void onBindViewHolder(@NonNull T holder, int position, RecyclerAdapter adapter) {
}
default boolean areContentsTheSame(RecyclerItem obj) {
return (this == obj);
}
default boolean isPeriodicUpdateNeeded() {
return false;
}
}
enum class ViewType(private val viewType: Int) {
CUSTOM_LIST_ITEM(R.layout.custom_list_item) {
override fun getViewHolder(viewGroup: ViewGroup): BaseViewHolder<*> {
return MyCustomModel.MyCustomHolder(
MyCustomListItemBinding.inflate(
LayoutInflater.from(viewGroup.context),
viewGroup,
false
)
)
}
};
abstract fun getViewHolder(viewGroup: ViewGroup): BaseViewHolder<*>
fun createViewHolder(parent: ViewGroup): BaseViewHolder<*> {
return getViewHolder(parent)
}
fun getInflatedView(parent: ViewGroup): View {
return LayoutInflater.from(parent.context).inflate(viewType, parent, false)
}
companion object {
private val map: MutableMap<Int, ViewType> = HashMap()
@JvmStatic
fun valueOf(value: Int): ViewType {
return map[value]
?: throw IllegalArgumentException("view type for value $value not found in enum")
}
init {
for (type in values()) {
map[type.viewType] = type
}
}
}
}
Thanks!
android
module
architecture
clean-architecture
android-module
0 Answers
Your Answer