Caribou can help you track all sorts of migrations using its flexible rule engine. Here are some examples of migrations that it can help with. Remember that this list is not comprehensive but only a starting point to get you inspired on how you can use the tool to track anything you like! 😃
Contents
Android
- Migrating from Java to Kotlin
- Migrating to Jetpack Compose
- Introducing an MVVM architecture to your application
- Introducing an MVP architecture to your application
- Migrating from Timber to Kotlin Multiplatform Arbor
- Migrating away from RxJava
- Migrating away from Android Log class statements
- Migrating away from Gson
- Migrating away from Moshi
- Migrating away from findViewById/Butterknife/Kotlin synthetic imports
- Decreasing the size of your application: Migrating away from JPGs and PNGs
- Migrating away from the Volley networking library
- Migrating away from Retrofit
- Migrating away from Data binding
- Moving files into another directory (e.g. introducing a feature module)
Java
Ruby
Web development
Other
Migrating from Java to Kotlin
Kotlin has been the officially recommended language for building Android apps for a couple of years now. Moving to the language is quite simple as there is an automated converter tool built inside Android Studio that can help you easily convert your project. That, however, will probably not result in the most idiomatic use of the Kotlin language! So please, do take your time to learn Kotlin and migrate gradually to get the best results!
To track this migration on Caribou you can use the following rules:
As you can see, all we are doing is defining a single file group with one rule that matches against all .java
files.
Then we are just setting the migration end criteria so that the migration is considered done when there are no more
files in that file group.
Tests first
Some engineering teams prefer migrating their tests classes first before jumping into anything else. To track the conversion of your tests from Java to Kotlin on Caribou you can use the following rules:
As you can see, we need to create a file group that contains 2 rules:
- One rule to match all files that end in
..Test
(as by convention all test classes in Java end in..Test
). - One rule to match all files with extension
.java
.
As these 2 rules are in the same file group, the result is files that end in ..Test
and have the extension .java
.
The final bit is defining the migration end criteria for this migration. All that we need to do is to say that we want
all of these files to be removed, i.e. the number of files in file group A
to be 0
.
Migrating to Jetpack Compose
Jetpack Compose is the new kid on the block, bringing declarative UIs to Android, and removing the need for declaring the layout composition of your application screens using XML! Tracking your compose migration on Caribou might depend on your migration strategy but here are some suggestions that you can experiment with:
As you can see we are using a variety of rules to capture the migration:
-
First, we create a file group that matches against all
XML
files in thelayout
resources directory, as with Jetpack Compose you will be replacing those with Kotlin code instead. -
Secondly, we create a file group that matches against all files ending in
..View
, which are typically your custom Views. We also search for files that contain theimport android.utilAttributeSet
import statement, which typically exists in these custom views. -
Finally, we create a file group for all the files that end in
..Fragment
. Hopefully, with Jetpack Compose you won’t need any Fragment classes! 🤞
Then we are defining the migration end criteria so that the migration is considered done when there are no more files in any of those file groups.
Introducing a Model-View-ViewModel (MVVM) architecture to your application
The Model-View-ViewModel is the
recommended architecture by Google
for Android these days. Tracking it on Caribou will depend on what your project looks like, but assuming you currently
have one Fragment
class per screen, and you want to end up with a ViewModel
class for each Fragment
, you can track
the introduction of MVVM
in your project with the following rules:
As you can see, we are defining two file groups, one that matches files ending in ..Fragment
and one for files ending
in ..ViewModel
. Then we are defining the migration end criteria so that the migration is considered complete when all
the files ending in ..Fragment
have corresponding files ending in ..ViewModel
. The interesting thing that is
happening in the background is that Caribou will try to match files with the same naming pattern together, so
a PaymentsFragment
will be considered migrated when there is a matching PaymentsViewModel
class, and not any other
class that ends in ..ViewModel
(like for example a SupportViewModel
class). Once every Fragment
has a
matching ViewModel
the migration will be considered complete.
Introducing a Model-View-Presenter (MVP) architecture to your application
The MVP architecture is one of the simplest architectures that one can set up in their application. It started getting
popular around 2016 but was superseded by MVVM when Google announced
that MVVM
was its preferred architecture for building Android apps. Even to this day MVP
might make sense for simple
applications as it is easy to set up and has a small learning curve. Tracking it on Caribou will depend on what your
project looks like, but assuming you currently have one Activity
class per screen, and you want to end up with
a Presenter
class for each Activity
, you can track the introduction of MVP
in your project with the following
rules:
As you can see, we are defining two file groups, one that matches files ending in ..Activity
and one for files ending
in ..Presenter
. Then we are defining the migration end criteria so that the migration is considered complete when all
the files ending in ..Activity
have corresponding files ending in ..Presenter
. The interesting thing that is
happening in the background is that Caribou will try to match files with the same naming pattern together, so
a PaymentsActivity
will be considered migrated when there is a matching PaymentsPresenter
class, and not any other
class that ends in ..Presenter
(like for example a SupportPresenter
class). Once every Activity
has a
matching Presenter
the migration will be considered complete.
Migrating from Timber to Kotlin Multiplatform Arbor
Everyone is excited about Kotlin Multiplatform and so are we in the Caribou team! If you want to track the removal of the Timber logging library and transition to a similar library that has Kotlin Multiplatform support (like Arbor) on Caribou you can use the following rules:
As you can see, for this migration we are tracking all the import statements for the Timber library (import timber
)
across our repository in a single file group. Then we are just setting the migration end criteria so that the migration
is considered done when there are no more files in that file group.
Migrating away from Android Log class statements
If your project is slightly older, then parts of it might still be using the good old android.util.Log
class! If you
want to track removing it from your project on Caribou you can use the following rules:
As you can see, for this migration we are tracking all the import statements for the Android SDK Log
class (import android.util.Log
) across our repository in a single file group. Then we are just setting the migration
end criteria so that the migration is considered done when there are no more files in that file group.
Migrating away from Gson
Gson has served the Android community well for several years, but these days there are better alternatives out there, especially for Kotlin projects. One of them is Moshi, while the newest kid on the block is a library by Jetbrains called kotlinx.serialization, which has support for Kotlin Multiplatform ❤️. To track the removal of Gson you can use the following rules:
As you can see, for this migration we are tracking all the import statements for the Gson
library (import com.google.gson
) across our repository in a single file group. Then we are just setting the migration
end criteria so that the migration is considered done when there are no more files in that file group.
Migrating away from Moshi
Similarly to the migration away from Gson, to track the removal of Moshi you can use the following rules:
As you can see, for this migration we are tracking all the import statements for the Moshi
library (import com.squareup.moshi
) across our repository in a single file group. Then we are just setting the
migration end criteria so that the migration is considered done when there are no more files in that file group.
Migrating away from RxJava
RxJava started to get widely used in Android projects around 2016, with many projects being full of RxJava up to this day. With all the benefits it brought, it also introduced quite a bit of complexity and a steep learning curve in many codebases, which led to the Android community welcoming Kotlin Coroutines as an alternative over the last few years. If you want to track the removal of RxJava on Caribou or you just want to upgrade from one version of RxJava to another check the rules below:
Removing RxJava 1
RxJava 1 reached its end of life as of March 31, 2018, with the recommendation to move to newer versions of RxJava, either RxJava 2 or RxJava 3. To track the removal of RxJava 1 from your project on Caribou, you can use the following rules:
As you can see, for this migration we are tracking all the import statements for the RxJava 1 library (import rx
)
across our repository in a single file group. This package name is unique to RxJava 1, as the other versions of RxJava
have different package names:
- RxJava 1:
import rx
- RxJava 2:
io.reactivex
- RxJava 3:
io.reactivex.rxjava3
Then we are just setting the migration end criteria so that the migration is considered done when there are no more files in that file group.
Removing RxJava 2
RxJava 2 reached its end of life as of February 28, 2021, with the recommendation to move to the latest version of RxJava, RxJava 3. To track the removal of RxJava 2 from your project on Caribou, you can use the following rules:
As you can see, we are searching for files that have contents that match this regular
expression: (?!.*rxjava3)import io\.reactivex
. This is because there’s an overlap between the RxJava 2 and RxJava 3
package namespace as can be seen here:
- RxJava 1:
import rx
- RxJava 2:
io.reactivex
- RxJava 3:
io.reactivex.rxjava3
To only target files that contain RxJava 2 for this migration, we need to use a regular expression with a
negative lookahead
where we are only matching against files that contain the io.reactivex
part, but not the rxjava3
part. Then we are
just setting the migration end criteria so that the migration is considered done when there are no more files in that
file group.
Removing any RxJava version
If you want to remove any RxJava version then you can track this migration on Caribou using the following rules:
As you can see, for this migration we are tracking all the import statements that either contain import rx
or
contain import io.reactivex
, by using a regular expression import (rx|io\.reactivex)
. Then we are just setting the
migration end criteria so that the migration is considered done when there are no more files in that file group.
Migrating away from findViewById/Butterknife/Kotlin synthetic imports
Android has a long history of attempting to bridge the XML and the Java/Kotlin world. First the findViewById function, then Butterknife and more recently the Kotlin compiler synthetic imports plugin and View binding. Here are some suggestions for tracking these migrations on Caribou:
Moving away from the usage of findViewById function
As you can see, for this migration we are tracking all the instances of the findViewById
function call in a single
file group. Note that we are only searching for findViewById(R.id
. Then we are setting the migration end criteria so
that the migration is considered done when there are no more files in that file group.
Moving away from Butterknife
As you can see, for this migration we are tracking all the import statements for the Butterknife
library (import butterknife
) across our repository in a single file group. Then we are just setting the migration end
criteria so that the migration is considered done when there are no more files in that file group.
Moving away from Kotlin compiler synthetic imports
As you can see, for this migration we are tracking all the Kotlin compiler synthetic import
statements (import kotlinx.android.synthetic
) across our repository in a single file group. Then we are just setting
the migration end criteria so that the migration is considered done when there are no more files in that file group.
Decreasing the size of your application: Migrating away from JPGs and PNGs
It’s very easy to bloat your application with large images that take up many Megabytes of space each, and as there’s
a correlation between the size of the application and the number of downloads it gets
that’s definitely something you want to avoid ❗. Thankfully, on Android, you can replace your JPG
and PNG
images
with images using the WebP format that provides substantially smaller size
of images with lossless compression
(i.e., the images look exactly the same) starting from API level 18
(Android 4.3). In some cases images can also be replaced by
vector drawables, which are typically
only up to a couple of KiloBytes large, resulting in even bigger savings. To track the removal of these images from your
repository on Caribou you can use the following rules:
As you can see, we are defining three different file groups, one for each type of file that we want to remove, then we are defining the migration end criteria so that the migration is considered done when there are no more files in any of those file groups.
Migrating away from the Volley networking library
Volley is an HTTP networking library introduced by Google a few years back, and has fully been superseded by other networking libraries, most notably by Retrofit. To track the removal of Volley from your project on Caribou, you can use the following rules:
As you can see, for this migration we are tracking all the import statements for the Volley
library (import com.android.volley
) across our repository in a single file group. Then we are just setting the
migration end criteria so that the migration is considered done when there are no more files in that file group.
Migrating away from Retrofit
As cool as Retrofit is, some projects are starting to use Ktor instead for the Kotlin Multiplatform support that it provides. To track the migration away from Retrofit on Caribou you can use the following rules:
As you can see, for this migration we are tracking all the import statements for the Retrofit
library (import java.retrofit
) across our repository in a single file group. Then we are just setting the migration
end criteria so that the migration is considered done when there are no more files in that file group.
Migrating from JUnit4 to JUnit5
JUnit4 has served us well over the years, but JUnit5 has been out for a while now, and it’s a complete rewrite with lots of interesting new features! If you want to track the removal of JUnit4 from your project on Caribou, you can use the following rules:
As you can see, for this migration we are tracking the import statements for the JUnit4 @Test
annotation that is added
on top of every test. The JUnit5 has a @Test
annotation as well, but under a different
package (org.junit.jupiter.api.Test
) so they are not picked up by this rule. Finally, we are setting the migration end
criteria so that the migration is considered done when there are no more files in that file group.
Migrating away from Data binding
Data binding
has been out for a while, and the Android community has had a love it or hate it reaction to the library. In some
scenarios it might make things easier, but at the same times others would argue that it can introduce more complexity in
projects by putting more logic than required in XML
files and making things harder to debug. To track the migration away from Data binding on Caribou you can use the
following rules:
As you can see, for this migration we are tracking all the xml
files that contain the </data>
XML tag, which is
unique to data binding. Then we are setting the migration end criteria so that the migration is considered done when
there are no more files in that file group.
Moving files into another directory (e.g. introducing a feature module)
Modularization of Android projects is quite common as, among other benefits, it can improve your project build times and can also help introduce more clear ownership of the features in your organization. Projects modularizing their codebase sometimes already have some package structure for the different features that the application has, but all the features are in the same Gradle module. To track the act of moving these to separate modules on Caribou you can use the following rules:
(assuming that you have some files under
app/src/main/kotlin/com/awesomeapp/payments
, and you want to move that code
to features/payments/src/main/kotlin/com/awesomeapp/payments
)
As you can see, for this migration we are creating a file group that tracks all the files in the
app/src/main/kotlin/com/awesomeapp/payments
directory. Then we are just setting the migration end criteria so that the
migration is considered done when there are no more files in that file group. The number of files in this file group
will gradually decrease over time, as files get moved into the new feature module directory.
Introduce tests against specific classes of a project
Sometimes we want to make sure that classes that have a specific naming convention have tests against them. This isn’t really a migration, but you can easily use Caribou to track this with the following rules:
(assuming that you have some classes that end in ..Handler
(e.g.
PaymentsHandler
, SubscriptionHandler
), and you want to introduce tests for them, so classes that end
in ..HandlerTest
(e.g.
PaymentsHandlerTest
, SubscriptionHandlerTest
)).
As you can see, we are defining two different file groups, one for the files that end in ..Handler
and one for the
files that end in
..HandlerTest
. Then we are defining the migration end criteria so that the migration is considered complete when the
number of the files in the two groups match. The interesting thing that is happening in the background is that Caribou
will try to match files with the same naming pattern together, so a PaymentsHandler
will only be matched with a
PaymentsHandlerTest
and not with any other ..HandlerTest
class. For example, if you have 2 files currently in the
project: PaymentsHandler
and SubscrionHandlerTest
, then Caribou will suggest that there are 2 files missing for this migration to be considered
complete. Those files are the PaymentsHandlerTest
and the SubscrionHandler
.
Decommissioning a Ruby On Rails codebase
In this example, we will consider the scenario where a team wants to decommission or reduce the usage of a Ruby on Rails codebase. Let’s start by first considering an example where all the Ruby code needs to be removed.
Decommissioning all Ruby code
The way to do this is to create a migration with rules that match .rb
and .erb
files as shown below. The first rule
is matching files with extension .rb
and the second one matches files with extension .erb
. The migration is
considered complete when both rules do not match any files, i.e. all .rb
and .erb
files are removed.
Decommissioning only parts of a Ruby codebase
The second scenario to consider is if it is not possible to remove all the Ruby files, i.e. some part of the Ruby codebase won’t be migrated. In this case, there are two options. The first option can be used if the Ruby files that need to be migrated are in specific directories. In this case, the rules can be set to be directory specific as shown in the image below:
In this case, the rules are set to match Ruby files that are in a directory called /payments/
. Once all the Ruby files
in this directory are removed, the migration will be considered done.
If it is not easy to segment the Ruby files that need to be migrated based on directories, then you can set the
migration end criteria to an actual number of files that do not need to be migrated. In the example below, the migration
will be considered complete when there are less than 30 .rb
files and 12 .erb
files remaining in the codebase:
Moving from Javascript to Typescript
Typescript is seeing quite a bit of adoption over the last few years as the type safety that it provides is deemed as an important feature that larger dev teams can lean on.
To track this migration on Caribou you can use the following rules:
As you can see in the example above, we are defining two file groups: one that matches against all standard
JavaScript .js
files, and one that matches against .jsx
files, a very popular syntax extension that
the React framework introduced. After the file groups are configured,
we are setting the migration end criteria so that the migration is considered done when there are no more files in any
of the 2 file groups.
Removing usages of a deprecated/legacy function from a codebase
You can use Caribou to remove any type of reference to a function or class that you want to stop using in your project.
For this example, let’s assume that there’s a function called loginDeprecated
that you want to remove from your
project. To track this migration on Caribou you can use the following rules:
As you can see, we are simply defining one file group for the files that contain the function invocation. Then we are defining the migration end criteria so that the migration is considered complete when the number of the files in the file group reaches zero.
If you are only looking to remove the legacy invocations only from specific files or directories then you will need to tweak your rules a bit:
Removing loginDeprecated
from all files whose names end in ..Controller
:
Removing loginDeprecated
from all files in the no/legacy/path
directory:
As a side-note, if the function that you want to remove is not sa unique as loginDeprecated
is, then you could explore
the possibility of renaming it to something unique before you start the migration so that it’s easier to track.