Chapter 1

Apps for Tablets

IN THIS CHAPTER

check Adjusting for screen size and screen orientation

check Managing multipanel activities

check Writing apps that run on both phones and tablets

Don’t think about an elephant.

Okay, now that you’re thinking about an elephant, think about an elephant’s legs. The diameter of an elephant’s leg is typically about 40 centimeters (more than four-tenths of a yard).

And think about spiders of the Pholcidae family (the “daddy longlegs”) with their hair-like legs. And think about Gulliver with his Brobdingnagian friends. Each Brobdingnagian was about 72 feet tall, but a Brobdingnagian adult had the same physical proportions as Gulliver.

Gulliver’s Travels is a work of fiction. An animal whose height is 12 times a human’s height can’t have bone sizes in human proportions. In other words, if you increase an object’s size, you have to widen the object’s supports. If you don’t, the object will collapse.

This unintuitive truth about heights and widths comes from some geometric facts. An object’s bulk increases as the cube of the object’s height. But the ability to support that bulk increases only as the square of the object’s height. That’s because weight support depends on the cross-sectional area of the supporting legs, and a cross-sectional area is a square measurement, not a cubic measurement.

Anyway, the sizes of things make important qualitative differences. Take an activity designed for a touchscreen phone. Zoom that activity to a larger size without making any other changes. Then display the enlarged version on a ten-inch tablet screen. What you get on the tablet looks really bad. A tiny, crisp-looking icon turns into a big, blurry blob. An e-book page adapts in order to display longer line lengths. But, with lines that are 40 words long, the human eye suffers from terrible fatigue.

The same issue arises with Android activities. An activity contains enough information to fill a small phone screen. When the user needs more information, your app displays a different activity. The new activity replaces the old activity, resulting in a complete refresh of the screen.

If you slap this activity behavior onto a larger tablet screen, the user feels cheated. You’ve replaced everything on the screen even though there’s room for both the old and new information. The transition from one activity to the next is jarring, and both the old and new activities look barren.

No doubt about it, tablet devices require a design that’s different from phone designs. And to implement this design, Android has fragments. You first start discovering fragments in Book 3, Chapters 3 and 4, and this chapter adds to that knowledge by emphasizing the differences between smartphone and tablet presentation. This chapter specifically uses the navigational graph technique explored in Example 03_03_02 of Book 3, Chapter 3.

Gaining Perspective

The examples in previous minibooks rely on the perspective of either a Nexus S or a Pixel 3a smartphone used in the portrait orientation (unless you chose some other smartphone setup during the initial configuration). Of course, you could just assume that no one will ever have any other sort of smartphone and will never use it in landscape orientation. You could go further and just assume that tablet users will be happy with the space-wasting view of a smartphone app, but the users of those devices will soon provide you with a rude awakening. Different devices have different perspectives of your app. The following sections help you understand how to configure Android Studio to support testing with multiple device perspectives so that you can create better layouts for your apps. These sections do rely on Example 03_03_02 from Book 3, Chapter 3, but you can easily understand the material without having to create the example if desired.

Creating the right devices

Creating great apps means choosing a set of test devices that match what you expect users will have (or at least provide a broad enough spectrum that the app should work on all current devices, even those you haven’t tested). As the chapter progresses, you build a flexible app designed around the ideas presented in Book 3, Chapters 3 and 4. In this section, you look again at one of those apps, Example 03_03_02.

You can use a number of techniques to see problems with Example 03_03_02, which is designed to work on a smartphone in portrait mode. One of those techniques is to work with the emulator controls, shown in Figure 1-1. Notice that two of the controls let you turn the emulated device — which you can do either to the left or to the right. Turning the device shows that the example doesn’t rotate when the screen rotates, making it quite impossible to use the app in landscape mode, as shown in Figure 1-2.

Illustration displaying the emulator controls that lets a user reconfigure the device.

FIGURE 1-1: Emulator controls let you reconfigure the device.

Illustration demonstrating that turning the device depicts that the text does not rotate when the screen rotates, making it quite impossible to use the app in landscape mode.

FIGURE 1-2: The example doesn’t rotate when turned.

