3.
Android Fundamentals
Written by Fuad Kamal
A lifestyle of various activities — such as training for cardio, strength and endurance — can help keep you healthy. Although they’re all different, they each have a specific purpose or goal.
Android apps are similar, in a way. They’re built around a set of screens, each of which is known as an Activity and built around a single task. For example, you might have a settings screen where users can adjust the app’s settings or a sign-in screen where users can log in with a username and password.
In this chapter, you’ll start building an Activity focused around the main screen for Kodeco Chat — and you’ll finally get to write some Kotlin code!
Here’s an example of what the final chat application will look like:
There’s quite a lot going on in this app. For now, you’ll start with the basics.
Exploring Activities
Ensure the app folder is expanded in the Project navigator on the left. Navigate to MainActivity.kt, which you’ll find in app/kotlin+java/com.kodeco.chat/MainActivity.kt.
Open the file, and you’ll see the following contents:
package com.kodeco.chat
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.kodeco.chat.ui.theme.KodecoChatTheme
// 1
class MainActivity : ComponentActivity() {
// 2
override fun onCreate(savedInstanceState: Bundle?) {
// 3
super.onCreate(savedInstanceState)
// 4
setContent {
KodecoChatTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Greeting("Fuad")
}
}
}
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
KodecoChatTheme {
Greeting("Fuad")
}
}
MainActivity.kt is where the logic for your chat screen goes. Take a moment to explore what it does:
-
MainActivityis declared as extendingComponentActivity. It’s your first and only Activity in this app. WhatComponentActivitydoes isn’t important right now; all you need to know is that subclassing is required to deal with content on the screen. -
onCreate()is the entry point to this Activity. It starts with the keywordoverride, meaning you’ll have to provide a custom implementation from the baseComponentActivityclass. -
Calling the base’s implementation of
onCreate()is not only important — it’s required. You do this by callingsuper.onCreate(). Android must set up a few things before your implementation executes, so you notify the base class that it can do so now. -
This line “composes” the given composable (everything that follows it in the braces
{}) into the activity. The content will become the root view of the activity. ThesetContent{}block defines the activity’s layout, where composable functions are called. Composable functions can be called only from other composable functions. Therefore, theGreetinginonCreate()is just another function, defined below that, but it’s also marked with@Composable. That makes it a Composable function.
Jetpack Compose uses a Kotlin compiler plugin to transform these composable functions into the app’s UI elements. For example, inside Greeting is a Text composable function that, in turn, is defined by the Compose UI library and displays a text label on the screen. You write Composable functions to define a view layout that gets rendered on your device screen.
You’ll learn much more about Jetpack Compose in Chapters 5, “Jetpack Compose”, and 6, “Advanced Jetpack Compose”.
These four lines are the key ingredients in creating Activities for Android. You’ll see them in every Activity you create. In the most general sense, any logic you add must come after calling setContent{}.
Note:
onCreate()isn’t the only entry point available for Activities, but it’s the one you should be most familiar with.onCreate()also works in conjunction with other methods you can override that make up an Activity’s lifecycle.This book covers some of those lifecycle methods. But if you’re curious to know more already, dive deeper at https://developer.android.com/guide/components/activities/activity-lifecycle.html.
Now, you know the basics of how Activities work. Throughout the chapter, you’ll add some properties and placeholder functions and explore their purposes.
Replace the entire contents of MainActivity.kt with the following:
package com.kodeco.chat
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Button
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Column {
var chatInputText by remember { mutableStateOf("Type your text here") }
var chatOutputText by remember { mutableStateOf("Press Send to update this text")}
Text(text = chatOutputText)
OutlinedTextField(
value = chatInputText,
onValueChange = {
chatInputText = it
},
label = { Text("Label") }
)
Button(onClick = {
chatOutputText = chatInputText
chatInputText = ""
}) {
Text(text = "Send")
}
}
}
}
}
Note: Sometimes, Android Studio won’t recognize new objects in your classes until you import the class definition. Android Studio shows this by highlighting the object in red.
To import the class definition:
• macOS: Click the object and press Option-Return.
• Windows: Click the object and press Alt-Enter.
You can also let Android Studio automatically handle imports for you when pasting code. Select Android Studio ▸ Settings ▸ Editor ▸ General ▸ Auto Import. In the Kotlin section, select the Add unambiguous imports on the fly checkbox. Click Apply in the bottom right.
Going through the code:
-
Columnplaces a column in the view. A column is a layout group that stacks all its contents, or “children”, in a vertical column. You’ll learn all about different view layout groups in Chapter 5, “Jetpack Compose”. - You define two variables,
chatInputTextandchatOutputText, and give them default values. Note the use ofby remember. Composable functions use the remember API to store an object in memory and update it during recomposition. You’ll learn more about this in Chapter 5, “Jetpack Compose”. - You define a simple
Textcomposable to display the contents of a chat message. -
OutlinedTextFieldis a styled TextField designed to accept user input from the keyboard. - Finally, you define a
Buttonthat, when tapped, updates the Text area with the text you type into the TextField.
That’s it! Run your app. Enter some text into the TextField and tap the send button. You should see the text at the top update with whatever you typed:
Congratulations! You’ve created the most basic chat app, which can accept input and display it back on screen.
A Blast From the Past: Activities, Fragments & Views
In the previous section, you built a simple layout using basic Kotlin code and the Compose UI framework. Android app development used to be much more complicated than this. You might run into legacy code in your journey as an app developer, so you should be familiar with how things used to be done as well.
Before Compose, your Kotlin code would live in an Activity as it does now. However, the entire layout typically would be defined in separate files using XML. Then, you would have to set an ID on each UI element you wanted to interact with from your Kotlin code, and you would need to write a bunch of code just to wire up the XML layouts with your application logic.
This UI logic changed quite a bit over the years as well. In the beginning, you would have to write code that looked something like this:
chatOutputTextView = findViewById(R.id.chat_output_text_view)
findViewById was error-prone and could lead to difficult-to-debug issues in the code. It’s also an expensive operation to run because findViewById must traverse the view tree hierarchy to find the view. So, over time, various strategies arose to wire the XML views and the Activities together. But a lot of cumbersome code and separate XML layouts remained. Also, note the reference to “R” — you’ll see this again soon. “R” stands for “Resource”.
Besides Activities, another type of UI container was called “Fragments”. The idea behind Fragments was to have reusable pieces of UI that defined and managed its own layout. An Activity would contain multiple Fragments. Although reusability might have seemed useful at the time, Fragments also introduced another layer of complexity for developers.
Both Activities and Fragments relied on XML layout files to render Views, the basic building blocks of the User Interface (UI).
With the advent of Jetpack Compose, you no longer need to deal with XML layouts or fragments unless you’re trying to update a legacy app. And if you are, Compose can integrate with legacy XML layouts and Fragments so you can update portions of your app in a stepwise fashion.
Compose follows the reactive programming model and, by its nature, is much more performant than the traditional XML layouts. Instead of declaring the layouts for your views in XML, you declare them programmatically in Compose UI. Subsequent chapters will delve into advanced design patterns and architectures that further enhance Compose’s ability to deliver exceptional performance, ease of use, debuggability and scalability for Android apps.
Managing Strings in Your App
You have gotten your first taste of writing code, have something resembling a chat app up and running and undoubtedly want to take things further.
One of the most important elements of any app is the text, or strings, displayed on the screen. As you move ahead in your Android development career, you’ll do well to master the ins and outs of using strings.
For instance, your app uses English labels, but that doesn’t mean it’s the only language your app can support. Supporting multiple languages in your app means people can use it even if they don’t understand English. You should consider supporting multiple languages when putting your app on the Google Play Store.
In the previous section, you set the chatInputText variable to use the string "Type your text here". This works well if you’re targeting only English-speaking users. But how would you support one, two or even a dozen other languages?
The answer to this is String resources.
In the Project navigator, expand res/values and open strings.xml. You’ll see a file with the following content:
<resources>
<string name="app_name">Kodeco Chat</string>
</resources>
strings.xml gives you a place to store all the strings used in your app. This helps to keep strings from being sprinkled throughout your code. Using strings.xml from the beginning is a better approach than adding it after completing the project, which would involve changing hard-coded text in many places.
strings.xml also makes it easy to add support for another language. Rather than hunting through the entire project to change all the strings, you copy the file and change it to hold the language translations of your choice.
For Kodeco Chat, you’ll use this file to keep your English text in a separate location. Update strings.xml so it contains all the strings needed for your app:
<resources>
<string name="app_name">Kodeco Chat</string>
<string name="chat_display_default">Messages will display here</string>
<string name="chat_entry_default">Type your text here</string>
<string name="send_button">Send</string>
<string name="chat_entry_label">Enter Chat Text</string>
</resources>
Now, in MainActivity.kt, update the label value of the OutlinedTextField from
label = { Text("Label") }
to
label = { Text(text = stringResource(id = R.string.chat_entry_label)) }
Make sure to add your import for stringResource. Remember earlier in this chapter when you saw code referencing “R”, for “Resource”? Android has resource files that store certain assets, such as strings and images. Back in the legacy days, another resource would have been the XML layout files.
stringResource is a Compose UI method that loads a string resource from a resource XML file. It takes a single parameter, id, which is the resource identifier. id is an integer, but you don’t need to know what the specific value is; instead, you can get Android to look it up for you by using that special “R” class. If you type in the code rather than copy-and-pasting it, after you type “R” and the “.” following it, Android Studio pulls up all the possible code completion options from the strings.xml file.
Delete everything inside the curly braces after “R”, then type “.” after the R.
Choose the first option, string, which references strings.xml. Then, type “.” and, again, Android Studio shows you all possible code completions, starting with a list of all the strings in strings.xml.
Choose chat_entry_label from the options to finish setting up the label.
Similarly, update the text displayed inside the button:
Text(text = stringResource(id = R.string.send_button))
Rerun the app if you don’t see it automatically update on your device or emulator. The strings for the button and the text field label are now retrieved from strings.xml, i.e., they’re localized!
But what about the other two strings that are the default values for the variables you defined?
Try updating chatInputText the same way:
var chatInputText by remember { mutableStateOf(stringResource(id = R.string.send_button)) }
Now, Android Studio shows a red highlight under stringResource.
- Mouse over the red highlight, and it shows you an error message: “Composable calls are not allowed inside the calculation parameter…”. These types of inline error messages are useful because they give you immediate feedback when you make a mistake while writing code.
- Second, a little red circle with an exclamation mark appears in the code editor’s upper-right corner. To the right of it is a number, in this case, “1”, which tells you how many errors are in the file. For warnings, which are things that you should correct but won’t stop the app from compiling, it instead displays a yellow warning triangle and a number showing how many warnings are in your code.
- Click that exclamation mark to open the Problems pane at the bottom of Android Studio. This pane also lists the error message.
- At the code editor’s right border, overlapping where a scroll bar might appear, are little red marks corresponding to each error message’s location in your code. For warnings, they would be yellow marks. Clicking any of these marks causes the code editor to jump to the place in the code where the error or warning occurs.
You’ve just learned the most basic debugging techniques. You’ll learn more about how to debug your apps later in this chapter. Sometimes, Android Studio will provide a helpful hint and even offer to fix the error or warning for you. But in this case, it doesn’t provide the solution.
Make the following changes:
- Before the other variable declarations, add this one:
val context = LocalContext.current. Make sure to add the import forLocalContext. - Change the remaining two variable declarations as follows:
var chatInputText by remember { mutableStateOf(context.getString(R.string.chat_entry_default)) }
var chatOutputText by remember { mutableStateOf(context.getString(R.string.chat_display_default)) }
In Android, the Context is an interface containing global information about the application environment. You’ll often encounter the need to reference Context for your code.
LocalContext.current references Context that Compose UI can use. Then, you use the getString() method to reference the R class.
getString() is an Activity-provided method that allows you to reference strings from the R file name or ID. Strings in strings.xml are given an ID during build time.
In this case, you’re retrieving the strings you added earlier to strings.xml.
Note: To learn more about String Resources in Android, review the Android developer documentation at https://developer.android.com/guide/topics/resources/string-resource.html, where you can also learn about string arrays and plurals.
Besides following the best practices for strings, your app is also ready for porting to another language. Sprinkling strings throughout your app is one of the worst types of technical debt to incur.
Technical debt reflects the extra development work that arises when a developer uses code that’s easy to implement in the short term instead of applying the best solution.
With that out of the way, you can return to developing Kodeco Chat.
Run your app. Everything should work as before, but with all your strings localized!
Debugging
In the previous section, you learned some basic debugging steps. But until now, you’ve used the “run app” button when you’ve run the app. Many times before, you didn’t even need to run it because Android Studio would update the running app even as you made changes. If you made a mistake — as in the previous section — while typing code that prevented the app from compiling, Android Studio alerted you to it right away. When you run the app or use the automatic preview feature, you won’t see errors that happen at run time. To see those types of error messages, you need to debug the app rather than just run it. Notice to the right of the “run app” button is another button that looks like a little bug:
Click that button now. The app builds and runs, but now it’s in debug mode and will show any errors occurring at runtime in the Debug pane at the bottom of Android Studio. You can also set breakpoints in your code. Breakpoints cause Android Studio to pause execution when it comes across them in your code. When you’re paused at a breakpoint, you can inspect the value of variables at the time of pause of execution and much more. Set a breakpoint now by clicking the line number of the line in the onClick() function for your button where chatInputText = "":
A red dot appears over the line number. Now, type “Hello 👋🏽” in the input text field and tap the send button. Android Studio pauses execution and highlights the line where the breakpoint was reached. You can also see the value of variables that were updated and are still in scope at this point in your code:
Resources
Besides strings, you can have other types of resource files in Android. In this chapter, you’ll use two of those: style resources and drawable resources. For a complete list of all the resource types, see the Android developer documentation at https://developer.android.com/guide/topics/resources/available-resources.
The App Manifest
Every Android app has an app manifest. It’s important because it tells an Android device everything it needs to know about your app.
Android is strict about its requirements for a manifest. The file must be named AndroidManifest.xml and located correctly in the project file hierarchy. Without this file, Android will not run your app.
On the left side of Android Studio, in the Project navigator, navigate to app ▸ manifests ▸ AndroidManifest.xml.
Note: The manifests folder in the sidebar is a virtual folder generated by Android Studio’s Android project view and is not directly related to anything in the file system. The actual file sits at the root of your app’s main folder inside app/src.
Also, for now, don’t worry about any warnings that appear in the manifest.
This is an XML-based file containing various tags. The main tags in this file are <manifest>, <application> and <activity>; you’ll use plenty more in chapters to come.
The <manifest> tag is the root element of the app manifest. You must declare all the other tags within this tag. You must also declare the package where your code sits within this tag. This security measure ensures that only your package is associated with this app.
The <application> tag contains app-specific information for the Android system, such as the icon to use for the app, the app’s name and what theme style it uses. This information tells Android how to present the app on the home screen and how to represent it in other areas, such as Settings.
Activities
Perhaps the most interesting tags are the <activity> tags. Every Activity in an app should have a corresponding tag in the manifest. This ensures your app runs Activities only from your app and none that might have come from elsewhere.
There’s a .MainActivity declared in there, with another tag, <intent-filter>, inside this declaration. This tells Android that MainActivity is the Activity to start when the app launches.
This happens because of the <action> and <category> tags inside <intent-filter>. You don’t need to fret about the details behind these tags at the moment — you’ll learn more about intents later in this chapter. What you need to know is the <intent-filter> is used to set your main Activity as the startup Activity.
When you create a project or use the new Activity wizard, Android Studio does the difficult work of updating the manifest for you.
If you prefer, you can edit the manifest manually. However, it’s best to let Android Studio do the hard work to reduce the chance of human error.
Intents
An Intent is an object used to indicate work or an action your app will perform in the future. Currently, your app has only one Activity. But if you were to add another, you would use an Intent to navigate between the two of them. Intents are incredibly flexible and can perform various tasks, such as communicating with other apps, providing data to processes or starting up another screen.
In fact, the Android system launches your app via an Intent. Remember <intent-filter> in the app manifest? The filter allows an Activity to be picky about what Intents it handles. In the case of your MainActivity, it only wants to handle Intents that attempt to launch it.
Permissions
Later in this book, you’ll find it necessary to add various permissions to your app, such as networking permissions in Chapter 7, “Advanced Architecture”. Guess where you add permissions in an Android app? Yep, that’s right — permissions also get listed in the manifest! Permissions get added with the <permission> XML tag. Permissions are integral to security and user privacy in Android. For example, if your app wants to track a user’s location, your app would need to list one of the location services permission in the manifest and properly request the permission from the user when appropriate. For much more in-depth coverage of permissions and security in Android, see Chapters 5, “Permissions”, and 6, “Security Best Practices”, of the Android App Distribution book (https://www.kodeco.com/books/android-app-distribution).
Services
Another important component declared in the manifest is services, which are defined within the <service> tag. You use services to implement things like processes that run in the background or communications APIs. For example, Kodeco Chat might want to fetch messages in the background even when it’s not the app currently running and then notify the user that it’s received new messages.
Foreground services perform actions the user can notice and must display a notification to the user when they run.
Background services perform things the user doesn’t directly notice, such as downloading data in the background so it’s available when the user brings the app to the foreground.
Themes
Notice the <application> tag in the manifest has the following attribute:
android:theme="@style/Theme.KodecoChat"
On a Mac, Command-click (or Control-click on a PC) “@style/Theme.KodecoChat”. Android Studio takes you into yet another type of resource XML file: themes.xml. Here’s a handy trick: If you mouse over the upper-left corner of Android Studio, you see an icon that looks like a target:
Click that target, and Android Studio selects the file you have open in the code editor, in the navigator pane on the left. This makes it easy to find where a particular file resides.
Here, you can see that the theme that’s been applied to this app is a subclass of a type of Material Design theme.
Key Points
Well done setting up your first Activity. It’s a skill you’ll use over and over in your Android career. To recap, you learned:
- What an Activity is.
- The role of the manifest and Intents.
- What the
onCreate()lifecycle method does. - How to keep strings in one place using
strings.xmland what Resource files are. - The basics of debugging your app.
Where to Go From Here?
With a small amount of code, you created a functional chat app while learning some foundational elements of building an Android app. Although this Activity is small, activities can get complicated as you add more Views. Later in this book, you’ll learn how to separate the logic code out of your Activities. That will leave mostly only Jetpack Compose UI code in your Activities, keeping your code separated into logical components that are easier to understand, scale and debug.
You also learned a bit about the old way of doing things in Android. For more information on legacy Android, see:
In this chapter, you covered basic concepts around debugging your app. As a professional app developer, you’ll likely spend more time debugging your apps than writing them. For a much deeper dive into debugging, see the book Android Debugging by Tutorials (https://www.kodeco.com/books/android-debugging-by-tutorials).
Finally, you saw how themes are declared in an Android App. For more information on Material Design, see the official Material Design page at https://material.io.
In the following chapter, you’ll learn how to import libraries with Gradle to use in your app.