Chapter 1
IN THIS CHAPTER
Considering the potential of Jetpack
Defining the Jetpack elements
Working with the AndroidX package
Interacting with lifecycle-aware components
Jetpack is all about making things simpler, and who doesn’t like that idea? Book 2 tells you about how Kotlin makes things easier when compared to Java, yet Kotlin also provides new ways of doing things. You use Jetpack to make things easier and perform new tasks by
androidx.*
packages from the API to improve backward compatibility and ensure that you have the latest updatesOf course, you keep hearing about such features, but they never seem to be fully realized in most products. Jetpack does have some warts, too. This chapter helps you understand the benefits of using Jetpack, understand the various Jetpack components, look into the details of the android.*
packages, and gain insights into this whole thing about lifecycle management. As you discover all the really amazing things about Jetpack, you’re also introduced to a few of the less lovely aspects as well.
Development environments of all sorts are moving toward the elimination of coding that has nothing to do with what you want to do. Android is no different. For example, you do want to display the prices for your new product, The Amazing Widget, onscreen so someone can buy it and make you incredibly rich. You don’t want to worry about managing the memory used to display the text. The first is what you want to do (display text); the second is what you want to avoid (managing memory). The combination of Kotlin and Jetpack is designed to help you avoid doing any more work than you really have to in order to complete a task. Achieving this goal means eliminating activities like
None of these activities has anything to do with what you want to do with your app, but they’re important in making your app work. Look at Jetpack as a helper that takes all these obscure burdens off your back and lets you focus on the main event: writing that game app or creating a fraud detector.
Of all the Jetpack benefits, the one that developers like most is the reduction or elimination of boilerplate code. Boilerplate code is simply glue code that you use to explain how to do something to the compiler. Here’s an example of Kotlin code without Jetpack to automatically perform tasks in the background:
view.viewTreeObserver.addOnPreDrawListener(
object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
viewTreeObserver.removeOnPreDrawListener(this)
actionToBeTriggered()
return true
}
})
The code spends a great deal of time explaining things like what to create and where to add it. Here’s the same code with the boilerplate elements eliminated:
view.doOnPreDraw { actionToBeTriggered() }
That’s it! You don’t have to do anything more than say what you want to do, which is to tell Android that when it is about to draw a view tree (a listing of elements to draw when an activity receives focus in your app), it should perform the actions specified by the actionToBeTriggered()
function first. Eliminating the boilerplate code makes your code a lot easier to write now and read later. Of course, a lot of automation makes this happen, and you need to be aware of the consequences of using automation when writing code (see “The effects of automation” sidebar, later in this chapter). You can see more examples of how elimination boilerplate code is useful at https://jakewharton.com/https:/android-developers.googleblog.com/2018/02/introducing-android-ktx-even-sweeter.html
.
Background tasks are those tasks that you can perform asynchronously without the user monitoring them, such as downloading a document from online storage. Well-designed apps use background tasks so that the user doesn't just sit there, looking at the screen and hoping that something will eventually happen. When a task does need to complete before the user can move on, you use a ForegroundService
, which isn’t deferrable. Otherwise, you can use one of the background task services:
DownloadManager
: Downloading data requires time that the user won't want to wait. Using the DownloadManager
allows you to perform this task in the background to any destination, even if the destination is outside the app process. The download request can survive changes in connectivity and even device reboots. You must configure any required permissions, including Manifest.permission.INTERNET
, to use this service.AlarmManager
: In some cases, you need to perform tasks at a specific time. The AlarmManager
waits for the time you set and immediately begins running the task in the background, which can be disruptive when used inappropriately. If the host device is asleep when the alarm goes off, AlarmManager
can wake the device to perform the task. In addition, you can set it so that the host device can't go to sleep until the task completes. An alarm won’t survive a device reboot.WorkManager
: You use this service when you need to perform tasks in the background in a manner that won’t slow the host system down. The WorkManager
considers issues like network and battery resources as it completes the tasks you assign to it. It's possible to configure the WorkManager
to complete tasks even if the host app terminates unexpectedly or the device reboots. You typically use WorkManager
to perform tasks such as sending logs or analytics to backend servers or to sync app data with a server. The “Performing Background Tasks Using WorkManager” section of Book 3, Chapter 3 tells you more about working with this background task service.Each activity (full-screen user interface element) or fragment (self-contained partial-screen user interface element) in your app is a separate element. To move between them, the user clicks something or some piece of automation performs the move on the user's behalf. The act of moving between activities or fragments is called navigation, and it’s essential that the navigation works correctly. Every activity or fragment that a user can navigate to is a destination. Actions determine when and how the navigation occurs. Three elements comprise navigation in Android:
NavHostFragment
rather than a NavHost
.NavHost
or NavHostFragment
. This is the app element that actually swaps app content as the user moves through the app. You provide the NavController
with a path to take through the app (such as when you want the user to perform specific steps) or a destination to move directly from one place to another.ViewModel
object for a navigation graph to share UI-related data between the graph's destinations. For example, you need a ViewModel
to ensure that the display remains consistent when the user rotates the device.As with many elements of Android design, you use a special graphical editor to create a navigation graph. Figure 1-1 shows a simple sample consisting of a main activity and an output data fragment.
Mobile devices typically don't have huge amounts of memory, especially RAM, which means that memory is at a premium. In addition, the Android platform runs on the premise that free memory is wasted memory, so it tries to use as much available memory as it can at all times, as described at https://developer.android.com/topic/performance/memory-management
. Android also doesn’t create a swap file in storage to increase the apparent size of RAM because doing so tends to cause solid-state memory failures.
Kotlin already helps you prevent memory leaks, and some people are frankly amazed at how well it does (see the article at https://proandroiddev.com/how-kotlin-helps-you-avoid-memory-leaks-e2680cf6e71e
for details). By adding Jetpack to the mix, you create applications with less code and a consistent style that tends to reduce memory leaks as well. So memory issues that you’ve dealt with in the past when working with Android apps are likely to be greatly reduced.
To see how your app uses memory, you can use the profiler accessed with Run⇒ Profile ‘app’ in Android Studio. Starting your app will seem to take an enormously long time because the profiler slows things down quite a lot. When you select MEMORY from the profiler’s drop-down list, you see a graph of memory usage similar to the one in Figure 1-2. The “Benchmarking Your Application” section of Chapter 2 of this minibook gives more details on how this process works.
Configuration changes can ruin your app’s day by making the device environment unstable. A device configuration change occurs because of these types of actions:
Android responds by restarting the activity. It first calls onDestoy()
to stop the activity and then calls onCreate()
to restore the activity. The problem is the destroying part. Any state information in your app is now gone.
Fortunately, ViewModel
automatically saves state information for your UI, so you don't have to worry about re-creating the user interface from scratch. The problem is with your app’s data state. This information requires boilerplate code using onSaveInstanceState()
to ensure that your app comes back up into the same state that the user left it in.
Jetpack is a relatively large package, but you can easily divide it into four component areas: foundation, architecture, behavior, and UI. These component areas address specific application needs, such as benchmarking your application code or navigation to a particular destination. The following sections talk about each of these component areas and provide a quick overview of what each of them contains.
The foundation components are sort of like the basement of a house. You might not find them that interesting at first, but like a house, you really need a basement or at least a foundation for your Android app. The foundation components provide various kinds of low-level support and enable you to create exciting apps. Here’s a quick overview of each of the foundation components.
Android KTX (https://developer.android.com/kotlin/ktx.html
): Provides a set of special Kotlin extensions that help you create concise code using these Kotlin features:
You use these features to perform tasks such as animation, content management, database access, graphics, user location management, network access, OS access, and so on. The point is that you write less code, and the code you do write is smaller, so it creates less chance for experiencing errors. The “Working with Android KTX” section of Chapter 2 of this minibook provides you with additional details.
https://developer.android.com/topic/libraries/support-library/packages.html#v7-appcompat
): Not everyone will have the latest version of Android. This feature helps you create apps that degrade gracefully on older Android versions. You can see this feature in action in the Sunflower demo app (https://github.com/android/sunflower
).https://developer.android.com/cars
): Helps you create apps for your car. Book 5, Chapter 4 talks about how to use this feature in more detail.https://developer.android.com/studio/profile/benchmark.html
): This feature lets you determine how well your app actually runs. It allows you to check things like CPU and memory usage to create a more efficient app that users will like using. You can see a short version of this feature in action in the “Managing memory” section, earlier in this chapter. A fuller treatment appears in the “Benchmarking Your Application” section of Chapter 2 of this minibook.https://developer.android.com/studio/build/multidex.html
): Sometimes an app requires fancy packaging to make it work. The “Considering the Android Runtime (ART)” section of Book 2, Chapter 3 contains a description of the Dalvik Executable (DEX) file, which contains the compiled classes for your app. This enables you to bundle multiple DEX files within a single app. The main reason for using this feature is that your app has exceeded the 65,536 methods Android limit.https://developer.android.com/topic/security/data
): Every app requires security to keep data safe. Of course, data includes things like your identity, so you have a vested interest in making sure that your app is secure. The main reason to use this feature is to enhance the security of your app using security best practices developed by people who likely sit around and act paranoid all day. When it comes to security, paranoid is good. The “Addressing Security Issues” section in Chapter 2 of this minibook talks about security in detail.https://developer.android.com/topic/libraries/testing-support-library/index.html
): Most platforms today include some sort of testing framework to make app testing easier. This is the testing framework for Android. The “Testing Application Functionality” section of Chapter 2 of this minibook talks about testing issues in more detail.https://developer.android.com/tv
): Helps you create apps for your smart television. Book 5, Chapter 3 talks about how to use this feature in more detail.https://developer.android.com/wear
): Helps you create apps for your wearable device. Book 5, Chapter 2 talks about how to use this feature in more detail.Architecture components tend to give you access to data or help you manage program elements better. These components contribute directly to the app, but aren’t really the main event because the user rarely sees them directly. The user would feel the effect if these components failed to function, but invisibility is the focus of these components. We tell you about some of these components, such as ViewModel
and WorkManager
, earlier in the chapter, and here are some others:
https://developer.android.com/topic/libraries/data-binding/
): Unless you want to spend a lot of time individually coding data fields in your app, data binding is the best solution for making data available to the user. Data binding is the process of moving data from a source to a UI element so that the user can see and possibly modify it. Chapter 4 of this minibook provides additional information about using data in these ways: https://developer.android.com/topic/libraries/architecture/lifecycle
): Manages the lifecycles of both fragments and activities. You can discover more about this topic in the “Working with Lifecycle-Aware Components” section, later in this chapter.https://developer.android.com/topic/libraries/architecture/livedata
): Databases store information that changes; otherwise, they wouldn't be particularly useful. This component detects database changes and provides notifications that lets you update the user information with the new data. This method of working with databases has the following advantages over using other component types: https://developer.android.com/topic/libraries/architecture/navigation.html
): Provides the functionality needed for in-app navigation. The “Navigating between activities and fragments” section, earlier in this chapter, discusses this component in more detail.https://developer.android.com/topic/libraries/architecture/paging/
): Gradually loads information from a data source into your app to make data management more efficient. By loading only the information that the app needs at any given time, the app uses fewer resources and makes better use of resources like network bandwidth.https://developer.android.com/topic/libraries/architecture/room
): This component simplifies SQLite database access. It also creates a cache of app data to make accessing previously used data faster and easier.https://developer.android.com/topic/libraries/architecture/viewmodel
): Manages the UI-related data in your app in a lifecycle-conscious way. You find the ViewModel
discussed throughout this chapter and in many of the chapters to come.https://developer.android.com/topic/libraries/architecture/workmanager
): Provides the means for creating Android background jobs. You can discover more about background processing techniques in the “Managing background tasks” section, earlier in this chapter.How an app behaves is important because it has to interact with the rest of Android. These components help your app interact correctly with standard Android services like notifications, permissions, sharing, and the Assistant.
https://developer.android.com/training/camerax
): Enables your app to interact with the camera (or cameras) supported by a device. Cameras aren't just used for selfies. You can use them for all sorts of things, like reading barcodes on products or obtaining information based on visual cues. What makes this component different is that it relies on a use-case approach, and it’s lifecycle aware. You also don’t need to worry about differences in device capabilities because this component addresses them for you automatically. The “Adding Camera Support Using CameraX” section of Chapter 4 of this minibook tells you more about this component.https://developer.android.com/guide/topics/media-apps/media-apps-overview.html
): Allows the playback of video and audio digital media. The component provides controls that let the user control the playback and optionally display the player state. You have two options when working with media: MediaPlayer
: Offers functionality for basic playback situations using the most common audio/video data formats and data sources. You can discover more about using the MediaPlayer
in the “Working with MediaPlayer” section of Chapter 4 of this minibook.ExoPlayer
: Allows you to create a custom player by using the low-level Android audio APIs. The ExoPlayer
supports high-performance features such as Dynamic Adaptive Streaming over HTTP (DASH) and HTTP Live Streaming (HLS). This book doesn't discuss the use of ExoPlayer
.https://developer.android.com/guide/topics/ui/notifiers/notifications.html
): A notification is a message that Android displays from your app. It could tell you about an appointment you need to make or remind you about something you need to do. When you tap the notification, Android opens the app so that you can see any additional details the app can provide. This component also provides support for wearable devices and interactions with your automobile. The “Working with Notifications” section of Chapter 4 of this minibook tells you more about this component.Permissions (https://developer.android.com/guide/topics/permissions/index.html
): Your interactions with your device are private. However, to perform certain tasks, you must interact with others in a manner that might reduce your privacy. A permission is a method of allowing the interaction to take place. You use permissions to allow access to your private data, such as emails and contacts, and to provide access to device features, such as your camera or the device’s file system. Your app must also have permission to access certain data sources, such as the Internet.
An app can access certain permissions without user aid. However, other permissions, those that are considered dangerous in some way, require direct user interaction. In this case, the user sees a message box asking whether to deny or allow permission to a particular resource or to perform a particular task. The “Getting Permission” section of Chapter 4 of this minibook tells you more about this component.
https://developer.android.com/guide/topics/ui/settings
): This component helps you maintain user configuration information for your app. The “Complying with User Preferences” section of Chapter 4 of this minibook tells you more about this component.https://developer.android.com/training/sharing/shareaction
): You use this component to send and receive data from other apps. The “Sharing with Others” section of Chapter 4 of this minibook tells you more about this component.https://developer.android.com/guide/slices
): A Slice is Android’s latest approach for building interactive remote content into your app. For example, you could use a Slice to display the results of a Google search based on what the user is doing with your app at any given moment. A Slice appears as a template in your app that you use to define how the slice should appear and interact with the user. The “Using Slices” section of Chapter 4 of this minibook tells you more about this component.The UI components are the components that the user will see and interact with the most. Widgets (components that add functionality) and helpers (components that enable interactions indirectly) reduce the coding you have to perform and give your app the same feel as other apps on the user’s device (making the app easier and more predictable to use). The following list tells about the widgets and helpers in this category.
https://developer.android.com/training/animation/
): These aren’t animations like those found in Saturday cartoons. Rather, an animation gives feedback to the user when there is a change in your app’s layout. When the layout also includes modifying the layout hierarchy, you can use a transition to make the change more natural for the user. The “Using Animations and Transitions” section of Chapter 5 of this minibook tells you more about this component.https://developer.android.com/guide/topics/ui/look-and-feel/emoji-compat
): Users love emoji (those pictures used to replace words in modern communication), except when they appear as squares on their screen because their device hasn’t downloaded the latest emoji. If you’re creating a communication app, you need to support emoji. We can’t have users relying on real words now, can we? The EmojiCompat
library helps you accomplish this task. The “Communicating with Emoji” section of Chapter 5 of this minibook tells you more about this component.https://developer.android.com/guide/components/fragments
): Fragments let you take a LEGO-like approach to developing activities. The “Navigating between activities and fragments” section, earlier in this chapter, offers an overview of fragments. The “Working with fragments” section of Chapter 3 of this minibook tells you more about this component.https://developer.android.com/guide/topics/ui/declaring-layout
): In its most basic form, a layout simply describes what a user sees when using the app. The layout isn't the presentation of widgets or data, but what order Android presents those elements onscreen. You see how to create single-activity layouts in a number of examples in Books 1 and 2. However, a layout also defines the precise hierarchy of elements in your app, which is what the “Creating a Great Layout” section of Chapter 5 of this minibook discusses in detail.https://developer.android.com/training/material/palette-colors
): Not everyone knows which colors to use to create a visually appealing presentation. Of course, if the user isn’t wowed by the first screen, it’s possible that no one will see the second. The Palette library provides aids in choosing the right colors for your app based on things like the images you use so that you can wow the user. The “Employing Color and Texture” section of Chapter 5 of this minibook tells you more about this component.https://developer.android.com/training/animation/vp2-migration
): Essentially, this element helps you create an environment that lets a user use swipes to move between pages of your app. The ViewPager2
component provides improvements to make working with various layouts easier.In the beginning, there was the Android Support Library, but it proved inconsistent and unmanageable, so people started looking for an alternative. That's where the AndroidX package comes into play. It gives you features that make working with Android easier:
androidx
namespace. Any remaining Android Support Library packages are mapped to the androidx
namespace as well. This means that you can find everything you need under one roof. Of course, mapping an old library to a new namespace can turn into a problem because any existing code will be looking in the wrong place. Look here to find the mappings you need: https://semver.org/
for a description), which brings the version numbers in line with what everyone else is using.It’s important to realize that AndroidX adds to app features. So, if you’re use an ActionBar
in your app, you rely on the androidx.appcompat.app.AppCompatActivity
class, which appears in all the examples so far in the book. However, you still need other imports, like these:
import android.os.Bundle
import android.view.View
The example code also includes support for Kotlin extensions:
import kotlinx.android.synthetic.main.activity_main.*
Again, these extensions aren't part of the Android Support Library. You don’t need to worry that they’re outdated pieces of code hanging around your modern app (see https://kotlinlang.org/docs/tutorials/android-plugin.html
for additional details).
A lifecycle is the current state of a particular component and the range of those states while the component is in use. For example, a component might be: initialized, created, stopped, started, paused, resumed, or destroyed. An event triggers a change in state. When you call onStart()
, a component will likely start doing something, like processing data. The lifecycle begins when the component is initialized and ends when the component is destroyed.
When creating activities for your app, you place components within each activity, and those components interact. If they interact in a random manner or react at the wrong time, you could find that your app crashes, causes memory leaks, or has other problems. A lifecycle-aware component is simply one that monitors other components before it begins performing a task.
Now that you have a basic idea of what a lifecycle and a lifecycle-aware component are all about, it's time to view them in more detail. Keep reading for a good overview of how a lifecycle management strategy works.
When your app runs, a new activity begins in which components interact with each other. An activity might do something like display your current location based on the location service input. In order to do that, a component would need to access the location service, obtain the location information, and then disconnect from the location service. In other words, the process would have a distinct start and stop.
Now consider another component in the same activity. This component contacts a map service and displays your location on the map. However, if this second component tries to display the location before the first component actually obtains it, the app could act in a strange manner, provide incorrect results, or simply crash the app. Consequently, the second component must first ask the first component about its state, which is done using the getLifecycle()
method of the first component’s LifecycleOwner
interface.
In short, components often interact with each other, and the state of one component might depend on the state of another. Consequently, you often see Android app activities that are simply packed with code designed to keep one component from doing something before another component finishes its task.
Using lifecycle-aware components can also decrease problems like race conditions, where one component tries to start a task before another component comes to a complete stop (or vice versa). Using lifecycle-aware components also reduces crashes caused by resource contention and memory leaks that develop when resources are used incorrectly.
When working with lifecycle-aware components, the Lifecycle
class object resides in a component and provides interaction with other components that depend on the host component. The information appears in two enumerations:
When an observer, the component that wants to know the status of another component, calls getLifecycle()
, it receives a Lifecycle
object (https://developer.android.com/reference/androidx/lifecycle/Lifecycle.html
) back. The Lifecycle
object contains the two enumerations with the events and states. In addition, this object provides the addObserver()
function, which tells the LifecycleOwner
, the first component, to provide the observer with event notifications, such as Lifecycle.Event.ON_START
, and state information, such as Lifecycle.State.STARTED
.
3.238.82.77