Joshua's Docs - Android Native Development Notes
Light

Note: This page focuses on native Android development, which in this context, means the Android OS, Java, Kotlin, Android XML, and Android Studio. It does not cover Flutter / Dart, or other non-native alternatives. If you are interested in alternatives to native Java / Kotlin android, I would suggest checking out:

Resources

I also have other notes pages on Android as part of this site, such as Android Studio, and Android Misc.

What & Link Type
JStumpp/awesome-android Collection
wasabeef/awesome-android-ui Collection
wasabeef/awesome-android-libraries Collection
android/architecture-samples Collection (Samples)
android/architecture-components-samples Collection (Samples)
Android Developers - Courses Educational Courses (many are free)
futurice/android-best-practices Collection (Tips)
MJ: Free Android Resources Collection

Assorted:

Kotlin Resources

What & Link Type
My Notes Section of this Page
Basic Syntax Cheatsheet

WebView

πŸ’‘ Reminder: If you are trying to use the internet in your app, you need to enable that permission (INTERNET) in your manifest, or else you will get a "Webpage not available" and net::ERR_CACHE_MISS error on all remote loadUlr() requests.

For many WebView setups, there is common boilerplate code that needs to be included for basic functionality. These often include:

WebView - Common Settings

Unfortunately, the default settings for WebView are not actually the same as the default settings for Android Chrome, so if you want your WebView to act more like the default browser, you will have to tweak quite a few settings.

Here is a partial list, with explanatory comments:

val webView: WebView = findViewById(R.id.mainWebView)

// JavaScript enabled - most sites need this!
webView.settings.javaScriptEnabled = true // JS enabled

// Web APIs
webView.settings.domStorageEnabled = true // localStorage
webView.settings.databaseEnabled = true // indexedDB

// Network / content settings
webView.settings.allowContentAccess = true // content URIs

// Misc - other settings to help emulate "desktop" mode
webView.settings.builtInZoomControls = true
webView.settings.displayZoomControls = false

// Enable mixed-content
webView.settings.mixedContentMode = WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE

WebView - Debugging

There are multiple ways to debug WebViews, in addition the Java / Kotlin / Android Studio side of things.

This docs page provides a good overview of the different options that are available.

If you want to use Chrome's remote debugging DevTools feature for WebView, there are really two sets of instructions you need to follow:

  1. Enabling WebView content debugging
    • Requires using WebView.setWebContentsDebuggingEnabled(true)
  2. Actually using Remote Debugging in Chrome

WebView - Loading Local Files

The recommended way to load local files and allow access from WebView is with WebViewAssetLoader. I have a full post detailing how to use this with Kotlin.

WebView - JavaScript

Android provides a fairly powerful set of methods for communication between the WebView JavaScript layer and your Android Java and/or Kotlin code. In fact, Android provides bi-directional communication; you can call JavaScript from Android, with methods like evaluateJavascript, and you can call native Android code from JavaScript, by exposing interfaces with methods like addJavascriptInterface.

WebView - JavaScript - Loading Issues

An issue that you might quickly run into when trying to call JavaScript code from Android is an error claiming the JavaScript objects you are trying to reference do not exist / are not defined.

For example:

Kotlin:

val name = "Joshua";
webView.evaluateJavascript("sayHi('${name}')", null);

Error:

Uncaught ReferenceError: sayHi is not defined

There are a few reasons why this can happen, and ways to fix:

  • The page has not finished loading and/or parsing JavaScript before you call evaluateJavascript

    • This is tricky, but common. Particularly because WebView.loadUrl starts an asynchronous process (loading the webpage), but the method itself has no callback or async listener!
    • There are three main ways to make sure the page is loaded and your JS is ready for calling before using evaluateJavascript:
      • Expose an Android interface to JS, then call Android from your JavaScript to let it know your method is now available. You would use the addJavascriptInterface method for this.
        • This is the only method that would 100% guarantee your specific JS is ready for access
      • Override the onPageFinished method on WebViewClient, and attach it to your WebView instance, so you can receive the page finished loading event and then run your evaluateJavascript code
      • Not recommended: Wrap all your evaluateJavascript strings in JS code that checks for page load before loading, and if not loaded, attaches a listener with callback
  • You forgot to make the object / variable globally scoped

    • When you use evaluateJavascript, it is kind of equivalent to running eval() in your browser console - the code will run at the highest scope (i.e. window). So, any variables / functions / objects that you are trying to access must be available within that scope
    Example
    <!-- 
    Bad: calling `sayHi` from Android will fail - due to the IIFE, sayHi is hidden from global scope
    -->
    <script>
    (() => {
    	function sayHi(name) {
    		alert(`Hello ${name}!`);
    	}
    })();
    </script>
    
    <!--
    Better: `sayHi` is declared without any scoping - should be accessible globally  / from window.sayHi
    -->
    <script>
    function sayHi(name) {
    	alert(`Hello ${name}!`);
    }
    </script>

WebView - Overriding User-Agent

To override the user-agent, don't use the header map argument of loadUrl() - instead use the WebSettings.setUserAgentString(myUAString) method.

WebView - Assorted Notes, Troubleshooting, and FAQs

  • How to enable MSE / DRM / EME content in a WebView?
  • Some network requests are bypassing my shouldOverrideUrlLoading hook! How do I intercept them?
    • AFAIK, that hook is really only used for initial page loads, not AJAX, which more and more websites use for a lot of requests. To catch all those, you need to hook into shouldInterceptRequest. For best results, you might want to use both, with a common shared function for matching requests.
  • How do I emulate "Desktop Mode" in WebView?
    • Unfortunately, there is no one-size-fits-all setting you can call. Different sizes use different mobile-vs-desktop detection patterns, so the best you can do is just try and match as many settings as you can to what they are expecting to see with a desktop.
    • The settings you will want to adjust are:
    • Another thing to try would be to emulate the actual pixel size of a desktop browser. I have not tried this, but this approach (scaling up and then down the WebView component), seems like a good idea to try

Kotlin - Where to Get Started?

Basics - Overview

Resources and Assets

Android has two main sub-systems for storing non-code files, such as images and multimedia files; resources and assets.

What to Use - Resources vs Assets

The main difference between resources and assets boils down to compile-time vs runtime.

Resources (/res folders) are handled at compile-time, and with that comes both advantages and limitations:

  • Built-in support for swapping out files based on the target system (API version, language, screen-size, etc.)
  • Ties in to the resource ID system - provides compile-time checking, compatible with methods that require IDs, etc.
  • Limitation: There are rules on what characters you can use in the filenames, and filenames get transformed into IDs, so you lose path syntax / accessing by directory.

In comparison, assets are basically left as-is at compile-time, and it is up the developer to pull them in correctly via paths during the runtime (e.g. via AssetManager). This means you don't get IDs, compile-checking, or automatic resource swapping.

πŸ’‘ The assets folder does not get automatically created in a new project; standard practice is to create it at the same level as /res, so src/main/assets instead of src/main/res. Or, use the IDE dialog to create it - File -> New -> Folder -> Assets Folder

There also is also kind of a third approach - using raw assets, but placing them in res/raw. This does not bypass the Resources system - but it does allow you to use openRawResource to get the raw data stream, which might not be the case if you use a standard sub-directory of res. See Providing Resources - "Accessing Original Files". Again, this still has all the other limitations of the Resources system.

πŸ“„ Some other helpful explainers: Assets or Resource Raw, StackOverflow.


File Access and Storage

πŸ“„ Docs entry-point: "Data Storage"


UI Development

Responsive Declarative UI

The new responsive standard is to use constraint layout as a base, and avoid deeply nesting layouts / elements, keeping everything as shallow as possible by constraining the position of elements based on those around them.

πŸ“Ό Intro video: Coding in Flow: ConstraintLayout Tutorial Part 1

UI Terms and Common Tasks

  • How to create a stylized container / rectangle / box?
    • You could use a custom View or ViewGroup
      • Or a simple wrapper, like CardView
    • You could use <shape> in XML (1)
    • You could use a layout component as the box / container
  • How to center component within constraintLayout?
    • There are multiple ways to do this, but the easiest is to just constrain each edge to the edge of the parent
      • This code centers both horizontally and vertically.
      • E.g. app:layout_constraintLeft_toLeftOf="parent"

Tying UI to Code

Here is the first codelab that seems to tie UI to interactivity via code.

The main way that UI is tied to code in native android is with Activities! An activity usually has a corresponding View that it loads when activated.

Additionally, once you are running an activity and have your view loaded, you can further tie UI to code through many different methods, including but not limited to:

  • Data Binding
    • A way to bind data values to components, instead of updating one by one
    • Declarative instead of programmatic / imperative
  • Manipulating values directly after using findViewById

To navigate between activities / layouts, you need to create a new Intent that targets the activity you want to switch to, and then pass it to startActivity.

For example:

val goToShopButton: Button = findViewById(R.id.go_to_shop_btn);
goToShopButton.setOnClickListener {
	val intent = Intent(this, Shop::class.java);
	startActivity(intent);
}

If you want to pass data between activities, you can use the intent.putExtra() method to append extra information to the intent before dispatching it. The method supports a lot of different call signatures, so you are not restricted to purely using just string as the payload. In the receiving activity, you can use the intent.getExtras and intent.get***Extra methods for reading in the data.

πŸ“„ For an in-depth tutorial, see the docs - "Starting Another Activity"

Customizing an Alert Dialog

Since some pre-build UI dialog methods have been deprecated, such as ProgressDialog, you might be wondering how to replicate them. Or maybe you just want to create a really advanced UI that shows up all within a modal.

The answer for either solution is passing a layout to AlertDialog, with setView.

Here is a more complete example, for showing a progress bar in an alert dialog.


Third Party Libraries and Dependency Management

In general, third-party libraries (aka dependencies), are managed through your build system. For modern Android development, with Android Studio, this means using the Gradle Build System.

If you are including third party libraries by copying the actual files (e.g. JAR files), the common practices for third party libraries is for them to live in app/libs, as opposed to under apps/src.

Importing JAR Files

If you are using Android Studio, adding a third-party JAR can be accomplished in just a few steps:

  1. Copy JAR file to app/libs/{LIB_NAME}/{FILENAME}.jar
    • Create libs directory if it does not already exist
  2. Right click on jar file within Studio, and select the Add as library option
    • This handles adding the implementation file('libs\\...') code to your Gradle config

Copying and pasting third-party code should be a last resort approach; whenever possible, using a dependency management system (preferably with versioning) should be preferred.


Kotlin Lang

🚨 In my personal opinion, Kotlin can be one of those languages that takes a substantial amount of effort to learn and get used to, especially if you are used to learning languages that "borrow" concepts from each other. One of the differentiating factors about Kotlin is that it is not afraid to throw some existing approaches out the window, while still utilizing / targeting the JVM. There is definitely a strong philosophy component with Kotlin that informs decisions the team has made with it, across all aspects of the programming language.

Kotlin - OOP

The class syntax in Kotlin can be a little hard to get used to, especially if you are less familiar / comfortable around Functional Programming (FP). Kotlin is rather different, even in comparison with Java.

For example, the idea of static methods or properties on a class pretty much... don't exist in Kotlin. Instead, you have to embrace using Companion Objects.

Kotlin - Class Syntax

πŸ“„ Main doc: Kotlin - Classes

Kotlin - Nested Objects

Unfortunately, Kotlin does not make nesting objects as easy as in languages like JavaScript. You cannot directly nest object declarations within each other - you have to declare them at the "top-level" separately, but then can combine in one:

object PassengerInfo {
	var count = 23
	var doneBoarding = true
}

object LuggageInfo {
	var count = 10
	var doneLoading = false
}

object Bus {
	var Passengers = PassengerInfo
	var Luggage = LuggageInfo
}

// Bus now contains nested objects, can access with dot notation
// Example: Log.v("Luggage Count", Bus.Luggage.count.toString())

