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:
- https://www.freecodecamp.org/news/how-to-make-sense-of-the-many-android-layouts-693b262706e0/
- https://developer.android.com/guide/components/activities/activity-lifecycle
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" andnet::ERR_CACHE_MISS
error on all remoteloadUlr()
requests.
For many WebView setups, there is common boilerplate code that needs to be included for basic functionality. These often include:
- Adding the
INTERNET
permission to yourAndroidManifest.xml
file - Enabling JavaScript support, via the
WebSettings.setJavaScriptEnabled
method - Flipping a bunch of WebView settings on or off, depending on your needs (see below, common settings)
- For more advanced functionality, using custom
WebViewClient
and/orWebChromeClient
subclass instances (you can, and sometimes should, use both) - Call
myWebView.loadUrl()
to actually initiate loading
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:
- Enabling WebView content debugging
- Requires using
WebView.setWebContentsDebuggingEnabled(true)
- Requires using
- 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 onWebViewClient
, and attach it to your WebView instance, so you can receive the page finished loading event and then run yourevaluateJavascript
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
- 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
- This is tricky, but common. Particularly because
-
You forgot to make the object / variable globally scoped
- When you use
evaluateJavascript
, it is kind of equivalent to runningeval()
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>
- When you use
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?
- You have to hook into (and allow) requests for the
RESOURCE_PROTECTED_MEDIA_ID
permission, through theonPermissionRequest
override - You can see how this is done from this SO answer, or in the
react-native-webview
library
- You have to hook into (and allow) requests for the
- 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.
- 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
- 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:
- User-Agent (
userAgentString
) loadWithOverviewMode
useWideViewPort
- And also see "WebView - Common Settings"
- User-Agent (
- 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?
- https://developer.android.com/kotlin
- https://developer.android.com/codelabs/build-your-first-android-app-kotlin#0
Basics - Overview
- How to add new screens / UI elements
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
, sosrc/main/assets
instead ofsrc/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
orViewGroup
- Or a simple wrapper, like
CardView
- Or a simple wrapper, like
- You could use
<shape>
in XML (1) - You could use a layout component as the box / container
- You could use a custom
- 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"
- There are multiple ways to do this, but the easiest is to just constrain each edge to the edge of the 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
Navigating to an Activity / Layout
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:
- Copy JAR file to
app/libs/{LIB_NAME}/{FILENAME}.jar
- Create
libs
directory if it does not already exist
- Create
- 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
- This handles adding the
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?
- JS/TS:
- 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
- Logical OR operator, e.g.
- Kotlin
- Can't use
||
with strings - Use the
Elvis Operator
- E.g.
var definitelyString = couldBeNullOrString ?: "default value"
- E.g.
- Can't use
- JS/TS:
- 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)
- Strict null check:
- JS/TS
- 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
- JS/TS
- 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)
- TypeScript
- Const
- TypeScript (and JavaScript)
- Use
let
orvar
for dynamic values,const
for constant
- Use
- Kotlin
- Use
var
for dynamic,val
for constant
- Use
- TypeScript (and JavaScript)
- 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
- TypeScript (and JavaScript)
- Non-Null Assertion Operator
- TypeScript
- Uses single trailing exclamation mark
const defNotNull = maybeNull!
- Kotlin
- Uses double trailing exclamation mark
val defNotNull = maybeNull!!
- TypeScript
- 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?`
- Use template string literals, with backticks for enclosing and
- 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
- Similar, use double-quotes for enclosing,
- TypeScript (and JavaScript)
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 usedrunBlocking
when I should have usedlifecycleScope.launch
.
Here are some additional resources on using Kotlin Coroutines specifically with Android:
- Android docs: "Use Kotlin coroutines with Architecture components"
- Shekhar: "Mastering Kotlin Coroutines in Android - Step by Step"
- Singh: "Kotlin Coroutines Tutorial for Android"
- MΓ©tais: "Modern Concurrency on Android with Kotlin"
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 tosrc/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 thenNew -> Kotlin Class/File
- just create the file (
- By default, Kotlin files actually go in
- Why do we have to call
super.___()
on overriding functions declared in main? Shouldn't our App'sMain
be the main handler for that action? For example, withonActivityResult
, 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'sconsole.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
- There is the
- 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 callsA
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
- Yes - use
- 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.
- In JS, this would be as easy as declaring
- 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