Blog Infos
Author
Published
Topics
Published

Photo by Marc-Olivier Jodoin on Unsplash

 

Do you have an application that you want to migrate to Jetpack Compose?

For me, it is the German Pocket Dictionary, a quick and easy way to search for meanings and translations in German and English.

Earlier this year, I decided to migrate the application to Jetpack Compose, but I never completed the migration only recently and pushed the changes to the Play Store.

This article is all about my experience, the challenges I faced, the thought process behind the migration, and the design decisions I took.

If you find this interesting or you are thinking about migrating your application to Compose, then this article is for you, and learn from my amazing experience.

The state of application before migration

Before coming to the migration process, let me bring you the starting state of the application so that you can understand why I took a certain path during the entire process.

As I have mentioned earlier, this was technically my first personal project in the Android world after I learned the basics from the tutorials.

You may be wondering why this is that important for this article. 🤔

It was hello-world with no proper design pattern or standard of professional development.

The application was old or a bit outdated for me as but as software engineering goes it was still relatively new.

There was no architecture followed in the application- NO MVVM or MVP, this means that you can find the entire code for an activity/screen inside the Activity itself and not separate it into different classes.

Not to mention, no Dependency Injection library was added to handle the dependencies.

No software principles were followed. Yep, everything was inside a single class.

In short, the application was a big ball of mud.

Now we are on the same page, so let’s talk about the entire process.

I had two major challenges that I had to overcome.

  • Migrate the Java codebase to the Kotlin codebase
  • Implement application architecture.

These two challenges were the biggest and the basis for all the decisions I took.

The first challenge was pretty simple to overcome, I wrote all the new code in Kotlin and slowly migrated the Java classes to the Kotlin classes as and when needed.

The second was the biggest and took most of the time, the application architecture.

I migrated the codebase in three phases:

  • Pre-migration
  • Migration
  • Post-migration

I divided the process into smaller tasks and repeated them for each screen.

Tasks

  1. Clean spaghetti code in activities. (Pre-migration)
  2. Implement MVVM architecture. (Pre-migration)
  3. Set up the UI layer for Jetpack Compose. (Migration)
  4. Migrate the UI layer to Jetpack Compose. (Migration)
  5. The migration of the core application library/components to Compose. (Post-migration)
  6. Clean up (Post-migration)

 

Pre-Migration

 

1. Clean spaghetti code in activities

The understanding of SOLID design principles was the driving force behind this task.

I started with an activity and noted down the different responsibilities the activity was handling (Single Responsibility Principle). Then I broke the class down into multiple classes until the responsibilities were separated.

I repeated this process for all the activities.

One thing that I made sure of was I didn’t refactor the UI-related or Android framework-related code as I was going to remove it in the upcoming steps when I would do the Compose migration.

How to break down classes/methods?

I just asked myself, Can I break this class/method into smaller components? If yes, break it until I can’t.

Simple.

2. Implement MVVM architecture

MVVM is the recommended application architecture for Android applications. So, I also decided to update the application with the same.

Again, I made sure that I make the minimum amount of changes in the UI layer when implementing the architecture.

Before I started working on this step, I set up the Hilt Dependency Injection library in the application to manage the dependencies.

Apart from the UI layer — Activities/VM, I updated the repository layer to follow the pattern.

Honestly, a huge amount of work was already covered when I broke down the activities into smaller classes in the previous step.

Mostly I had to add ViewModel to handle UI logic and reactively update the activity with the changes.

This step was fairly simple as I didn’t have much logic in my application, so I was mostly bridging the data layer with the UI layer in the ViewModels.

During this step, I also refactored the application package structure for the future and packaged the code based on features instead of the type of class/layer:

The primary benefit of this approach was that if I plan to modularize the application, I’ll have most of the work already done because of the structure of the packages.

Migration

 

 

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Migrating to Jetpack Compose – an interop love story

Most of you are familiar with Jetpack Compose and its benefits. If you’re able to start anew and create a Compose-only app, you’re on the right track. But this talk might not be for you…
Watch Video

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer for Jetpack Compose
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engin ...
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer f ...
Google

Jobs

3. Set up the UI layer for Jetpack Compose

This step was different as there are multiple ways to update your UI in the Jetpack Compose.

So, how can I do it?

Something I read on the r/androiddev discord server which helped me a lot in this, which was:

Your ViewModel should be agnostic of the UI Layer and shouldn’t know about the implementation details of the UI.

This is a brilliant statement if you think about it and is really insightful while designing your applications.

Let me take a moment to explain how I understood the above statement.

You should write the ViewModels so that it doesn’t depend on how the UI is implemented.

This basically means that your ViewModel should not know whether the UI is written in XML, or Compose, or whether the ViewModel is part of an Android application or a web application, or a multiple-platform application.

The ViewModel should have minimum dependencies on the framework and should know as little as possible about the framework it is part of.

Once you embrace this principle and write your code in this manner, you can easily swap the UI implementation from XML to Compose or to any other UI toolkit for that matter with the minimum amount of changes.

In fact, you can migrate your application to KMM or KMP with minimum changes and share the ViewModel in that framework.

I just wanted to mention this so that you can understand and think differently while working with the ViewModels or any other part of the application for that matter.

Now, coming back to the migration process.

I choose to store the ViewState of each screen into a Kotlin sealed class with different states like Initial, Loading, Loaded, and Error, which are common states for most of the use cases.

