From Messy to Masterpiece: How Coding Standards Rescued Our Kotlin App



"The app works, but the codebase feels like a haunted house."
That’s how our junior dev described our project. And honestly, they weren’t wrong.

When we started building our Android app in Kotlin, everything was exciting — fast screens, sleek UI, clever logic. But within a few months, we were buried in bugs, confused by code, and frustrated during every PR review.

The problem?
We had no coding standards. Just a jungle of inconsistent logic.

So we turned things around — not with a rewrite, but with a code clean-up campaign powered by Kotlin’s best practices. Here’s how we fixed our mistakes — and how you can too.


MISTAKE #1: Wild Formatting Wars

No indentation rules. Random semicolons. Files looked like ransom notes.

Before:

fun fetchuser():List<User>{return userlist;}

After (Using Kotlin style guide):

fun fetchUser(): List<User> {
    return userList
} 

Fix: We adopted 4-space indentation, removed semicolons, and formatted consistently with ktlint.

Benefit: Easier code reviews, cleaner diffs, and a more welcoming codebase.


MISTAKE #2: Confusing Naming That Told You Nothing

usrlist1, getU(), DataMngr... what do these even mean?

Before:

val usrlist1 = getU()

After:

val activeUserList = userRepository.getActiveUsers()

Fix: Clear, camelCase naming conventions for variables and methods, PascalCase for classes.

Benefit: Developers could read and understand code without decoding abbreviations.


MISTAKE #3: Ignoring Null Safety — Until It Crashed

Our app crashed more often than it launched. NullPointerException was a daily visitor.

Before:

val nameLength = user.name.length  // Crashes if name is null

After:

val nameLength = user.name?.length ?: 0

Fix: We embraced Kotlin's null safety features: ?., ?:, and avoided !!.

 Benefit: Crashes dropped significantly. Code became more predictable and safe.


MISTAKE #4: Writing Java in Kotlin

Our Kotlin files were still clinging to Java habits — verbose, repetitive, and clunky.

Before:

class User {
    var name: String = ""
    var age: Int = 0
}

After:

data class User(val name: String, val age: Int)

Fix: We used Kotlin-specific features: data classes, when, extension functions, and scope functions.

 Benefit: Shorter, cleaner code that actually looked and felt like Kotlin.


MISTAKE #5: Logic All Over the Place

Business logic lived in Fragments. API calls happened in click listeners. Total chaos.

Before:

button.setOnClickListener {
    val user = apiService.getUser() // Blocking call
    textView.text = user.name
}

After (Using MVVM):

viewModel.userLiveData.observe(viewLifecycleOwner) {
    textView.text = it.name
}

Fix: We moved to MVVM, separating UI, logic, and data.

Benefit: Code was modular, testable, and less prone to spaghetti messes.


MISTAKE #6: Manual Dependency Management

We passed objects manually or relied on global singletons. Testing? Impossible.

Before:

val repo = UserRepository(ApiClient())

After (Using Hilt):

@HiltViewModel
class UserViewModel @Inject constructor(
    private val repository: UserRepository
) : ViewModel()

 

Fix: Adopted Hilt for dependency injection.

Benefit: Cleaner setup, easier testing, no more “why is this null?” moments.


MISTAKE #7: Logging? More Like Guessing

We used Log.d everywhere. No tags. No structure. And we caught exceptions… but did nothing.

Before:

try {
    fetchUser()
} catch (e: Exception) {
    Log.d("TAG", "Oops")
}

After:

try {
    fetchUser()
} catch (e: IOException) {
    Timber.e(e, "Failed to fetch user from server")
}
Fix: Replaced logs with Timber and added proper error handling.

 Benefit: Debugging became a breeze. No more guessing games in production logs.


MISTAKE #8: Async Madness

We mixed threads, callbacks, and random GlobalScope.launch like a chaotic soup.

Before:

GlobalScope.launch {
    val data = api.fetchData()
}

After (Using coroutines properly):

viewModelScope.launch {
    val data = repository.getData()
}

Fix: Standardized coroutine usage with proper scopes and dispatchers.

Benefit: Smooth performance. No more ANRs or weird async bugs.


MISTAKE #9: Everything Was a Code Smell

Code smells went unchecked — long methods, unused variables, confusing logic.

Fix: Introduced ktlint and detekt in our CI/CD pipeline.

Benefit: The codebase started cleaning itself. Review time shrunk. Dev confidence grew.


MISTAKE #10: Too Many Comments. Or None at All.

We either overexplained the obvious or left mysteries in silence.

Before:

// this function gets user
fun getUser() { ... }

After:

/**
 * Fetches user from cache or API depending on freshness
 */
fun fetchUser(): User { ... }

Fix: We wrote meaningful KDoc only where necessary.

Benefit: Future devs (including us) actually understood the why — not just the what.


The Results: Cleaner Code, Happier Devs, Fewer Bugs

After applying coding standards:

  • Crashes dropped by 40%

  • Code reviews became faster and friendlier

  • New team members were productive by Day 2

  • We actually enjoyed working in the codebase


 Final Take: Clean Code is Kind Code

The biggest lesson?

Bad code isn’t written by bad devs — it’s written by rushed, unsynced teams.
Coding standards gave us structure. Structure gave us sanity. And sanity gave us speed.

If you're drowning in spaghetti code, start small:

  • Pick one mistake from above

  • Apply its fix today

  • Watch your app (and team) get better overnight


 Clean code isn’t about perfection. It’s about respect — for your future self, your teammates, and your users.

Let your codebase tell a story worth reading.



Comments

Popular posts from this blog

Kotlin Some important unsorted interview questions and answers

Build Bigger Android Apps, Faster: Why Git LFS Is a Game-Changer for Large Assets

Material Design Components based Android interview questions and answers