Oof – look, that's a legacy codebase!
Tags: android, refactoring, kotlin, programming
Published: 2023-07-16 22:26:12

If you are an engineer who has been in the industry for over nine or ten years, you will likely have experienced projects that are not "in such great shape".

You know what I'm talking about. Those projects where the first engineer decided to implement an MVP architecture based on some random article they skimmed from Medium about using one Activity and custom views for each screen.

Then, when the Jenga tower was shaking on the brink of collapse, the engineer figured it was time to "go looking for a new adventure" thus, a new developer had to be hired!

The new dev figured that the previous one had no idea what they were doing, so they decided to flip things over – this time we go with a custom BottomSheetDialogFragment for each screen. Surely this time it'll work!

It didn't.

And yet again, this engineer had to go looking for new challenges. Repeat this 5 times over a period of 5 years and you no longer have a piece of software — you have a ticking bomb. You have a Coke that will explode in your hand as soon as you grab it from the shelves and leave you with a face full of shrapnel.

Now it is **your** turn. I will share a few things I have learned to help you deal with these situations. Hopefully, some of these tips might come in handy.

### Remove unused resources

**Issue**

You go through the codebase and more often than not see pieces of code that are completely grayed out, unused. There is no such thing as keeping code "just in case", you either use the code or you don't – keeping leftovers there is just going to confuse people, and it is probably a symptom of a deeper issue.

**Solution**

Android Studio has a "Remove unused resources" tool under Refactor


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fpfsvlgak2wh0i4pgzn4.png)

You can preview what the removal of the unused resources would look like and exclude some of those deletions if you don't feel comfortable enough with some of the things Android Studio picks.


### PR templates

**Issue**

So, the QA engineers complain that every fix your team submits is like a butterfly effect: it creates 3 new different bugs, or they say the fix/feature isn't even working at all. This is what I call late-stage "I don't give a flying fig" syndrome. The code base is so fragile and so prone to bugs that no one even cares to test stuff, or even run those changes locally.

**Solution**

Set a PR template for the team. It is not necessary to use a super-long annoying-to-fill form, just put two items to complete every time an engineer wants to submit a PR. A short description of what's in there, and a video walkthrough of the engineer explaining the changes and showing how the feature works.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/migg30ik151ng65pcjf8.png)


You know Slack lets you record and download videos? 


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rjfwxdg5umhvaoj1mkl2.png)

You can use that tool to record videos, then download them, and drag-and-drop them into the PR description. It's a piece of cake, and won't take more than 5 minutes. But if your engineers can't even find the time to whip up a 5-minute video to explain their work, well, that's a whole other cake.


### Kotlin basics

**Issue**

You go through the code and see things like mutable data types being used on re-assignable (mutable) variables

`var someLiveData = MutableLiveData<Something>()`

or 

`var someCollection = mutableListOf<Something>()`

or maybe you see data class structures abusing `var` properties

```
data class Something(
  var prop1: SomethingElse?,
  var prop2: SomethingElse?
  // ...
)
```

This is usually a symptom of engineers who haven't fully migrated from Java to Kotlin. It is important to fully understand concepts like mutability and nullability.

**Solution**

This is a symptom of a lack of Kotlin basics. I'd like to defer to a super-helpful article I read some time ago:

https://www.javacodegeeks.com/starting-with-kotlin-cheatsheet


### Tech Soup

**Issue**

Alrighty, in different corners of the app, you have screens that are either using ViewBinding, Kotlin Synthetics, Butterknife (because why not?), or Composables.

Some screens have the ViewModels communicate back to the UI using either LiveData, Flow, or Kotlin Channels, and some are using DataBinding.

Every engineer that passed through this project had their own unique genius way of doing things... because the guy before them was a complete idiot, obviously.

**Solution**

Try to find a balance between what _the team_ thinks the best practices are and what you already have there in the codebase. Schedule either weekly or bi-weekly meetings with the engineering team, go layer by layer through the architecture and decide what the best practices are going to be from now on. For example:

"Okay, we use Retrofit for the API calls, do we wrap the responses on a `Response` wrapper to carry over the status response or we don't care about it?"

"Okay, how do we have ViewModels communicate to the UI from now on? We have all these options, what do we use and why?"

"Okay, do we use Jetpack Compost or do we use something else as a UI framework?"