The list of states is not fixed as they can vary with the complexity of the screen and depend on the state the screen can be in.

Once the possible states of the screen were decided, I delegated the data to the ViewState and the ViewModel acted as the bridge between the UI state and the UI events while the ViewState became the single source of truth for the UI layer.

At this point, most of the work was done and at the end of this step the application was well structured and the ViewModel didn’t depend on how the UI was implemented.

4. Migrate the UI layer to Jetpack Compose

This step was both easy and difficult at the same time. You see when I began the migration process I was learning Jetpack Compose which means that I got stuck a lot, and thanks to the wonderful community I was able to overcome the hurdles I faced along the way.

One of the best things about Compose is the interoperability, which means that you can have Compose UI inside XML and vice versa.

This helped a lot, I followed the following steps in this task:

  • Break the screen into unit-sized components.
  • Rewrite the unit-sized components in Compose.
  • (Optional) Add the above-created component to the View.
  • Repeat until the entire screen is rewritten in Compose.

Let’s take the example of a simple screen that displays a list of students to understand the above process.

The unit-sized component in a list that is displayed using RecyclerView is the item UI or ViewHolder.

Please Note that in the following example, I have cut short the best practices just to showcase how to break down the screen into unit-sized stateless composables which are then integrated into the stateful composable that interacts with the ViewModel.

You can create a StudentItem.kt which is implemented in Compose.

@Composable
fun StudentListItem(student: Student) {
Card {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp, horizontal = 16.dp)
) {
Text(text = student.name)
}
}
}

Then create a stateless screen component that contains the list and the list item.

@Composable
fun StudentListContent(
students: List<Student>
) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(students) { student ->
StudentListItem(student = student)
}
}
}

This StudentListContent stateless composable is integrated into StudentListScreen which is a stateful composable.

@Composable
fun StudentListScreen(
viewModel: StudentsViewModel = hiltViewModel()
) {
val students by viewModel.students.collectAsState()
StudentListContent(students = students)
}

Now talking about the optional step where the interoperability shines.

For some screens, the UI can be complex and you can find it difficult to migrate the entire screen to Compose in one go.

In situations like these, what you can do is follow the first two steps by creating unit-sized components and slowly migrating the entire screen.

You can read more about the process from the Official Android docs on Compose Migration Strategy.

Once the entire screen is migrated to unit-sized composables then create the screen composable and rewrite the screen using Compose to complete the migration process.

Something that helped me a lot during this process is that I wrote the screen in a stateless manner to minimize the dependency of the screen on the underlying architecture and the framework. This also made the screen testable and scalable.

Then I created a stateful composable that acts as a bridge between the ViewModel and the stateless screen as shown in the above example.

Repeat the step for the remaining screens/activities.

At this point, your UI layer should be written with Compose.

Post-migration

 

 

But wait, the migration isn’t completed yet, there are two more steps to it.

Let’s see what are they.

  • Migrate the core application libraries/components to Compose.
  • Remove unnecessary files and clean up.
5. The migration of the core application library/components to Compose

This step includes the steps to migrate application-level architecture migration and the library migration.

At this moment, although the screens were written in Compose, they were inside each activity.

So, I set up the application navigation in Compose using Compose Destination library and removed activities.

After removing the activities, I was left with only MainActivity which is a single activity in the entire application with all the different screens implemented in Compose.

Then I migrated the resources to Compose, these resources were colors.xml, values.xml, themes.xml, and fonts.xml.

I migrated all the components which could be migrated to Compose to make a 100% pure Compose application.

For some readers, they may have to rewrite the MainActivity to accommodate the changes and migrate other major components of the screen like the DrawerLayout, Bottom Navigation, etc, but for me, that wasn’t really the case.

6. Remove unnecessary files and clean up

As the name suggests, I removed the leftover XML files like drawable resources, menu-related XML files, and many more.

Lastly, I refactored/cleaned the leftover parts of the application like AndroidManifestto hold just the MainActivity.

Closing Notes

Some parts of the application are still written in Java and that is fine by me as long as they hold strong and don’t need refactoring, they can stay untouched. I know that I can always refactor them to make a 100% Kotlin codebase.

The process of migration is fairly straightforward for an application of any size as long as you define a migration strategy and break it down into smaller steps.

For this application, it took me a long time and I could have easily created a new application from scratch in half the time I spent in the migration process and I could have skipped the challenges and the decisions I took in the process. What’s the fun in that?

The process can be shorter or longer depending on the state of your application.

Like if your code is written in Kotlin and views are in XML with MVVM architecture, then you would have to follow only the last two phases — migration and post-migration phase.

If you can take away anything from this article, I want you to take away one thing:

Write reusable stateless components and spend some time on the application architecture. If done correctly, it could make your life much easier during the entire process as after sometime you would be easily adding features and easily built components from previously built reusable components.

Related Topics: SOLID Design Principles, Composition over Inheritance

I really liked the entire migration process and I hope this article could help you.

Have you migrated your application to Jetpack Compose? Comment below your experience or reach out to me on Twitter or LinkedIn.

Thank you very much for reading the article. Don’t forget to 👏 if you liked it.

This article was originally published on proandroiddev.com on December 28, 2022

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog
In the world of Jetpack Compose, where designing reusable and customizable UI components is…
READ MORE
blog

How to animate BottomSheet content using Jetpack Compose

Early this year I started a new pet project for listening to random radio…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu