I am trying to write two fragments that share the same layout and a lot of view set-up code. Let's say that the layout has a Title TextView and a RecyclerView. In Fragment A, I want the recycler view to use a custom adapter that will be different from what Fragment B will use, and I want Fragment A to also have a different callback to the parent activity than what Fragment B will have. What is the best way to structure these fragments so that I don't have to copy and paste a lot of code?
I could opt for something like BaseFragment with FragmentA and FragmentB extending it, and maybe overriding a getAdapter() method, but I've read that composition is favored to inheritance. How would I handle this using composition?
CodePudding user response:
Firstly, it's not always the case that composition is favored to inheritance. Both of them have their best fit scenarios.
Try to draft on how you will implement with these two and compare them.
For example, with inheritance you can do it with a BaseFragment
while going with inheritance it can be something like the following:
class SingleFragment {
// feature A specific code, could be abstracted to a separate class
// feature B specific code, could be abstracted to a separate class
}
It is up to you to figure out which one fits your purpose but based on my experience, inheritance would have these benefits in this case:
- Less confusing code. While both the code for fragment A and B are used in the SingleFragment, this fragment becomes more complex and harder to maintain. That violates the open-closed principle (assuming A and B are not closely related) too.
- Easier to refactor when one of them changes. Even though two of them share the similar layout for now, it is possible that one of them will change in future. Replacing the BaseFragment would be easier and less error prone than taking all the code for feature A out from the SingleFragment.
CodePudding user response:
Well there is some missconception here. Composition is favored over the inheritance, yes, but that doesn't mean you should try to avoid the inheritance at all cost. It is a fundamental part of the language and as such should not disregarded.
The basic idea behind COMPOSITION OVER INHERITANCE is that inheriting on to many levels make it harder to understand the code, as the logic might be spread into multiple parent classes, can lead to changing the behaviour that you don't expect to be changed, and then might expose to much of the interface and methods to the user, making it harder to write and debug the code, since instead of having 5 method to choose from, you have 50, from all parent classes. Thean each time you need a few methods, you extend a class and get full 50 of them, which you don't want, need, nor should be using.
Composition eliminates that, allowing for smaller simpler easier to read classes. Let's you reuse the part of the code that you need, and only that.
Read up on UseCase pattern, when you write bunch of business logic classes, having only public method per class, doing exactly one singular thing! Then you build fragments etc, using only the use cases(pieces of logic) that you need.
That said, your FragmentA and FragmentB will inherit from default Fragment class anyway!
Opposite to what your general idea is, I believe, you should always start with some BaseAbstractFragment : Fragment(), that your fragments A and B will inherit from, since sooner than later you will run into the need od duplicating the code in the fragments.
For example, they both will hold the adapter field, maybe some observables or callbacks you need to clear in the lifecycle etc. You can do all that in base fragments, and then only specify the rest in fragment A or B.
Why do you need 2 different adapters for fragments working on identical layouts?
You can have one adapter, that takes a function as parameter, as pass that function on adapter constructions -> different in each fragment. Here's a field in the adapter.
val clickAction: (item: UIMODEL, position: Int) -> Unit,
And that's hwo you can call it. Here I'm using presenter, but the idea is the same, to pass an action in the constuctor.
init {
adapter = GenericAdapter(
data = items,
presenter = SinglePickPresenter(clickAction = { item, _ -> callback.invoke(item) })
)
}
This way, you can change the callback each time, you create the adapter