The “Creating an Android virtual device” section of Book 1, Chapter 2 gives you the basics of creating a virtual device. When creating a virtual device, remember that you can choose a startup orientation, as shown in Figure 1-3, and it often helps to have one of each running as you test (see the next section of this chapter for more details).

As shown in Figure 1-4, the maximum resolution of a tablet is nearly twice that of the maximum of a smartphone. The emulator offerings supplied with Android Studio represent the most popular resolutions. However, some devices do provide slightly higher resolutions, and you may need to test against them using a physical device and the USB technique described in the “Testing Apps on a Real Device” section of Book 1, Chapter 3.

Screenshot of the Android Virtual Device Configuration dialog box to choose a startup orientation for creating a virtual device, which helps to have one of each running as you test.

FIGURE 1-3: Test simultaneously in portrait and landscape mode by having one of each.

Screenshot depicting that the maximum resolution of a tablet is nearly twice that of the maximum of a smartphone.

FIGURE 1-4: Tablets can have four times the screen real estate of smartphones.

Running Example 03_03_02 on a high-resolution tablet demonstrates that it doesn’t scale very well, as shown in Figure 1-5. The text is so tiny that your user will easily go blind, and the controls are all jammed into the upper-left corner. Between the inability to rotate the display and a lack of scaling, the Example 03_03_02 is a failure, despite looking good in Book 3, Chapter 3.

A mobile screen for running Example 03_03_02 on a high-resolution tablet. The text is so tiny and the controls are all jammed into the upper-left corner.

FIGURE 1-5: The example app doesn’t scale well, either.

When creating a testing setup for apps used with both smartphones and tablets, you want to be sure to have a range of device resolutions and form factors. Also helpful is to be able to quickly display both landscape and portrait versions of your app, as shown in Figure 1-6.

Remember As a final consideration for emulation, you can choose to install any API version you want on an emulator, but a real device may not support the latest Android version. An inability to update to the latest version may make special layout features unavailable, so if your app uses them to create the best possible display, you may also find that some people can’t use your app.

Screenshot of the Android Virtual Device Manager dialog box for defining a range of device sizes and orientations to ensure that your app appears correctly onscreen.

FIGURE 1-6: Define a range of device sizes and orientations to ensure that your app appears correctly onscreen.

Running code on multiple devices

You may have noticed that even with great machine resources, starting an emulation to test your app can take a while, depending on what you expect the app to do. Sometimes, the more efficient approach is to start the testing process on multiple emulators when your goal is to see how the layout works. To use multiple devices to run the app, follow these steps:

  1. Choose Run⇒  Select Device.

    You see the context menu, shown in Figure 1-7.

    Screenshot displaying a context menu for accessing the multiple-device capability of troubleshooting device connections.

    FIGURE 1-7: Accessing the multiple-device capability.

  2. Click Run on Multiple Devices.

    You see the Select Deployment Targets dialog box, shown in Figure 1-8.

  3. Select the devices you want to use and click Run.

    Android Studio compiles the app, starts the selected emulators or attached devices, and installs the app on the devices. This process requires longer than the usual time. However, you can then work with each device independently to see various app changes.

Screenshot of the Select Deployment Targets dialog box to select the devices you want to use and click Run.

FIGURE 1-8: Choose which devices to use for the emulation.

Copying the project

Clearly, the example app has some design deficiencies, but it makes a good starting point for this chapter, which means making a copy so that you get to keep the original. Use these steps to create a copy of Example 03_03_02.

  1. Create a new folder named 05_01_01 in your source code folder.

    You see the new empty folder.

  2. Copy all the folders and files in the 03_03_02 folder.
  3. Paste all the folders and files into the 05_01_01 folder on your system.

    You see a copy of the files and folders in the new folder.

  4. Choose the Open an Existing Android Studio Project option from the main Android Studio window.

    Android Studio displays an Open File or Project dialog box.

  5. Select the 05_01_01 folder and click OK.

    The app won’t run correctly at this point, so don’t even try. In fact, it may not compile, either.

  6. Open AndroidManifest.xml and change package="com.allmycode.p03_03_02" to read package="com.allmycode.p05_01_01".
  7. Right-click the javacom.allmycode.p03_03_02 folder and choose Refactor⇒  Rename from the context menu.

    You see the Warning dialog box shown in Figure 1-9, telling you that Android Studio will rename the package instances for you.

  8. Click Rename Package.

    You see a Rename dialog box like the one shown in Figure 1-10.

    Screenshot of the Warning dialog box telling you that Android Studio will rename the package instances for you.

    FIGURE 1-9: Rename the package instances in your app.

    Screenshot of a Rename dialog box to define how you want the rename process to proceed.

    FIGURE 1-10: Define how you want the rename process to proceed.

  9. Type p05_01_01 in the field supplied, check both options, and click Refactor.

    Android Studio generates a Refactoring Preview like the one shown in Figure 1-11.

  10. Click Do Refactor.

    After a few moments, the Refactoring Preview dialog box goes away and you can see that the directory names and other elements have been renamed.

    Screenshot of a dialog box in which Android Studio generates a Refactoring Preview, to type p05_01_01 in the field supplied, check both options, and click
Do Refactor.

    FIGURE 1-11: Verify the refactoring process.

  11. Open settings.gradle, change the rootProject.name entry to 05_01_01, and then click Sync Now.

    Android Studio syncs the change with the rest of the app.

  12. Click Build⇒  Make Project.

    The copied project should compile without error.

Because you're changing things, open resvaluesstrings.xml and change the <string name="app_name">03_03_02</string> entry to read <string name="app_name">Layout Validation</string> so that the example has a better name than the example number. This technique comes in handy any time you want to retain an existing project but also use it as a starting point for a new project.

Seeing presentation differences

The new example you've created, 05_01_01, has two problems that you’ve already noted. The first is that it doesn’t change orientation when the user changes the device from portrait to landscape presentation. The second is that the text isn’t the right size for each device; what looks nice on a smartphone causes the user to squint when working with a tablet. The following sections address both of these issues.

Correcting the orientation problem

It seems as if your app should be able to automatically reorient itself without additional code, but some apps truly are directional, and you see them all the time when you try various apps in Google Play Store (meaning that they have been vetted by Google). For example, a utility to control a furnace might actually work when placed in landscape mode, except, you couldn’t conveniently see things like the temperature schedule because adding this information requires a longer display. Consequently, you must decide on an appropriate orientation for your app and then provide code to manage it. The example in this section allows for changes in orientation and reorients itself as needed (a little trick is involved when you’re using an emulator).

Remember If you have been working with the app on an emulator without orientation support, stop the app and restart the emulator afterward. Otherwise, you might not see the change, strange as that might sound. Restarting the emulator apparently resets features such as orientation support.

To begin, you must tell Android that you’re interested in knowing about certain changes in configuration, like orientation. Open the AndroidManifest.xml file and add the following code, in bold, to the activity.

<activity android:name=".MainActivity"
android:configChanges="orientation|screenSize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>

<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>

You can add other configuration changes to the android:configChanges attribute. However, for now, all you need is to know about orientation and screen size.

The next change appears in activity_main.xml file, where you need to add a new TextView to display the orientation information received by the code you add in a moment. Name the new control LastChange and give it a default text value of N/A.

The last change is in MainActivity.kt. You need to add an override for onConfigurationChanged(), which is where all the change information appears (so it can get rather complex). Here's the code you use for this part of the example:

override fun onConfigurationChanged(newConfig: Configuration) {
if (newConfig.orientation ==
Configuration.ORIENTATION_LANDSCAPE) {

LastChange.setText("Landscape")
} else if (newConfig.orientation ==
Configuration.ORIENTATION_PORTRAIT) {

LastChange.setText("Portrait")
} else {
LastChange.setText("Unrecognized")
}
super.onConfigurationChanged(newConfig)
}

Remember Notice that the code that interacts with the change must come before the call to super.onConfigurationChanged(newConfig). Otherwise, nothing will happen.

When running the app on an emulator, you see a new little symbol on the bottom of the display with the rest of the controls, as shown in Figure 1-12. This little symbol looks like a box with an arrow in each corner. You must click that symbol to change the orientation of the app.

A mobile screen displaying the layout validation, to click the little symbol that resembles a box with an arrow in each corner, to change the orientation of the app.

FIGURE 1-12: Look for the little orientation symbol on you emulator.

After you click the orientation symbol, you see the app reorient itself as shown in Figure 1-13. Of course, the text size isn’t right, and now the various controls appear shoved over to the left instead of being centered.

A mobile screen depicting how the app reorients itself correctly after you click the orientation symbol.

FIGURE 1-13: The app now reorients itself correctly.

Remember As you play with the app, you notice that it maintains state properly and doesn’t suddenly display a different page without clicking the appropriate button. Your app must work as expected at all times, which includes changes of configuration such as a changed orientation.

Organizing the layout to match the device

You could slowly drive yourself nuts trying to obtain the right Android layout using programmatic means. A much easier method involves using layouts effectively. For this example, you need to modify how you approach the layout so that it can work well in different orientations and on different-sized devices. To start, consider that activity_main.xml currently uses a ConstraintLayout, which works well when you know you need to work with a specific-sized device at a specific orientation. Using a several LinearLayout setups works better in this case, so activity_main.xml now uses a setup that looks like the one in Figure 1-14.

Screenshot of a setup depicting that several LinearLayout setups works better and makes the design more flexible.

FIGURE 1-14: Using a different layout makes the design more flexible.

Remember When changing a design, you don't have to start from scratch. You can simply start by using the Text tab to type new names for the elements you want to change, such as by changing the ConstraintLayout to a LinearLayout, as shown in the figure. This design also rearranges a few things and sets some properties differently — all of which you can perform on the Design tab. The new design is actually simpler, and you don't specify any screen positions. For example, here’s the new code for the Caller TextView control:

<TextView
android:id="@+id/Caller"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="@string/CallerText"
android:textSize="24sp"/>

Notice that the control now specifies a textSize attribute value of 24sp, for scalable pixels (sp). Use sp whenever possible to account for differences in device dots-per-inch (dpi) values. Android supports a range of size qualifiers, and it's important to know which one to use. The article at https://blog.mindorks.com/understanding-density-independent-pixel-sp-dp-dip-in-android describes the differences between dp, sp, dip, and so on. Trying to keep them straight could make you crazy! The reason the example now uses 24sp for the text is that it provides a good presentation across smartphone and tablet devices. The text in the pushbuttons is set to 18sp because it shows up better onscreen.

Tip In using this setup, spacing and weights become more important. For example, the nav_host supports two Button controls and a Space, while activity_main.xml supports spaces around the nav_host and a Button. The weights for each element must equal 1 to space the controls evenly across the device screen, so nav_host actually has a layout_weight setting of 3, not 1, as is used for the other elements. Here is the setup for the horizontal LinearLayout for activity_main.xml:

<Space
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"/>

<fragment
android:id="@+id/nav_host"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="3"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph"/>

<Space
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"/>

<Button
android:id="@+id/Previous"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="@string/PreviousClick"
android:text="@string/PreviousText"
android:textSize="18sp"/>

<Space
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"/>

None of the controls uses any sort of positioning any longer because the combination of the LinearLayout controls makes it unnecessary. Of course, you give up the ability to precisely position controls in the process. A ConstraintLayout comes in handy when positioning is critical or you want special effects.

The last odd sort of change is that all the fragments must now have two Button controls and a Space, even fragment_see_results.xml. The solution is to include a blank button. Here's the code used to create this new setup:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
tools:context=".SeeResults">

<Button
android:id="@+id/GoSayGoodbye"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="2"
android:onClick="@string/GoSayGoodbyeClick"
android:textSize="18sp"
android:text="@string/GoSayGoodbyeText"/>

<Space
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"/>

<Button
android:id="@+id/InvisibleButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="2"
android:clickable="false"
android:textSize="18sp"
android:visibility="invisible"/>

</LinearLayout>

Because the visibility attribute is set to "invisible", you don't even see the Button onscreen, but the effect of the button is felt through the layout, with Goodbye appearing on one side of the display and Previous on the other. Of course, the main concern is whether these changes have actually fixed the problems noted earlier. Figure 1-15 shows the smartphone landscape view, and Figure 1-16 shows the tablet landscape view. The new design works for simple apps. However, as you see in later sections of the chapter, apps with complex layouts require a bit more planning.

A mobile screen depicting how the new layout works great on a smartphone no matter the orientation.

FIGURE 1-15: The new layout works great on a smartphone no matter the orientation.

Illustration of the tablet landscape view depicting that tablets also can now use the layout for simple apps.

FIGURE 1-16: Tablets can now use the layout as well and the viewer won’t squint.

Developing a Nested Navigational Graph

The navigational graph is a lot more flexible than previous examples would have you believe. You can create complex navigation that makes high-end apps possible with a lot less work than you needed to perform in the past. This section conveys the first part of that process, creating a nested navigational graph that groups fragments in such a manner as to make supporting multiple device types considerably easier. When using a nested navigational graph, what you create is a setup in which you use a fragment alone on a smartphone or placed side by side with another fragment on a smartphone in landscape mode (with the requisite reduction in font size). When working with a tablet, you might see four fragments in a group placed in various ways depending on whether the tablet is in portrait or landscape mode. The purpose of all these configurations is to use screen real estate more effectively when working with complex apps.

The example in this section begins by copying the example in the previous section using the technique discussed in the “Copying the project” section, earlier in this chapter. Call this example 05_01_02. As you follow the steps, substitute 05_01_02 for 05_01_01 as needed. You don't need to do anything other than copy the project to start the sections that follow.

Understanding the uses for nested navigational graphs

The essential idea behind a navigational graph (as described in Book 3, Chapter 3) is to outline a process consisting of a series of workflows. In that chapter, you see three workflows:

mainEntry->doStuff->seeResults->sayGoodbye
mainEntry->seeResults->sayGoodbye
mainEntry->doStuff->sayGoodbye

The problem with this setup is that doStuff might actually require a number of steps that won’t fit on a single smartphone screen. To make the fragments work, you have to fit them on a single screen of the smallest device you want to support. However, in order to use screen real estate efficiently, a larger screen might show multiple fragments. You have a problem here that you can solve using a nested navigational graph by maintaining the individual fragments but grouping them together into a single nested graph within the main graph.

Consider a series of forms used to make a sale from an online shopping cart. The app you create makes the shopping process easier because a user can safely store personal information locally. So, you might break this process into four parts, as shown here:

  1. Verify the content of the shopping cart and total price based on that content and details like shipping method.
  2. Obtain the buyer’s personal information and shipping address if different from the buyer’s address.
  3. Obtain credit card or other means of purchase.
  4. Finalize the sale by requiring buyer verification of details.

The process must follow all four of these steps, so grouping them makes sense. However, a buyer need not necessarily want to make another purchase. The seeResults part of the overall process might simply display current orders. So, a buyer might want to make a new purchase and see it added to the list of pending orders, see just the pending orders, or just make a purchase. The three original workflows still work, but doStuff is now more complex. Examples of other kinds of nested graph include:

  • Login or other verifications
  • Setup or other wizards
  • Gaming or other activity flows
  • Demos that allow limited interaction
  • Any complex multiscreen workflow or process

Developing an app design

A complex app doesn't just sprout out of your mind like a patch of petunias. You don’t get up in the morning, proclaim you’ll be brilliant, and create the design from scratch without reworking it a little. Most people start with an overview, which is what you can consider the original design from Book 3, Chapter 3 to be. You augmented that design in this chapter as example 05_01_01, and you see this augmented design in Figure 1-17.

Illustration of an augmented design connecting the existing links such as doStuff, seeResults, and sayGoodbye from the mainEntry.

FIGURE 1-17: An augmentation of the original Book 3, Chapter 3 design.

