We migrated an Image Cropping library to Kotlin Multiplatform in few easy steps!
Kotlin Multiplatform is a modern technology from JetBrains, that makes it possible to share code between different platforms, or write fully cross-platform apps for Android, iOS, Web, Desktop and more!
One of the most important advantages of KMP is the ability to migrate existing Android projects in a short time. Although the process might differ for some projects, most apps that use a modern stack of Kotlin libraries, and a clean architecture, will have a very smooth transition.
In this article, you will learn how we ported an existing android project, that led to the creation of this awesome library: Krop: Kotlin Multiplatform library for Image Cropping with Compose Multiplatform.
To better understand the topic, it is important to have a basic understanding about KMP. Please refer to the Official Introduction.
Backstory
During the development of our app using Compose Multiplatform, we received a task to quickly implement photo editing functionality for Android and iOS. The task included the ability to crop, rotate, flip and mirror images.
We decided that using a library will be the fastest solution. But we quickly realized that there is none with crop support for KMP. Therefore, we came up with a list of steps to tackle this issue with minimum effort:
1. Search for existing solutions for Android
2. Study and compare them
3. Make the final decision, pick one solution to migrate to KMP, or use as a reference to implement cross-platform functionality
Every step will be described separately in its respective section. We will explain some key-points and share the results.
Search for existing solutions for Android
As every developer, we started our journey by browsing the web. And since we are Android developers originally, we searched for existing libraries or snippets written in Kotlin.
Luckily, we found some decent libraries, but the ones that caught our attention the most were:
1. easycrop
2. Compose-Cropper
And since we found more than one option, we decided to generate some criteria and compare them.
Comparison
We created a list of criteria, which contain important points for our project and the migration process. Our goal is to have a smooth transition with minimal rewriting of the existing logic.
Here are the main criteria:
- Supported functionality: The library must support at least crop functionality. It will be a bonus if it supports rotation, flip and mirroring out of the box.
- Lightweight: Simple code, with no heavy dependencies and complicated algorithms, will keep the app light and also make the migration smoother.
- 100% Kotlin: Migrating libraries fully written in Kotlin is usually less time-consuming, and less prone to errors, since we keep the original code untouched.
- Jetpack Compose instead of Views/XML: Libraries using old Views and XML require extra steps. First, they need to be migrated to Jetpack Compose, and only then, to Compose Multiplatform.
- Clean Gradle build scripts: Updating the scripts is important before migrating. Projects that have odd scripts, custom tasks and build phases will make it harder.
- Clean and readable code: Less dependency on the Android SDK means smoother transition. Also, readable code is easier to port. If we understand the code, we can modify it correctly.
- All dependencies support KMP/CMP: Some libraries use dependencies that do not support Kotlin and Compose Multiplatform. This means we have to migrate those dependencies as well, or replace them with more suitable ones.
- Popular: Famous libraries usually have more users and test cases. Therefore, a library with good stars to issues ratio is favorable.
- Actively maintained: Some authors stop developing their libraries, answering questions and fixing issues. This means if we encounter some problems, there is no chance to get help.
Comparison results are demonstrated in this table:
The results are very similar. Both libraries are great and relatively easy to migrate. But they had some important differences, which made it more difficult to choose.
Final decision
In one hand, easycrop supports everything we need. Also, it is easier to port, since all the dependencies were already ported by Google and JetBrains. The code is simpler and cleaner. But the build script requires some updates.
In the other hand, Compose-Cropper is more popular, and actively maintained. This means we won’t be the only ones finding issues, and fixing them.
We quickly decided that for our current task, easycrop is more suitable. We are ready to maintain it in the future, if any issues are encountered.
Migration
Porting this library to KMP took part in two small steps:
1. Update build scripts and dependencies
2. Add KMP to the project and move code to commonMain
Every step will be described separately in its respective section. We will explain some key-points and share the results.
Update build script and dependencies
This step is fundamental, since using a fresh build script and dependencies makes the migration a lot smoother. Newer versions offer a cleaner and more stable API, which is reflected positively on the process.
The most important changes that had to be made were the following:
1. Update Gradle, Kotlin and AGP versions: At the time, we decided to stick to Kotlin 1.9.24 since 2.0 was still relatively new
2. Migrate to KTS, add versions catalog and bump dependencies: Dependencies had to be updated to the latest stable versions compatible with Kotlin 1.9.24
3. Move maven publishing scripts to convention plugins
Here’s an example from the code:
Maven publish logic was moved to PublishConventions.kt as a plugin.
All these changes can be found in this Pull Request.
Migrate the code to Kotlin Multiplatform:
The second and the final step is introducing KMP to the project.
Here are the most important changes that had to be made:
1. Add org.jetbrains.kotlin.multiplatform plugin to the project
2. Move the code to commonMain: Sometimes you can just rename the main sourceSet to commonMain, and you are good to go!
3. Move resources to commonMain
We started by creating a convention plugin to avoid repeating scripts.
Add multiplatform convention plugin
This class is similar to PublishConventions.kt that we used earlier:
As you probably noticed, we added more target platforms than we initially planned. That’s because easycrop had a very little dependency on Android, and we could port the library easily to iOS, Desktop, JS and WasmJS!
Kudos to the author for such clean code!
Update module’s build script to use the new plugin:
At this step, we synced the project and assembled the library to find any build errors.
Move the code to commonMain
Most classes were easily moved from main to commonMain without any issues. This, again, is due to the clean code that was initially written by the author.
Some exceptional classes were moved to androidMain, since they had platform-specific logic. This includes image sources that used ImageStreamSrc to retrieve data. They have a dependency on Android’s Uri, File and InputStream.
For other platforms, we moved ImageBitmapSrc to commonMain. It has a dependency on ImageBitmap, and is supported by Compose Multiplatform.
Here’s how it looks like now:
The code is 100% identical to the older Android version, no changes had to be made!
Some UI controllers relied on device orientation to draw the layout. And since such logic is also platform-specific, we had to use expect/actual to access platform code as the following:
That’s it!
Move resources to commonMain
Compose Multiplatform already supports XML drawables for all platforms. Therefore, moving resources was even easier. We just renamed the res folder to composeResources, and moved it to commonMain, then updated our code to use the new Res.drawable instead of R.drawable:
You can find all the changes in this Pull Request (Including the first section with build script)
Conclusion
Kotlin Multiplatform is still under development, and finding a library for each use-case is still challenging. But thankfully, we are able to port most of our existing Android libraries and use them in other platforms, if they meet certain criteria.
I hope this experience will help you and the Kotlin Community to increase open source solutions for the Multiplatform development.
This is my first article, I would appreciate your feedback!
Check Krop repository: https://github.com/tamimattafi/krop
Follow me on Github: https://github.com/tamimattafi