Chapter 1

Getting an Overview of Jetpack

IN THIS CHAPTER

check Considering the potential of Jetpack

check Defining the Jetpack elements

check Working with the AndroidX package

check 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

  • Reducing the amount of boilerplate code
  • Ensuring that your app follows best practices more often than is possible through hand coding so that you don’t end up with horrid errors that are impossible to find
  • Modularizing the development environment so that it’s easier to understand
  • Unbundling the androidx.* packages from the API to improve backward compatibility and ensure that you have the latest updates
  • Helping you manage the life span of your app so that it doesn't end up being some sort of Franken-app

Of 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.

Understanding the Benefits of Jetpack

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

  • Background task management
  • Navigation
  • Lifecycle management
  • Memory management
  • Configuration change management

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.

Remember To make it easy for you to perform tasks without worrying about whether you’re using the latest code, best practices, or coding techniques, Jetpack gets updated more often than the rest of Android. This means that you can ensure that your code is always current without expending much effort. However, because Jetpack is packaged separately, you can also choose to use an older version for backward compatibility. So, you get the best of both worlds: the latest code and the ability to support existing apps with less trouble.

Eliminating boilerplate code

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.

Managing background tasks

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.

Navigating between activities and fragments

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:

  • Navigation graph: An XML file that contains all the navigation information. This information includes the destinations you define, along with all the paths a user can take to reach those destinations.
  • NavHost: An empty container that displays all the destinations defined in the navigation graph. It also contains a NavHost implementation that supports destination fragments, which are self-contained parts of an activity that have their own user interface that you might want to use as a destination in some cases. Destination fragments rely on a NavHostFragment rather than a NavHost.
  • NavController: An object that manages navigation within a 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.

Technical Stuff As the user moves through an app, the navigation elements automatically update the back stack, which is a last-in/first-out structure that tracks where the user has been. The back stack normally starts with a single entry: the main activity for your app where all the user activities begin. When the user clicks the Back button, the navigation elements automatically pop the previous destination off the back stack and display it. However, the display isn't static; you see any updates that occur as a result of navigating to the new destination. For example, in an email app, a message might appear as unread until a user clicks it. Clicking the Back button would show that the message is now read. You discover more about how navigation works in the “Providing for Navigational Needs” section of Book 3, Chapter 3.

Tip The navigation elements actually do a lot of work for you. In addition to managing the navigation between destinations, the navigation elements perform these tasks:

  • Handle fragment transactions, such as displaying common elements for all activities in your app.
  • Process Up and Back actions correctly by default. For example, clicking Up should never exit the application (removing the main activity from the back stack), and the navigation elements know about this requirement.
  • Use standardized resources for animations and transitions so that what the user sees onscreen won’t look odd or cause other problems.
  • Implement and control deep linking, which is the method a user can employ to access a specific activity in an Android app directly. Deep linking implies that there is no back stack because the user accesses the activity directly. Consequently, the Up and Back actions have no effect.
  • Provide helpers that help you employ Navigation UI patterns, such as navigation drawers and bottom navigation, with minimal additional work.
  • Ensure type safety when navigating and passing data between destinations using Safe Args — a Gradle plug-in.
  • Create a 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.

Screenshot of a simple sample graphical editor to create a navigation graph consisting of a main activity and an output data fragment.

FIGURE 1-1: Using a graphical editor to create a navigation graph.

Managing memory

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.

Remember With all these factors in mind, keeping memory leaks (a failure to release unused objects in memory) to a minimum is essential if you want your app to perform well. If you’re really interested in precisely how memory leaks occur, the article at https://proandroiddev.com/everything-you-need-to-know-about-memory-leaks-in-android-d7a59faaf46a details them. You don’t really need to know this information to use this book, but it’s interesting to delve into later.

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.

Screenshot displaying a graph of memory usage when you
select MEMORY from the profiler’s drop-down list in Android Studio.

FIGURE 1-2: Checking memory usage in your app.

Performing configuration changes

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:

  • The user changing the screen orientation
  • A keyboard being added or removed
  • Device features like multiwindow mode being enabled

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.

Remember By using the Saved State module for ViewModel that comes with Jetpack, you can eliminate the need for most, if not all, of this boilerplate code and the inherent errors that such code produces. The difference is that your ViewModel now receives a SavedStateHandle object that contains the saved instance data for your app. You no longer have to deal with restoring the data by hand. The article at https://developer.android.com/topic/libraries/architecture/viewmodel-savedstate provides some additional details about using the Saved State module.

Considering the Jetpack Components

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.

Foundation

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.

Architecture

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:

Behavior

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.

  • CameraX (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.
  • Media and playback (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.
  • Notifications (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.

    Remember 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.

  • Preferences (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.
  • Sharing (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.
  • Slices (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.

UI

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.

Getting an Overview of the AndroidX Package

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:

  • Consistent namespace: All the packages in AndroidX appear within the 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:
  • Separate maintenance: One of the issues with the Android Support Library was having to update the entire library every time a change took place. AndroidX packages are separately maintained and updated so that you get changes faster and the update is a lot less painful. The packages also use semantic versioning (see https://semver.org/ for a description), which brings the version numbers in line with what everyone else is using.

Warning The last release of the Android Support Library is 28.0.0. You won't see any additional updates, which means that every day your app waits to use AndroidX is another day that it falls behind with needed changes. All new features will appear as part of the androidx namespace.

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

Remember You use android.os (https://developer.android.com/reference/android/os/package-summary) to provide access to basic operating system services, message passing, and interprocess communication. Likewise, android.view (https://developer.android.com/reference/android/view/package-summary) provides basic user interface classes. You use them to interact with the user and display information onscreen. Both of these appear as part of the underlying API, not as part of the Android Support Library.

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).

Working with Lifecycle-Aware Components

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.

Focusing on activities

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.

Remember Rather than create a huge mess within your activity code, you can instead rely on lifecycle-aware components that contain code allowing them to monitor the app and react as needed. Because the dependent component can monitor the lifecycle of the monitored component directly, the code is smaller, easier to manage, and less likely to contain errors. More important, the app as a whole is better organized and more modular, so you can reuse code with greater ease. You find the classes and interfaces used to create lifecycle-aware components in the androidx.lifecycle package.

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.

Understanding events and states

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:

  • Event: The action that triggered a change in state. These events map to the callbacks found in other components.
  • State: The current status of the component.

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.238.82.77