question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Codebase Improvements

See original GitHub issue

I have some general suggestions (that I’m willing to put into practice via PRs) for improving FlorisBoard’s codebase. These include:

  • Cleaning up existing dependencies (some deps are unnecessary, for example, such as stdlib-jre7)
  • Using kotlinx-serialization instead of moshi (smaller runtime size, slightly faster, and no reliance on reflection)
  • Using Kotlin Gradle build scripts rather than Groovy build scripts and cleaning up build logic
  • Storing source files at source root rather than within single-nested subpackages (i.e. src/main/kotlin/App.kt rather than src/main/java/dev/patrickgold/florisboard/App.kt)

This list isn’t exhaustive-- there are other things that could be done too. As these are rather major changes, I wanted to seek approval first.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:2
  • Comments:8 (8 by maintainers)

github_iconTop GitHub Comments

3reactions
serebitcommented, Feb 9, 2021

The reason I went with Moshi is that I wanted to have a JSON deserializer which has a good range of features and is relatively performant. My first attempt was to use Gson but I quickly realized that Gson is a resource and performance hog. So I went with Moshi because it seemed more easy to st up that using kotlinx-serialization and was way faster. In general though I don’t have a good reason why we shouldn’t use kotlinx-serialization instead of Moshi, as long as the end result is still the same. And if it is slightly faster, it is even better 😃

Even the Gson maintainers don’t like Gson! It’s pretty awful. Moshi is a good runner-up, but it still does things mostly Java-style. kotlinx-serialization was pretty awkward to use pre-1.0, but the latest version is actually quite nice and reasonable to use so long as the code is architected for it. Plus, it has the benefit of using zero reflection, so the runtime is pretty light (about half the size of Moshi), and it’s a bit faster in deserialization (though not by a whole lot).

I definitely agree with cleaning up the build logic, though does Kotlin Gradle have any advantage in terms of build time over Groovy? From what I’ve seen the syntax slightly changes (it both gets better and worse at the same time IMO) but the build time is relatively the same.

Initial from-scratch compilation with build.gradle.kts is generally slower than build.gradle, as Gradle has to cache a compiled version of the script (in my testing, the overall fresh build time goes from 27s to 32s for this project). Build time overall after the script compilation has been cached is generally the same. As for syntax, it was originally pretty obnoxious, but it’s improved with time-- most commonly used libraries and plugins now have first-class support for the gradle.kts syntax. Here’s what the current configurations would look like when converted to an idiomatic gradle.kts syntax:

build.gradle.kts
plugins {
    base // adds clean task
}

subprojects {
    repositories {
        google()
        jcenter()
    }
}
settings.gradle.kts
rootProject.name = "FlorisBoard"

include(":app")

pluginManagement {
    repositories {
        gradlePluginPortal()
        google()
    }

    // allows the plugins syntax to be used with the android gradle plugin
    resolutionStrategy.eachPlugin {
        if (requested.id.id == "com.android.application") {
            useModule("com.android.tools.build:gradle:${requested.version}")
        }
    }
}
app/build.gradle.kts
plugins {
    id("com.android.application") version "4.1.1"
    kotlin("android") version "1.4.10"
    kotlin("android.extensions") version "1.4.10"
}

android {
    compileSdkVersion(29)
    buildToolsVersion("29.0.2")

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_1_8.toString()
    }

    defaultConfig {
        applicationId = "dev.patrickgold.florisboard"
        minSdkVersion(23)
        targetSdkVersion(29)
        versionCode(26)
        versionName("0.3.7")

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildFeatures {
        viewBinding = true
    }

    buildTypes {
        named("debug").configure {
            applicationIdSuffix = ".debug"
            resValue("string", "floris_app_name", "FlorisBoard Debug")
        }

        named("release").configure {
            isMinifyEnabled = false
            proguardFiles.add(getDefaultProguardFile("proguard-android-optimize.txt"))
            resValue("string", "floris_app_name", "@string/app_name")
        }
    }

    testOptions {
        unitTests {
            isIncludeAndroidResources = true
        }
    }
}