Follow these steps to implement the design changes considered in the previous section of the chapter:

  1. Add three new fragments below doStuff and name them:

    • personalInfo
    • creditCard
    • finalize

    You use the same process as you do in the “Adding destinations” section of Book 3, Chapter 3.

  2. Delete the existing links among doStuff, seeResults, and sayGoodbye by clicking each link and pressing Delete.
  3. Create new links as shown in Figure 1-18 to create the new workflow.

    You use the same process as you do in the “Creating links between destinations” section of Book 3, Chapter 3.

    Illustration for implementing new links such as personalInfo, creditCard, and finalize design to the existing links doStuff, seeResults, and sayGoodbye.

    FIGURE 1-18: Define the new links for the updated design.

  4. Rename doStuff to verifySale by highlighting the doStuff block in the design and typing verifySale in the ID field of the Attributes pane.

    You see a dialog box asking whether to perform the update locally or globally.

  5. Click Yes to perform the update globally.

    The navigational graph designer applies the change globally.

  6. Perform Steps 4 and 5 to rename seeResults to seeOrders, with a Label of fragment_see_orders.
  7. Shift-click verifySale, personalInfo, creditCard, and finalize.

    You see all four blocks highlighted.

  8. Click Group Into Nested Graph on the toolbar (the icon that looks like three blocks grouped).

    You now see the grouped items as a single block with an ID value of navigation, which clearly isn't helpful. If you wanted to maintain the original design, you could always use doStuff for an ID because this single grouped block encapsulates what you used to call doStuff, but a better name might be purchaseGoods.

  9. Highlight the grouped block and type purchaseGoods in the ID field of the Attributes pane.

    The final graph looks like the one in Figure 1-19.

Illustration of an updated graph design highlighting the grouped block and type purchaseGoods in the ID field.

FIGURE 1-19: An updated graph that considers a more complex process.

You should note a few things aspects of this new design. For one thing, all the nested graph links are dashed so that you can easily see that clicking one of them won't show the actual actions. To see the actions contained within a nested graph, you must double-click the block, which reveals the content.

The second thing to note is that the Destinations pane takes on a new meaning, as shown in Figure 1-20. Notice that when you go into a nested graph to see its individual elements, you can’t see any of the elements outside the nested graph. You click Root (with the left-pointing arrow) to leave the nested graph and return to the overview.

Screenshot of the Destinations pane viewed from within the nested graph.

FIGURE 1-20: Seeing Destinations from within the nested graph.

Remember The fragment file for verifySale is still titled fragment_do_stuff.xml, and the fragment file for seeOrders is still titled fragment_see_results.xml. You could change these filenames and likely would for a large project, but for now, just leave them as is.

Considering the content needs

At this point, you should try to compile the app by choosing Build⇒  Make Project. You see two unresolved reference errors as a result. The errors come from the fact that the actions have changed. Double-click the entries to modify the actions: action_doStuff_to_seeResults becomes action_finalize_to_seeResults, and action_doStuff_to_sayGoodbye becomes action_finalize_to_sayGoodbye. If you like, you can also change the Caller.setText() call to "Caller: Finalize".

Of course, these actions don't completely fix the app linkages, but they’re a start. The problem is that you now have a different flow, and using the Button controls as the app has done makes the app brittle and not very adaptive to the content. What you really need is a quick method of navigating the graph that isn’t so brittle. In addition, you want the fragments to contain only content. So, begin by removing the buttons from all the fragments and adding two buttons to activity_main.xml so that it looks like Figure 1-21.

To keep things easy, change the fragments so that they all have a simple TextView that describes the current page, like this:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
tools:context=".DoStuff">

<!-- TODO: Update blank fragment layout -->

<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_horizontal"
android:text="This is the Main Entry page."
android:textSize="24sp"/>
</LinearLayout>

Screenshot depicting a component tree where the fragments contain only the content in the LinearLayoutCompat.

FIGURE 1-21: Reorganize the layout to place all controls on activity_main.xml.

The nav_graph.xml file requires changes as well. Set the Label attribute for each of the destinations to match the ID attribute. You also need to add information about the buttons for each of the destinations by adding arguments on the Attribute pane. Click the + icon to see a dialog box like the one shown in Figure 1-22.

Screenshot of a dialog box to add information about the buttons for each of the destinations by adding arguments on the Attribute
pane.

FIGURE 1-22: Add button information to each destination.

For this example, type Button1 or Button2 (when the destination has two buttons) in the Name field, select String as the Type, and type the text that should appear on the button in Default Value, such as Buy Goods for Button1 of mainEntry. Figure 1-23 shows an example of how your entries should look for mainEntry.