You go layer by layer, figure out what the available options are (please don't thrust yet another tool into the mix!), and pick one. If possible, document that somewhere, either Notion or a wiki or whatever, but do document it – agreeing on something and not writing it down is the same as doing nothing.


### Navigation Soup

**Issue**

It's like a choose-your-own-adventure book...except in the land of app navigation! Some parts of the app are using Navigation Graphs. Some are going for the one Activity, many custom views approach. Some are full-screen DialogFragments. Others are full-height BottomSheetDialogFragments. What will you choose for your app journey?

**Solution**

Same as above, sync with the rest of the team and pick one solution. I'd recommend that whatever you decide, you use Fragments. Think about all the third-party libraries out there that provide some sort of UI; they all do it either through Activities or Fragments. The goal for each flow of your app (the login flow, the account creation flow, the onboarding flow, etc.) should be exactly that — expose each of those flows as if you were sharing an SDK that would be used by other developers.

Of course, this requires a bit more technical brainstorming depending on the spaghetti you have at hand – I'd recommend that if the app has a base class for each screen (those awesome noodles of wisdom) you keep using them. Hear me out:

Let's say your app works using `BaseSomethingFragment`, each screen extends on this base class. Internally, `BaseSomethingFragment` extends from `BottomSheetDialogFragment` and it tweaks it so it prompts at full height.

Something you could do is to tweak this base class so it has a `FragmentViewContainer` and it uses a Navigation Graph internally, that way you can create the rest of the screens using Fragments, and eventually move every screen into bare Fragments instead of using that base class. 

Just an idea, it's not a foolproof plan, but it'll give you the chance to do things one step at a time.


### Isolate new code through modules

**Issue**

Given that the app is one large monolithic `:app` module, containing all the above items, how can we begin to implement our fresh approaches and start to separate the components?

**Solution**

Sunset the APP module. All the apps out there can be divided into different flows. For example, when the user goes through the login screens, that's the login flow – it might be a collection of 2, or 3 screens, but that's a self-contained flow. You might also have an onboarding flow, account creation flow, etc.

Each of these flows should be contained in a feature module (just a fancy name for a library module) and each of these feature modules should be completely independent of each other so we prevent dependency-spaghetti. You can do this step by step:

First, you have your monolithic app module

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ae9n73zcco5jeg2lt9ad.png)

Now, just to keep things tidy let's rename that module into `:app-legacy` just everyone knows that we aren't supposed to keep adding things in there.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7nzli3wuptdkx7j1d0rx.png)

We are going to create a new application module, namely `:company-app` and have it depend on the old `:legacy-app` module. We have also turned the `:legacy-app` module from an application module into a library module.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t1eqp0g4tgh5xyywrzzw.png)

Most likely, your old `:legacy-app` module had an `Application` class – in situations like these, the `Application` class is usually highly bloated with code. So, to start fresh you can have your new `:company-app` application module have its own `Application` class that extends from the old one. You can also mark the old `CompanyApp` as `@Deprecated` to prevent further things from being added in there.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/htp6cinp818vxe96klt5.png)

You might also have to move many plugins from the `:legacy-app` down into the new `:company-app` application module. For example, if you are using Firebase and `google-service.json` you might have to move these down there, as they need to be plugged into the application module.

Now you can create completely independent feature modules to either refactor existing flows or create new ones. Let's say we had an account creation flow that was rid of bugs and we want to extract it out into an isolated module so we can refactor things safely.  

First, we create a new library module for this new flow.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cbileexfgacdoy4t3x8g.png)


Now, we move all the classes, all the `Fragments`, all the stuff specific to that flow into that new library module. If something is reused between flows, let's say you have a `SharedPreferences` wrapper, that stays up in the `:app-legacy` module.

What about if we want to navigate between feature modules? Let's say we have another feature module for another flow of the app.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/efn3rczvamsqrxsehiic.png)


Well, you can use a Navigation class. You can declare an interface up in the `:app-legacy` that exposes the entry points for each of the screens in each flow. Something like:

```
interface MyNavigationProvider {
   fun getLoginFragment(context: Context): `BaseSomethingFragment`
   fun getUserProfileDetail(context: Context): Intent
   // ...
}
```

You can provide either Fragments or Intents, whatever fits your architecture best.

This interface would be implemented in the `NewCompanyApp` Application class, and since the `:company-app` module ties all the feature modules together it knows about all these classes.

If you ever need to jump from one screen in one feature module to another, the only thing you need to do is to cast the application context into the `MyNavigationProvider` interface that was declared up in `:app-legacy`

Something like:

`getApplicationContext() as? MyNavigationProvider` 

and that's it!

Now, don't think the job is done yet. This is the first step on a long road. Eventually what I try to do is turn the `:app-legacy` into a `:core` module, with just the things that are common to all feature modules. I try to extract the UI and everything else down into feature modules.

But this, at least, should be enough to provide your team with a way to create new flows independently.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m5tvhrh9d03e5nqm8z9m.png)

There are many things you can do after this, you can extract all your base UI into a `:design-system` library module, so you encapsulate the look-and-feel of your app in one single place, _but_ I would strongly recommend you first get the _basics_ working, then you can start thinking about adding extra stuff.


### Set a coding standard

**Issue**

Every PR someone submits seems to have more changes due to code formatting than actual substance - it's like a one-line bugfix needs 30 changes; one for the bug and 29 for imports turning into wildcard imports or tab-formatting. Who knew code formatting could be so complicated?

**Solution**

Either push some of the `.idea` files up into the repo, or share the code style configuration across the team.

### That's all

Finally, set expectations with everyone in the team, engineers and uppermanagement, lay all the things on the table so everyone is aware of what's going on and what needs to be fixed.

I'm sure most people will cry "micromanagment!" at some of my proposals, but hey, to each their own – you like to push code up and pretend it's working, that's your prerogative. That's not my thing.