dependencies {
    implementation("org.jetbrains.kotlin", "kotlin-stdlib-jdk7")
    implementation("org.jetbrains.kotlin", "kotlin-reflect")
    implementation("androidx.appcompat", "appcompat", "1.2.0")
    implementation("androidx.core", "core-ktx", "1.3.2")
    implementation("androidx.preference", "preference-ktx", "1.1.1")
    implementation("androidx.constraintlayout", "constraintlayout", "2.0.4")
    implementation("com.google.android", "flexbox", "2.0.1")
    implementation("com.squareup.moshi", "moshi-kotlin", "1.9.2")
    implementation("com.squareup.moshi", "moshi-adapters", "1.9.2")
    implementation("com.google.android.material", "material", "1.2.1")
    implementation("org.jetbrains.kotlinx", "kotlinx-coroutines-core", "1.3.7")
    implementation("org.jetbrains.kotlinx", "kotlinx-coroutines-android", "1.3.7")
    implementation("com.jaredrummler", "colorpicker", "1.1.0")
    implementation("com.jakewharton.timber", "timber", "4.7.1")
    implementation("com.michael-bull.kotlin-result", "kotlin-result", "1.1.9")
    implementation("com.nambimobile.widgets", "expandable-fab", "1.0.2")

    testImplementation("junit", "junit", "4.12")
    testImplementation("androidx.test", "core", "1.3.0")
    testImplementation("org.mockito", "mockito-core", "1.10.19")
    testImplementation("org.mockito", "mockito-inline", "2.13.0")
    testImplementation("org.robolectric", "robolectric", "4.4")
    androidTestImplementation("androidx.test.ext", "junit", "1.1.2")
    androidTestImplementation("androidx.test.espresso", "espresso-core", "3.3.0")
}

In my opinion, the main benefit of the kts syntax is significantly better IDE support and a much more declarative method of writing build scripts. The initial compilation speed is also being addressed with the new Kotlin compiler and additional caching measures-- Gradle 6.8 introduces compilation avoidance based on the classpath ABI, for example.

This is the only suggestion I do not really agree with, as one can easily read the package name for a class based on all single-nested subpackages and I kinda like this approach. The only thing that would make sense to me is to rename it to src/main/kotlin/dev/patrickgold/florisboard/App.kt, as FlorisBoard is a 100% Kotlin Android app. This should only be changed though once I merge my suggestions-phase1 branch back into master to avoid a merge disaster 😃

This is fair-- while the root convention is the one recommended by JetBrains, it also goes against the conventions that Java has held for a long time.

2reactions
serebitcommented, Feb 10, 2021

There were several reasons why I avoided the kotlin.Result class, namely being that you can only return an object inherited from Throwable on failure, whereas normally you can return anything, regardless if the result resembles a success or failure. Also the currently used kotlin-result library has a much more closer syntax to the behavior in Rust (where I first learnt to work with results) and I like the Ok(…) and Err(…) approach a bit more.

I’d still advocate for using the kotlin.Result class anyway; to me, the reduction of dependency count is worth the minor change in syntax, considering how little the Result class is used in FlorisBoard’s codebase as is. That said, if I do submit a pull request for swapping kotlin-result for kotlin.Result, it’ll be separate from the main PR so you have the ability to reject it without rejecting all other changes along with it.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Case Study – Housekeeping and Codebase Improvements ...
Our startup develops artificial intelligence to help with such modeling and automate many routine tasks. Our AI already detects separate objects ...
Read more >
Tips For Improving a Large Code Base With A Small Team
But when faced with limited resources, it's important to prioritize, and understanding the cost of maintaining certain improvements is part of ...
Read more >
What my team and I learned when improving code quality in ...
In this article, we present a list of learnings and a general process for you to achieve a better code quality based on...
Read more >
Improvements since Codebase 4
Additions and improvements. There have been hundreds of improvements since Codebase version 4. The following are the most important: ...
Read more >
UI improvements to Codebase
Side-by-side diffs · Syntax highlighting within diffs · Improvements to our code review interfaces · Ability to restrict which IP addresses can ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found