Screenshot of the Attribute pane depicting how your entries should look for mainEntry.

FIGURE 1-23: Add arguments to handle the button text.

Making these changes allows you handle the two destination buttons using a single function for each. Both functions work the same, but onDestination1Click() is a little longer because it contains more destinations. Here is the onDestination1Click() code:

fun onDestination1Click(view: View){
val navController = Navigation.findNavController(
this, R.id.nav_host)
val Fragments: NavHostFragment =
supportFragmentManager.findFragmentById(
R.id.nav_host) as NavHostFragment
val ChildFragment =
Fragments.childFragmentManager.fragments[0]

val thisCaller = navController.currentDestination
Caller.setText("Caller: " + thisCaller!!.label)

when (ChildFragment) {
is MainEntry -> navController.navigate(
R.id.action_mainEntry_to_doStuff)
is DoStuff -> navController.navigate(
R.id.action_doStuff_to_personalInfo)
is personalInfo -> navController.navigate(
R.id.action_personalInfo_to_creditCard)
is creditCard -> navController.navigate(
R.id.action_creditCard_to_finalize)
is finalize -> navController.navigate(
R.id.action_finalize_to_seeResults)
is SeeResults -> navController.navigate(
R.id.action_seeResults_to_sayGoodbye)
}

ChangeButtons(navController)
}

This function works similarly to those you have worked with in the past, but in reality it provides a simplification because you no longer micromanage everything. The navController provides access to the various locations that you want to manage, while ChildFragment tells you about the current destination. For example, you use ChildFragment to determine the name of the caller based on its label property. ChildFragment also lets you know about the destination type so that you know where the app should go next.

Part of the magic in this version of the app is that navController changes to match the current destination. So, after you call navController.navigate(), navController no longer points to the previous destination; it points to the new destination instead. The call to ChangeButtons(navController) configures the buttons to match the new destination using the following code:

fun ChangeButtons(navController: NavController) {
val newCaller =
navController.currentDestination!!.arguments
if (newCaller!!["Button1"] != null) {
Destination1.visibility = View.VISIBLE
Destination1.setText(
newCaller!!["Button1"]!!.defaultValue.toString()
)
} else
Destination1.visibility = View.INVISIBLE

if (newCaller!!["Button2"] != null) {
Destination2.visibility = View.VISIBLE
Destination2.setText(
newCaller!!["Button2"]!!.defaultValue.toString()
)
} else
Destination2.visibility = View.INVISIBLE
}

This is where the default argument values you configured in Figure 1-23 come into play. Each of the destinations has between zero and two buttons. When there is button text to display, the code uses the argument text to update the button. Otherwise, the button is invisible. Figure 1-24 shows the result of making the app useful with a nested navigational graph.

A mobile screen depicting the result of rearranging the app
with a nested navigational graph using the fragments.

FIGURE 1-24: The fragments now have only content, so you can easily rearrange them.

Creating a Responsive App

You can do more than you've seen in this chapter to create an app that works equally well on a smartphone or a tablet (or any other device, for that matter). Here are some other methods of adding flexibility and responsiveness to consider:

  • Choose when to use a navigation bar or a navigation drawer. When working with a larger device, rely on a navigation bar, displayed at the bottom of the app, to provide a better view of app features in an easily accessed manner. However, when working with a smaller device, use a navigation drawer (hidden on the app bar) instead to conserve space.
  • Make full use of Toolbar functionality. You see part of this feature in action in the “Complying with User Preferences” section of Book 3, Chapter 4.
  • Restrict your app to specific device sizes when necessary. You have a number of options in this regard, which are explained at https://developer.android.com/guide/practices/screens-distribution.html.
  • Create different layouts based on smallest screen width. After designing a layout using fragments that contain only content, you can rearrange the fragments in any pattern that a particular device will accommodate and then place that layout in a special folder for devices of that size, as explained at https://developer.android.com/training/multiscreen/screensizes.
  • Define different layouts based on device orientation. This technique relies on the same approach you use for screen widths. You can use the resulting special folders by orientation, smallest screen width, or both.
..................Content has been hidden....................

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