Kotlin for TS/JS Devs

  • Calling the debugger
    • JS/TS:
      • debugger;
    • Kotlin:
      • ???, does not exist?
  • Fast way to fallback to string default, if value might be null
    • JS/TS:
      • Logical OR operator, e.g. couldBeNullOrString || "default value"
      • In general, this kind of stuff is way easier in JS due to dynamic nature, and type coercion
    • Kotlin
      • Can't use || with strings
      • Use the Elvis Operator
        • E.g. var definitelyString = couldBeNullOrString ?: "default value"
  • Quick inverted null check (e.g. only continue if not null - cast null to false, else true)
    • JS/TS
      • if (!!couldBeNull)
      • if (Boolean(couldBeNull))
    • Kotlin
      • Strict null check: if (couldBeNull != null)
  • Ternary operator
    • JS/TS
      • max = max < b ? b : max
    • Kotlin
      • No ternary operator, but there is shorter if syntax:
      • if (max < b) max = b
      • Or, written another way max = if (max < b) b else max
  • Array annotations
    • TypeScript
      • myArr: string[] = ["a", "b"]
      • myArr: Array<string> = ["a", "b"]
    • Kotlin
      • Syntax for array type declaration is not the same as Java (which is close to TS), and initializing is even stranger.
      • Example: val myStrArr: Array<String> = arrayOf("a", "b", "c")
      • Example: val nullableStrArr: Array<String?> = arrayOfNulls<String>(sizeNum)
  • Const
    • TypeScript (and JavaScript)
      • Use let or var for dynamic values, const for constant
    • Kotlin
      • Use var for dynamic, val for constant
  • Semicolons
    • TypeScript (and JavaScript)
      • Generally not necessary, but can cause problems in certain situations if omitted
      • Majority of users and docs do use them
    • Kotlin
      • Generally not necessary, but can cause problems in certain situations if omitted
      • Majority of users and docs do not use them
  • Non-Null Assertion Operator
    • TypeScript
      • Uses single trailing exclamation mark
      • const defNotNull = maybeNull!
    • Kotlin
      • Uses double trailing exclamation mark
      • val defNotNull = maybeNull!!
  • Template Strings
    • TypeScript (and JavaScript)
      • Use template string literals, with backticks for enclosing and ${} for pulling in JS values
      • Example: const greeting = `My name is ${nameVar}, what is yours?`
    • Kotlin
      • Similar, use double-quotes for enclosing, $ for variable prefix, and ${} for eval() chunk
      • Example: val greeting = "My name is $nameVar, what is yours?"
      • If you are trying to use dot notation to access a property, make sure to wrap in ${} instead of just using the dollar sign prefix by itself

Kotlin Coroutines, Threading, Async, and Concurrency

Currently, everything I wrote in this section was written after only a few hours of learning and using Kotlin Coroutines / threading stuff, so take that into consideration

Coroutines and related lower-level code can be a little hard to parse, especially if you are a JavaScript developer that is used to the (IMHO) pleasant syntax of async / await and Promises.

Furthermore, there are many different approaches to threading and concurrency within Kotlin, Java, and Android. Some things that work in Kotlin outside of the Android OS, will crash if running inside an App (see below).

🚨 WARNING: Something that is easy to miss is that Kotlin, the language, is its own thing outside of Android. Why is this especially important for threading code? Well, the Kotlin docs don't really callout example code that would crash an Android app, and also don't discuss best practices for Coroutines that are specific to Android. See below for details.

Kotlin Coroutines on Android

This is mentioned above, but a really important distinction to make is that Kotlin != Android, in that the Kotlin language can run outside of just Android, and certain Coroutine patterns that work elsewhere might not necessarily work inside of Android, where, for example, you have a main UI thread.

πŸ“„ The Android Docs have a sub-page on using Kotlin Coroutines with Android. This is a must-read if you are planning to use Coroutines with Android, and you are probably going to need to install androidx.lifecycle.* dependencies as outlined in the doc.

A good example of this is that the Kotlin docs often use runBlocking {} as a the bridge between an area of non-coroutine code and a new coroutine context, but if you use this on Android (from the UI thread), it will crash your app 100% of the time!

I ran into the above issue with suspendCoroutine and wanted to blame that method for causing my app to hang, but it was really that I had used runBlocking when I should have used lifecycleScope.launch.

Here are some additional resources on using Kotlin Coroutines specifically with Android:

Kotlin Coroutines - Capturing Callback Values

A common issue with coroutines is how to capture and pass back the value returned from a callback. Because the callback shares a different scope, and can't be awaited, you need something to signal from inside the callback. Luckily, there are a few options available. The one I'll discuss is the one that seems to be most recommended, and it is using the suspendCoroutine method to wait for a value to be resolved.

It works similar to JavaScript Promises, where KT resume is akin to JS resolve(), and KT resumeWithException is close to JS reject().

