/android-playground

Personal playground for experimenting with new ideas in Android

Primary LanguageKotlin

Style guides

Best practices

How to show the root project's dependencies

# shows as a tree
./gradlew dependencies

# shows as a list
./gradlew androidDependencies

How to show the app module (aka project)'s dependencies

# shows as a tree
./gradlew app:dependencies --configuration <buildVariant>CompileClasspath
./gradlew app:dependencies --configuration debugCompileClasspath

# shows as a list
./gradlew app:androidDependencies
./gradlew app:dependencies --configuration <buildVariant>CompileClasspath | grep "^+---" | sort
./gradlew app:dependencies --configuration debugCompileClasspath | grep "^+---" | sort

# for more details
./gradlew help --task app:dependencies
./gradlew help --task app:androidDependencies

How to show modules (aka projects) on which the app module (aka project) depends

./gradlew app:dependencies --configuration implementation | grep '+--- project' | sort

How to show where a dependency in the app module (aka project) comes from

./gradlew app:dependencyInsight --configuration <buildVariant>CompileClasspath --dependency <group>:<name>
./gradlew app:dependencyInsight --configuration debugCompileClasspath --dependency org.jetbrains.kotlin:kotlin-stdlib

# for more details
./gradlew help --task app:dependencyInsight

How to build a debug APK

# Build the app module with the product flavor "foo" and the build type "debug"
./gradlew app:assembleFooDebug
# Build all the modules with all the build variants
./gradlew assemble

https://developer.android.com/build/building-cmdline#DebugMode

How to install a debug APK

# Install the app module with the product flavor "foo" and the build type "debug"
./gradlew app:installFooDebug
# Install with all the build variants
./gradlew install

Testing

How to run a local unit test

./gradlew test

How to run an instrumented unit test

./gradlew connectedAndroidTest

# Alternatively
# https://docs.gradle.org/current/userguide/command_line_interface.html#sec:name_abbreviation
./gradlew cAT

How to run a local unit test for a build variant and a module

./gradlew <module>:test<buildVariant>UnitTest

How to run an instrumented unit test for a build variant and a module

./gradlew <module>:connected<buildVariant>AndroidTest

Meaning of task clean in project-level build.gradle

The following custom task in the project-level build.gradle is to delete the project-level build directory when clicking the menu bar > Build > Clean Project.

task clean(type: Delete) {
    delete rootProject.buildDir
}

Predefined color resources in Android SDK

https://developer.android.com/reference/kotlin/android/R.color

Predefined string resources in Android SDK

https://developer.android.com/reference/kotlin/android/R.string

String

WebViewClient

How to get WebViewClient.onReceivedSslError() called

Open https://httpforever.com

How to get WebViewClient.onReceivedHttpError() called

Open https://httpstat.us/404

How to encode HTML entities

val encoded: String = "<>&'\"".htmlEncode() // &lt;&gt;&amp;&#39;&quot;

How to decode HTML entities

val decoded: String = String =
    Html.fromHtml("&lt;&gt;&amp;&#39;&quot;", Html.FROM_HTML_MODE_COMPACT).toString() // <>&'"

String.htmlEncode is a part of the Core KTX library and is syntactic sugar for TextUtils.htmlEncode .

How to convert AARRGGBB as Int to Color

val color: Color = Color.valueOf(0x11223344)

Release build

How to debug a release build

You can debug a release build only if you set both minifyenabled false and debuggable false.

// app/build.gradle
android {
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

            debuggable true
            signingConfig signingConfigs.debug
        }
    }
}
// app/build.gradle.kts
android {
    buildTypes {
        getByName("release") {
            isMinifyEnabled = false
            proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")

            isDebuggable = true
            signingConfig = signingConfigs.getByName("debug")
        }
    }
}

How to identify the class that acts up by minifyenabled true

Add the following to proguard-rule.pro.

-keepattributes SourceFile,LineNumberTable

How to close DrawerLayout when the user taps the Back button

/** isOpen and close() require "androidx.drawerlayout:drawerlayout:1.1.0" or higher. */
override fun onBackPressed() {
    if (drawerLayout.isOpen) {
        drawerLayout.close()
        return
    }
    super.onBackPressed()
}

OnBackPressedDispatcher

  • OnBackPressedDispatcher.hasEnabledCallbacks() returns true if both of the following are met.
    1. There is at least one callback registered with this dispatcher.
    2. Your Activity is being between onStart() and onStop() (both inclusive). If you override onStart() and/or onStop(), it is between super.onStart() and super.onStop().
    3. Even if you pass a Fragment to OnBackPressedDispatcher.addCallback(...), the Fragment's lifecycle does not affect OnBackPressedDispatcher.hasEnabledCallbacks().
  • If your Activitiy overrides onBackPressed() but you forget to call super.onBackPressed() in it, your callback will never be called.

How to define a custom view

class MyCustomView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyle: Int = 0
) : View(context, attrs, defStyle) {
    init {
        context.withStyledAttributes(
            attrs,
            R.styleable.MyCustomView,
            defStyle,
            0
        ) { // simpler than Theme.obtainStyledAttributes(...)
            // ...
        }
    }
}

CookieManager

val cookieManager = CookieManager.getInstance()

val url1 = "example.com"
val url2 = "example.com/foo"
val url3 = "sub.example.com"
val url4 = "https://example.com"
val url5 = "http://example.com"

cookieManager.setCookie(url1, "a=1")
cookieManager.setCookie(url2, "b=2")
cookieManager.setCookie(url3, "c=3")
cookieManager.setCookie(url4, "d=4")
cookieManager.setCookie(url5, "e=5")
cookieManager.setCookie(url5, "e=5!")

val cookie1: String = cookieManager.getCookie(url1) // a = 1; b = 2; d = 4; e = 5!
val cookie2: String = cookieManager.getCookie(url2) // a = 1; b = 2; d = 4; e = 5!
val cookie3: String = cookieManager.getCookie(url3) // c = 3
val cookie4: String = cookieManager.getCookie(url4) // a = 1; b = 2; d = 4; e = 5!
val cookie5: String = cookieManager.getCookie(url5) // a = 1; b = 2; d = 4; e = 5!

Other information in separate Markdown files

markdown

Use A rather than B for simplicity (except for Jetpack Compose)

A B Note
Activity.resources
Fragment.resources
View.resources
Activity.context.resources
Fragment.requireContext().resources
View.context.resources
Activity.getString(...)
Fragment.getString(...)
Activity.resources.getString(...)
Fragment.requireContext().getString(...)
Fragment.resources.getString(...)
String.toUri() Uri.parse(...)
bundleOf(...) Bundle().apply { ... }
CharSequence.isDigitsOnly() (old-school ways to check if a string contains only digits)
Context.withStyledAttributes(...) obtainStyledAttributes(...)
Intent.getStringExtra("foo") Intent.extras?.getString("foo") The same goes for other types.
view in fun onViewCreated(view: View) requireView() in fun onViewCreated(view: View)
Uri.encode(url) URLEncoder.encode(url, Charsets.UTF_8.name())