Jetpack Compose: Clean and Encapsulated Composables

A Deep Dive into Encapsulation for Cleaner Composables in Jetpack Compose

Igor Stevanovic
Dev Genius

--

Jetpack Compose Logo
The Jetpack Compose logo used in this image is the official logo created by Google

Over the past year, Jetpack Compose has revolutionized the Android landscape. Its intuitive syntax has significantly simplified the work of engineers. The creation of complex UI components, which was once a daunting task, is now a breeze. With just the use of a Modifier, you can create in minutes what would have taken hours or even been impossible to create in XML. This efficiency is making Jetpack Compose increasingly popular each day.

The Naming Convention Dilemma

Any seasoned Jetpack Compose user is aware of an unspoken rule regarding the naming of composables in an Android App. The norm is to name functions that return Unit and that is annotated with the @Composable tag using PascalCase, resulting in names like PrimaryButton, SecondaryButton, ExampleStateSuccess, ExampleStateLoading, ExampleStateError, and so forth. However, after reading an article by Christopher Keenan, I began to question this naming convention and noticed a few issues that had been bothering me but I had chosen to ignore them.

For instance, what happens when you have similar items with easily confusable names, leading to hours of trying to decipher which is which? Or what about excessively long names that compromise readability? I’ve encountered such instances in the past with names like ParkingLocationMapDetailsSuccess, ParkingLocationMapDetailsError, ParkingLocationMapDetailsLoading, PrimaryLargeButton, SecondarySmallButton, and the list goes on.

Proposed Solution

We could adopt a slightly unconventional approach that may not seem intuitive initially. We could encapsulate similar or related composables into Object and use the invoke operator for the main composable. For instance, it would look something like this:

The usage would be:

At first glance, this might appear odd and counterintuitive. However, upon reflection, it’s not that bad. Many composables can be grouped and encapsulated in this manner, such as different states for an item or a list of items:

And the usage would be:

We also have the option to encapsulate attributes. For instance, we can nest colors, typography, and so forth within PrimaryButton.Defaults.
Upon examination, we can identify several advantages to this approach:

  • Encapsulation of all associated types
  • Simplified discovery and utilization of related types via IDE code completion
  • Enhanced static enforcement of the convention

Adopting this encapsulation approach offers a multitude of benefits. Firstly, it provides a clear structure and organization to your code by grouping related types together. This not only enhances readability but also makes maintenance and debugging easier. Secondly, it simplifies the process of finding and using related types, thanks to the IDE code completion feature. This can significantly speed up the coding process and reduce errors.

Furthermore, this method ensures better static enforcement of the convention, which can lead to more consistent and reliable code. It also promotes better code reusability, as encapsulated objects can be used across different parts of the application without the need for duplication. Additionally, this approach can help in reducing the risk of naming conflicts, as the encapsulating object provides a unique namespace for its associated types. Lastly, it can also improve code documentation, as the encapsulating object can serve as a natural place to provide comments and explanations about the purpose and usage of the grouped composables.

While the encapsulation approach offers numerous benefits, it’s also important to consider potential downsides.

  1. Learning Curve: This approach might not be intuitive for developers who are new to Jetpack Compose or Kotlin. They might find it challenging to understand the use of the invoke operator and the concept of encapsulating composables within an object.
  2. Potential Misuse: There’s a risk of incorrectly requiring the encapsulating object type as a parameter to another composable function. Although this isn’t dangerous, it could lead to confusion and misuse.
  3. Increased Complexity: While encapsulation can make code more organized, it can also increase complexity if not used properly. Overuse of encapsulation might lead to a large number of nested objects, making the code harder to navigate and understand.
  4. Refactoring Existing Code: If you decide to adopt this approach in an existing project, it might require significant refactoring of the codebase, which could be time-consuming and error-prone.
  5. Compatibility with Existing Libraries: Some existing libraries or tools might not fully support this approach, which could lead to compatibility issues.

Remember, the key is to find a balance and use encapsulation judiciously, considering both its advantages and potential drawbacks.

Conclusion

In conclusion, encapsulating composables and their associated types within an object in Jetpack Compose presents an intriguing approach to improve code organization, readability, and reusability. While it offers numerous benefits such as better static enforcement of conventions and easier discovery of related types, it’s also crucial to consider potential downsides like the potential misuse, and increased complexity.

This topic certainly opens up a lot of room for discussion and exploration. I encourage you to share your thoughts and experiences in the comments below. Have you tried this approach? What challenges did you face? How did it improve your coding practices? I’m eager to hear more perspectives on this topic. Your insights could greatly contribute to this ongoing conversation and help us all grow as developers.

You can find all of the source code in my GitHub repo.

Want to Connect?

GitHub
LinkedIn
Twitter
Portfolio website

--

--