DEV Community

Cover image for Our experience becoming a Compose-first app
Vinícius Veríssimo
Vinícius Veríssimo

Posted on

Our experience becoming a Compose-first app

This is a little piece about our journey applying Jetpack Compose to our existing codebase at the Moises.ai App, where we have time-based features that require high-performance and scalable UIs while also taking care of the developer's experience.

Jetpack Compose Concerns

Since we work with a small team of developers, we had many concerns about the impact of adding a new technology to our project. How would it impact the team's performance and the challenges of building new things and maintaining the current one? We had to start our explorations with the Jetpack Compose and still keep up with the high pace of a startup.

We had to be cautious about fully relaying our solutions in a new way of building UIs. To do that, we had a few things in mind:

  • It cannot decrease our lead time;
  • It has to interoperate with our current codebase;
  • We must be able to automatically test the UI with our current tools (at the time, it was Appium);
  • We can't impact our app's performance;
  • The team needs to have time to build knowledge about it;
  • The team must have the confidence to use it.

With that, we started to fill those gaps.

Exploration Time

We started by choosing someone from the team to spend part of their time exploring this new technology and how we should integrate it with our current code.

This person had already studied Jetpack Compose using the Android Developer Courses and had created a few small projects in Compose to practice. It was necessary to understand how it works and the difference from using Android Views.

We realized that it wasn't just a matter of coding the views in different syntax; it was also necessary to rethink how we handle UI states to fully take advantage of the Compose UIs.

With this in mind, it was time to implement and ship a small thing in Compose to get an idea of how it could be used in our project.

Yes, we added Jetpack Compose to our codebase by writing new components with it and keeping the old ones untouched. Some people don't like this approach because it results in a codebase with mixed technologies, but we didn't want to risk changing thousands of code lines that were working fine to introduce a new way of doing things.

Adding Compose to the project

In addition to the standard Gradle setup, we also have a project-specific requirement to use Compose in our features; it must use our Design System.

We already had an implementation of our Design System in Android Views using a mix of components and styles. Since Compose handles themes, components, and styling differently, we have decided to re-implement part of our Design System in Compose.

We prioritized the implementation of Typographies, Colors, and Buttons, the basic elements to build any small Proof of Concept. The first challenge is the initial attachment of the Compose structure to the Material Theme. The available set of colors and typographies wasn't enough for our Design System, so we implemented our own custom Theme inspired by the Material Theme implementation but focused on our necessities. It was hard work, but we took advantage of this soon.

Finally, we were ready to add the Jetpack Compose to our main project, but we were still in the Proof of Concept state. We didn't want to add this new technology to our time-based features, which require high-performance UIs and complex business solutions.

We decided to start with a static screen with simple UI elements to see how it will integrate with our current implementation. The app's navigation was Fragment-based, so we implemented a Fragment wrapper for the Compose UI, and we managed to keep the navigation between fragments. It worked pretty well.

From the user perspective, there was no distinction between this new UI and the others. This achieves part of our interoperability criterion. I say part because we were essentially creating a whole screen in Compose without any mix of Compose and View on the same screen, but it was enough. Later, new requirements appeared, and we had the chance to use Compose only in part of an existing View screen, which we efficiently managed with ComposeView.

With an old codebase, it is easy to have parts of the code that reflect patterns, ideas, and concepts of a given time. In a startup environment, the code constantly changes, evolves, and quickly adapts to the needs. Even though the current patterns we used to control our UI states weren't the best to take full advantage of the Compose capabilities.

Our UI state control was mainly based on a few LiveDatas in ViewModels with different properties to be handled by the UI. As a better approach, we've decided to have the UI state as a Data class exposed by a Flow and collect it on the Compose side. This way, and using the concepts of the Unidirectional Data Flow, we managed to have more control over the UI state and provide it closer to the properties received by the composables. Later, we kept using it while maintaining some old view-based screens.

To make it shippable, we still had to make it compatible with our UI test automation.

Automated Validations Support

For the automated test support, we used the testTag modifier to make the automation of our screen validations possible with Appium. Later, we used the same property when we moved to Espresso. So, we achieved another criterion.
Okay, now we have a simple feature implemented with a testable new UI paradigm and architecture. Finally, it was time to ship it and see how it would perform.

Shaping the Compose muscle

The final reception was excellent. The screen performed as intended and was seamless with the other screens, but it was lighter and had a few smooth animations. This is how we could differentiate the implementations of the final product.
The team was satisfied with the result. The new feature was used as a proof of Concept for using Jetpack Compose in our platform and as a way to introduce Compose to the other team members.

At the time, one person had more knowledge about Compose than the others. With the new technology approved, it was time to grow the team's knowledge about Compose.

Because of the exploration moment, we had someone who knew all the paths to learn Compose. Then, there were months of mentoring, study, pairing, and building new features to practice composing in a real scenario with more dynamic screens with CRUD and reactive content.

We had the opportunity to create more wrapped screens with Compose, partial Compose screens, and Compose screens with partial View content that wasn't yet supported in Compose. We tested new ways to organize components, files, and new supports on our Design System theme, rewiring exited components to fit our needs, highly based on the Material's patterns.

More criteria were achieved:

  • The team was happy to learn a new thing and to see it quickly being applied and shipped to production;
  • The team was happy with Compose itself, how fast it built a new screen, listed things, created components, etc.
  • We had the curve of being slower because we were trying to understand new concepts, new patterns, new small and big animations, and new ways to solve problems. However, once we were comfortable with Compose, we became faster and produced more code that was easier to read and understand.

Even though one criterion was missing, the one about using it in a feature that required high performance. Eventually, we had the opportunity to use our knowledge in a big feature with UI challenges.

Performance Challenges

We received the request for a new feature:
An audio recording screen that will render a waveform on the screen during the recording phase. Then, the user can seek the waveform, override any part of the recorded audio, play it, and process it.

This was our first real big challenge in Compose. Long story short, we struggled to draw the waveform with Canvas in Compose in a performative way; we had many performance problems because the data kept changing fast, and we needed to draw the waveform smoothly for the user. We almost gave up using Compose to do it, but we were decided to make it work. In the end, we managed to reorganize the composables and structure the data to avoid unnecessary recompositions; we used the Layout Inspector and other available tools to evaluate our approaches.

This challenge was necessary to achieve the last criterion; the screen performed great, and we were ready to do any other Compose challenge. It consolidated our ideas, patterns, and strategies.

A few months later, we had more features that required performance and were time-based, but we were already sold to the Compose. We were already thinking of Compose-first solutions; using the old Views wasn't an option anymore.

Conclusion

It takes time to adopt new technology in existing projects. In a startup environment, you can go fast and break many things or reduce your speed just enough to shape your ideas and have more substantial outcomes.

Image description
We introduced the Jetpack Compose in baby steps. Even knowing this was the way to go, we needed to be sure that it would solve our needs, and we focused on spreading the knowledge across the team so everyone could work seamlessly in the code base.

Top comments (0)