suspend fun getJsResult(webView: WebView, jsToEval: String): String {
	return suspendCoroutine<String> { continuation ->
		// evaluateJavascript uses a callback to return the serialized result
		webView.evaluateJavascript(jsToEval) { evalResultStr ->
			continuation.resume(evalResultStr)
		}
	}
}

πŸ“„ Overview of this approach, plus use of infix: Smuts - "Escaping Callback Hell". Also, this StackOverflow answer.


Miscellaneous Questions and Answers

These questions (and answers) are somewhat random, but might be useful to some

  • Where do I place a new Kotlin class?
    • By default, Kotlin files actually go in src/main/java, same as Java files. If you want to change it to src/main/kotlin, you need to modify the sourceSets configuration.
    • To create a new class, you can
      • just create the file (MyClass.kt) in your app directory (e.g. src/main/java/com/{USER}/{APP}/MyClass.kt)
      • Main menu: File -> New -> Kotlin Class/File
      • Right click on src/main/java and then New -> Kotlin Class/File
  • Why do we have to call super.___() on overriding functions declared in main? Shouldn't our App's Main be the main handler for that action? For example, with onActivityResult, which would you need to pass the activity result back up via super?
    • Sometimes it really is not necessary, but it is common practice to include it anyways, since it takes barely any extra code, incurs no perf penalty, and might be eventually required in new scenarios.
    • See https://stackoverflow.com/q/11745366/11447682
  • How do I log something?
    • There is the android.util.log class, but the problem with that is that all the methods want strings - unlike JS's console.log which can take any form of input
      • In the case you want to log an object, map, array - anything other than string - you need to serialize it first yourself πŸ˜”
    • There are also alternative 3rd party logging libraries, such as xLog
  • How do I use a regular URL hyperlink (e.g. https://example.com) with Kotlin Doc (KDoc) @see blocks?
    • You can't. (😑)
    • Just use regular markdown, without @see prefix
  • Is there a way to log output to a file, as opposed to just Log.v type stuff?
    • Not really, at least not without writing some wrapper stuff
    • It's relatively easy to dump logcat or even just copy and paste though
  • Can I use app-wide global variables? How do I persist a state variable across multiple activities?
    • Answer: Nope, and it's not as straightforward as one might think.
    • Longer answer:
      • Although you can create a class to hold the values, or try to use the Application object, neither is recommended due to memory and/or app reloads
      • For persisting a value across activities, it is recommended to use shared preferences, or a local database (e.g. Room APIs)
    • Also, kind of
  • Is there a way to prevent an activity from reloading, or duplicating itself when triggered by a new intent? E.g., if A is already running, and a new intent fires that calls A again, don't create new instance and/or navigate.
    • Yes - use android:launchMode="singleTask" on the activity declaration within the manifest XML
      • This works great for an activity triggered by NFC detection
      • singleInstance works for most use-cases as well
    • A related topic is also saving instance state
  • How to hold a reference to this in a Kotlin coroutine, or other nested scope
    • In JS, this would be as easy as declaring const _this = this; and then referencing _this later
    • In Kotlin, you could capture the value of this, the same as above, but you can also use class reflection, if you are trying to get a reference to a class.
    • You can also avoid losing the correct value for this by making sure to explicitly provide a variable name when entering into a closure / lambda / etc.
  • How do I run code in a timed loop, similar to JavaScript's setInterval?
  • How do I track down parts of my code that are blocking the main thread? How do I debug the Choreographer: Skipped ___ frames! The application may be doing too much work on its main thread error?
    • If you already know that you have parts of your code that are doing heavy operations on the main thread, than the solution is to just rewrite those parts, but what if you aren't even sure what part of your code is causing this?
    • To actually figure out what sections of code are using up CPU cycles, and especially on main, use the Android Studio CPU profiler - you will need to hit record on both the profiler and the CPU sub-tab of the profiler

Markdown Source Last Updated:
Mon Jul 19 2021 01:41:12 GMT+0000 (Coordinated Universal Time)
Markdown Source Created:
Sat May 15 2021 17:28:56 GMT+0000 (Coordinated Universal Time)
Β© 2024 Joshua Tzucker, Built with Gatsby
Feedback