Getting our apps ready for Jetpack Compose
作者:Joe Birch
The original link
Since the release of Jetpack Compose and the release of the Alpha box, developers who are following us can’t wait to incorporate Compose into their apps. By moving your project to Compose, you’ll see significant improvements in development efficiency, application stability, and maintainability, as well as possibly other benefits such as recruitment (who wouldn’t want to work for a company that uses these new technologies?). But beyond these topics, one question I often get asked about Compose is: When will a stable release be made? Actually, that’s what I want to know… Of course, the official version is not likely to be released soon. I think what we should do now is to focus on how to adapt our APP to Compose. When it comes to the actual release, we still have a lot of work to do if we want our APP to work better. It’s possible that we won’t be migrating our entire application to Compose right now (although that might be tempting), and anything we can prepare for will reduce the friction that we might eventually experience with the migration, while also improving our codebase.
Note: In this article I’ll cover the basics of how Compose works. If you haven’t started exploring Compose yet and don’t know how it works, I suggest you take a look at the official documentation.
Start thinking about the UI declaratively
Although we haven’t started with Compose’s declarative UI yet, we can start developing our APP declaratively in mind. One of the most important concepts of declarative UI is state — our UI components are composed from the data we provide. Instead of manually modifying properties (setText,setVisibility, etc.) when values change. The concept of state from a single data source makes state management easier, as different components of a UI page reference the same state object instead of each component modifying it individually.
Before declarative forms became popular (before ViewModels and LiveData), we used to set View properties separately with different data sources. For example, a commit operation is performed as soon as a piece of data is available, and the result is applied to certain views. As more places are called, the different parts of a screen become fragmentary because they operate independently.
One problem is that your UI can’t just show one state, and since these separate data streams make up the UI components, it’s easy to keep things out of sync and unpredictable because data can come in from anywhere. In Compose, this is even more of a problem because your entire UI will be re-rendered as the state changes, so synchronization is important when the data changes so that you can ensure that the rest of your UI will render as expected. Having the state of a single data source not only ensures this, but also gives Compose a better focus on the UI.
As you can see, we now have only one state reference, controlled by all the actions on the screen. This state reference then controls all components on the screen, rather than each component maintaining a state individually. This allows these components to be stateless, simplifying the description of what our state represents. This is perfect for a Composable UI because a Composable renders with the state it provides. A single data source means that when we need to re-render the screen we can use the same data source to ensure that our state is properly reflected in the UI.
Unidirectional data flow
Monomial data flows are a topic that could be written in its own right, but while we’re talking about state, I feel it’s worth mentioning the concept that one-way data flows are also closely related to a single data source. Watch David Gonzalez’s talk to learn more about one-way data flow on Android
In simple terms, the purpose of one-way data flow is to restrict data flow in one direction to other parts of the screen or other applications. So in terms of the data flow between the view and the data, let’s see if the user clicks a button, gets listened on, triggers some function in the ViewModel, and then initiates a repO request. After the request comes back, modify the state of our ViewModel and finally display the result on the button that was clicked. Here we can see that data flow is a circular flow, and of course the actual implementation of the actual flow depends on the architecture and framework used, but the key concept of the loop is the same.
The same applies to our UI component — when our UI rerenders, the button state is just enough, and the parent component receives the new state and passes that state to its children — again, keeping our UI component stateless simplifies our state description. Then, if the child component wants to fire some event of its own, that event is passed to its parent component and the whole event loop is triggered.
Based on this limitation, any component interaction must have its events triggered by the parent component, which then controls state, and then updates the UI. Now our UI state is much more predictable, because we know where the state is and how it’s being manipulated. This concept is particularly critical in Compose. For Jetpack Compose, the Composable is rendered by state and its changes, thus ensuring predictability and validity through one-way data flow. Sticking to the stateless principle on UI components makes it easier to perform automated tests because we only need to focus on rendering and behavior, not state management.
Decouple UI components
The Jetpack Compose value focuses on the UI rendering of the application, meaning that it is not responsible for the data flow, business logic, and other things associated with the application. The goal is to achieve a lightweight Composable that is decoupled from the rest of the project. In that sense, this is exactly what we want our View class to do in our current application. Even without compose, the idea is to decouple these classes to improve project stability and testability. When it comes to adapting compose, if your view is the same as, or very close to, the Composable UI component, it will save you a lot of trouble switching that component into a Composable component. There’s a lot we can do with Compose.
When you’re working on an existing project, a little bit of refactoring will make it easy for you to adapt for Compose later. As a link, you can take a quick look at the View component in your APP and think about what you need to do to switch to Compose. If there’s nothing, that would be great. Most of the time, however, the component manages its own state by simply launching a REPO, displaying a Dialog, or triggering other actions to modify the state displayed on the screen. Because these can change the state, but they have to change if you want to follow the rest of this article.
Although this can be done all at once when migrating to Compose, it will add to the trouble and extend the reach — which will lead to degradation of our code. All we need to do in the meantime is do a few small refactorings to make the migration easier — these could be to move the dialog into the parent class and trigger the display via callbacks, or to move the view state management to the parent class and change it to the parent class passing the view state to the child class.
When adding a view without adopting Compose, be sure to ask yourself what the view’s responsibilities are. With this in mind, some of the following questions may come to mind as you develop technical specifications and code reviews:
- Should this piece of business logic be contained within the View? Can we put the logic in the parent class and pass the data in as arguments?
- Can state be passed in by state, rather than managed by the view itself?
- Why can’t event firing be a stream of events that are passed in the form of listening instead of being executed directly inside the View?
As you can see, these sample questions are directly related to the concepts mentioned above in this article. There may be other things we can think of, but I’m just giving you a few examples. It is important to keep this in mind as we write the code, how much will need to change to adapt compose later, and what can we do now to reduce the technical debt for adapting compose in the future?
Don’t emphasize every detail
While we’re trying to decouple our UI components and start thinking about an equivalent implementation for Compose, it’s almost impossible to re-create one with Compose all at once, or you don’t know what to expect right away. Because compose and traditional Views are interoperable, we can nest existing components directly into our Composable UI. Therefore, if you are planning to migrate a very beautiful and complex UI to Compose, you can do this bit by bit. That’s a lot more than having to get rid of everything in front of you nice, sometimes it’s important to be realistic. Using compose only in some places is great for building momentum within your team, but if you need to increase UI interoperability within your APP, I think it’s better not to use Compose at all.
Start your access plan
With all that in mind, we now know how to clean up our code for Jetpack Compose. But it’s still important to have a plan for your adaptation, because it can get lost in the grand scheme of things. This is more likely to happen if your project is large. Knowing what the current state of our APP is is much harder than knowing how to migrate the project to Compose. Then again, take a step back and think in smaller chunks. What we should do is move from where compose is now, step by step, to a point where we can be comfortable using compose. Keep in mind that transitioning the UI to declarative is only one part of compose’s migration — adapting to Compose would be difficult without the other concepts mentioned in this article.
Part of your plan should also include adhering to the principle of one-way data flow and decoupling UI components so that the more you adapt now, the less work you’ll need to adapt later when Compose comes along. When you review a PR or prepare a technical document for a requirement, try to make sure that your merge doesn’t add work to future adaptations. Although that’s something we’ve been trying to do, there hasn’t been a lot of declarative thinking change. With that in mind, and with some form of standard for managing state and data in your APP, it becomes more natural to incorporate Compose into your code.
Using Compose for the UI is just one piece of the puzzle, and there are many things to consider, such as building the above concepts into your team, the Compose framework, and other changes introduced (such as writing unit tests to Composable).
Ready for minSdk=21 (Lollipop)
Jetpack Compose requires a minimum SDK version 21, also known as Android Lollipop. However, we have released many versions, and there may be some users with devices lower than this version, and there are also apps that support the use of earlier versions. However, this could block you from adapting to Compose if there isn’t a lot of adaptation work. Since there’s still a bit of time before a stable release, now you can check to see if you’re still supporting releases below 21.
- Why are we supporting releases below 21 now? Is there a business requirement or is it just that we haven’t updated it in a long time?
- How many users are under version 21? Do we know about these users, and how do they behave on our APP? Are they our target audience
- Are there any other issues that we know of or plan to deal with because of pre-21 equipment?
- Can migrating to Compose bring in more revenue than we support pre-21 versions?
- If we go to 21, what should we do now to ensure that users who can’t upgrade continue to use our APP?
Adapting to a lower version of Android can cause some difficulties in the development and testing process. Sometimes we can’t use some of the libraries, or we run into problems that are only encountered in earlier versions. These affect not only the output of development, but the output of the entire team. With that in mind, consider why support is needed and question whether something is still necessary. Once you’ve decided to upgrade to the lowest version, you can start preparing what workspaces you still have to make sure users who can’t update can still use them (fixing some serious bugs, making sure you have a stable version before upgrading). Upgrading to the latest version as soon as possible will also make the later migration to Compose easier.
Build momentum within your team
Because Jetpack Compose not only required a change in the way we think about UI development, but also a new set of apis to learn and understand. Although the framework is still alpha and some things may change a bit, the compose concept and the way it is called will remain the same because the declarative UI is basically the same. That’s why researching Compose right now won’t waste your time.
Wanted to further build momentum for using Compose within the team. You might also want to do a short talk about these concepts within your team, or even hold some sort of “hack day” where people can learn and use Compose themselves. Because Compose is going to be the way we develop apps in the future, it’s worth it for the company. Snapp Mobile, for example, has held several of these “hack days” where people collaborate to explore Compose. Because Compose probably won’t have a stable release anytime soon, hosting events like these will allow the team to get used to the declarative UI and get to know the Compose framework.
At Compose Academy, we’re constantly updating and releasing a lot of examples to help your team get used to that framework faster. Hauler can contact us if your team is interested.
To be patient
It’s also important to be patient with new ways of building uIs. Despite the excitement of the new framework, traditional UIs aren’t going away anytime soon. So we have plenty of time to adapt to Compose. There’s also no overwhelming advantage to something new. You can’t migrate all your existing code to Compose all at once. And you can’t start with Compose for every requirement. Not only is there something that compose cannot be adapted right away, there are also some higher-priority things that need to be implemented to create value for the customer.
Although Compose will eventually become the standard way we build Android UIs, the process is not a sprint but a marathon, so take your time and enjoy the process.
Of course, there are a lot of things we need to do to prepare for Jetpack Compose. The above are just some of the things THAT I can think of to explain to you and some of the key points that we need to adapt for Compose. Are you considering using Compose for your team, or are you learning something to move your team in the right direction? If so, I’m glad to hear from you.