Mobile App user interfaces have come a long way from the early days, and while users have more devices to choose from than ever, they all expect consistently high-quality experiences from their apps. Apps are expected to run fast and the user interfaces are expected to be smooth; all this while running on a massive array of devices of varied capabilities. Your app needs to run on devices with screens as big as televisions on the one end of the scale, and smartwatches with screens as small as 2.5 cm or even smaller on the other end of the scale. At first glance, this may seem like a nightmare, but there are simple tricks to make building responsive Android apps easy.
In this book, you'll learn a diverse set of skills as well as some theoretical knowledge that you can apply to build fast, responsive, and great-looking Android applications. You'll learn how to go about designing the screens that your application will actually need, and then how to build them for maximum flexibility and performance while keeping your code easy to read and avoiding bugs.
In this chapter, we will look at the basic principles used to build user interfaces for Android applications. You'll need to be familiar with concepts to build even the simplest Android application, so in this chapter, we'll cover the following topics:
- The basic structure of an Android application
- Creating a simple Activity and layout files using Android Studio
- Where to find the most useful parts of the Android Studio layout editor
- How a well-organized project is structured
Since Android was first introduced in 2008, the user interface design has changed radically, from the first early versions of the grey, black, and orange theme, to the Holo theme, which was more of a stylistic change than a fundamental shift in the design language, and eventually culminating in material design. Material Design is more than just a style; it's a design language complete with concepts for navigation, and overall application flow. Central to this idea is the notion of paper and card, the idea that the items on the screen are not simply next to each other but may also be above and below in the third dimension (although this is virtual). This is achieved using the elevation property that is common to all widgets in Android. Along with this basic principle, material design offers some common patterns to help the user identify which components are likely to take which actions, even the first time they are used in the application.
If you look at the original Android theme alongside the Holo Light theme, you can see that while the style changed dramatically, many elements stayed similar or the same. The grey tones flattened, but are very similar, and many of the borders were removed, but the spacing remains very close to the original theme. The material design language is often very similar in its basic styling and design to Holo:
The design language is an essential part of modern user interface design and development. It not only defines the look and feel of your widget toolkit, but also defines how the application should behave on different devices and in different circumstances. For example, on Android, it is common to have a navigation drawer since the slides are from the left, while this would not feel natural to the user on other platforms. Material design defines much more than the look and feel of navigation, it also includes guidelines for motion and animation, how to display various types of errors, and how to guide your users through an application for the first time. As a developer or designer, you may feel like this limits your creative freedom, and to some degree it actually does, but it also creates a clear message for your users on how your app expects to be used. This means that your users can use your app more easily, and it requires less cognitive load.
Another aspect of application development, which is of vital importance in any modern mobile application, is its performance. Users have come to expect that applications will always run smoothly, no matter the actual load on the system. The benchmark for all modern applications is 60 frames per second, that is, a full render event delivered to the user every 16.6 milliseconds.
Users don't just expect an application to perform well, they expect it to react instantly to external changes. When data is changed on the server side, users expect to see it on their device instantly. This makes the challenges of developing a mobile application, especially one with good performance, become even more difficult. Fortunately, Android comes with a fantastic toolset and an enormous ecosystem for dealing with these problems.
Android attempts to enforce good threading and performance behavior by timing each event that happens on the main thread and ensures that none of them take too long (and producing an Application Not Responding (ANR) error if they do). It further requires that no form of networking is conducted on the main thread, since these will invariably affect the application's performance. However, where this approach gets hard to work with is--any code related to the user interface must happen on the main thread, where all the input events are processed, and where all the graphics rendering code is run. This helps the user interface framework by avoiding any need for thread locks in what is very performance-centric code.
The Android Platform is a complete alternative to the Java Platform. While at a high level, the Android platform APIs are a form of Java framework; there are noticeable differences. The most obvious is that Android does not run Java bytecode, and does not include most of the Java standard APIs. Instead, most of the classes and structures you'll use are specific to Android. From this perspective, the Android platform is a bit like a large opinionated Java Framework. It attempts to reduce the amount of boilerplate code you write by providing you with skeleton structures to develop your applications.
The most common way to build user interfaces for Android is to do it declaratively in the layout XML files. You can also write user interfaces using pure Java code, but while potentially faster, it's not commonly used and carries some critical pitfalls. Most notably, the Java code is much more complex when handling multiple screen sizes. Instead of simply being able to reference a different layout file and have the resource system link in the best fit for the device, you have to handle these differences in your code. While parsing XML may seem a crazy idea on a mobile device, it's not nearly that bad; the XML is parsed and validated at compile time, and turned into a binary format which is what is actually read at runtime by your application.
Another reason it's really nice to write Android layouts in XML is the Android Studio layout editor. This gives you a real-time preview of what your layout will look like on a real device, and the blueprint view helps enormously when debugging issues like spacing and styling. There is also an excellent linting support in Android Studio that helps you avoid common problems before you're even finished writing your layout files.
Android Studio is a fully-featured IDE built on top of the IntelliJ platform, designed specifically for developing Android applications. It has a huge suite of built-in tools that will make your life better, and help you write better applications more rapidly.
You can download Android Studio for your favorite Operating System (OS) from https://developer.android.com/studio/. The setup instructions for each operating system vary slightly, and are available on the website. This book has been written assuming an Android Studio version of at least 3.0.
Once installed, Android Studio will also need to download and install an Android SDK for you to develop your applications on. There are platform options for virtually every version of Android ever released, including emulated hardware, which allows you to test how your application will run on different hardware and Android releases. It's best to download the latest Android SDK, and one of the older versions as well to check backward compatibility (4.1 or 4.4 are good options).
An Android application is very different in its internal structure when compared to applications on other platforms, in even the simplest details. Most platforms see their applications as monolithic systems with a fixed entry point. When the entry point returns or exits, the platform assumes that the application has finished running. On Android, an application may have several different entry points for the user, and others for the system. Each entry point has a different type, and different ways for the system to reach it (called intent filters). The most important part of an application from the user perspective is its activities. These (as the name suggests) are supposed to represent an action that the user will take with the application, such as the following:
- List my emails
- Compose an email
- Edit a contact
Activity is a non-abstract class extending the
Activity class (or any descendant of
Activity), and registers itself and its intent filters in the application manifest file. Here's an example of how the manifest entry for an
Activity that can view and edit contacts might look:
<activity android:name=".ContactActivity"> <intent-filter> <!-- Appear in the launcher screen as the main entry point of the application --> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <intent-filter> <!-- Handle requests to VIEW Uris with a mime-type of 'data/contact' --> <action android:name="android.intent.action.VIEW" /> <data android:mimeType="data/contact"/> </intent-filter> <intent-filter> <!-- Handle requests to EDIT Uris with a mime-type of 'data/contact' --> <action android:name="android.intent.action.EDIT" /> <data android:mimeType="data/contact"/> </intent-filter> </activity>
The application manifest file (always named as
AndroidManifest.xml) is how the Android system knows what components your application has, and how to reach each of them. It also contains information such as the permissions your application will need from the user, and which versions of the Android system the application will run on.
Activity is typically intended to do a single thing from the user's perspective, but this is not always the case. In the preceding case, there are three possible intent filters, each of which telling the system something different about the
- The first one tells the system that
ContactActivityshould have its icon displayed on the launcher screens, effectively making it the main entry point of the application
- The second tells the system that
ContactActivitycan be used to
VIEWcontent with a mime-type of
- The third tells the system that
ContactActivitycan also be used to
EDITcontent with the
The system resolves
Activity classes through Intents. Each Intent specifies how and what the application would like to do on behalf of the user, and the system uses the information to find a matching intent-filter somewhere in the system. However, you won't typically add intent-filters to all of your
Activity entries; you'll launch most by specifying the class directly within your application. Intent-filters are normally used to implement abstract inter-application interactions for example, when an application needs to "open a web page for browsing," the system can automatically launch the user's preferred web browser.
Activity will generally have a primary layout file defined as an XML resource. These layout resource files are generally not standalone, but will make use of other resources and even other layout files.
Keep your activities simple! Avoid loading too much behavior into a single
Activity class, and try and keep it bound to a single layout (and its variants, such as "landscape"). At worst, allow multiple behaviors with common layout widgets (for example, a single
Activity to view, or edit a single contact). We'll go through some techniques for this in Chapter 4, Composing User Interfaces.
The resource system in Android needs some special attention, as it allows for multiple files to collaborate together to create complex behavior out of simple components. At its heart, the resource system selects the most appropriate of each resource when it is requested (including from inside other resources). This not only allows you to create screen layouts for portrait and landscape mode, but allows you to do the same for dimensions, text, colors, or any other resource. Consider the following example:
<!-- res/values/dimens.xml --> <dimen name="grid_spacer1">8dp</dimen>
The above dimension resource can now be used in layout resource files by name:
<!-- res/layouts/my_layout.xml --> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="@dimen/grid_spacer1">
Using this sort of technique, you can adjust layouts for different screen sizes by simply changing the distance measurements used to space and size the widgets, rather than having to define completely new screen layouts.
It's a good idea to try and be generic with resources such as dimensions and colors. This will help keep your user interface consistent for your users. Consistent user interfaces are often more important than trying to innovate. The less mental effort your user needs to understand your application, the more they can engage with it.
Now that we've gone over the basics of an Android application structure, let's create a simple screen and see how things all fit together. We'll use Android Studio and one of its wonderful template activities. Just follow these easy steps:
- Start by opening Android Studio on your computer.
- Start a new project using the
Filemenu, or the quickstart dialog (depending on which one shows up for you).
- Name the project as
SimpleLayout, and leave any additional support (C++, Kotlin) off:
- On the next screen of the New Project wizard, ensure that you support Android 4.1 or higher, but leave only
Phone and Tabletchecked for this task:
- Android Studio comes with a fantastic selection of Activity templates available on the next screen. This will be the first
Activitygenerated to get you started with your project. For this example, you'll want to scroll down the list and find
Navigation Drawer Activity. Select it and click on
Activity details as their defaults (
MainActivity, and so on) and click on
Finish to complete the New Project wizard. Android Studio now creates your project and runs a first build-sync over to get everything working.
- Once your project has finished being generated, you'll be presented with the Android Studio layout editor, looking something like this:
Congratulations, this template provides an excellent starting point to explore how Android applications and their user interfaces are built and fit together.
At first glance, the layout editor in Android Studio is a standard WYSIWYG editor; however, it has several important features that you need to be aware of. Most importantly, it actually runs the code for the widgets in order to render them within the editor. This means that if you write a custom layout or widget, it will look and behave as it will on the emulator or on a device. This is fantastically useful for rapidly prototyping screens, and can drastically cut down on development time when used properly.
To ensure that your layout is being rendered correctly, you'll sometimes need to ensure that the layout editor is configured correctly. From the toolbar at the top of the layout editor, you can select the virtual device configuration you would like it to emulate. This includes whether the layout is being viewed in portrait or landscape mode, and even what language settings to use for the layout rendering and resource selection:
It's important to keep in mind that the list of available Android platform versions that the layout editor can emulate is limited, and it is not connected to the list that you have installed as virtual devices (so you cannot add new versions to the layout editor by installing additional platform versions). If you want to see how your user interfaces of versions that Android Studio doesn't directly support look, the only way to do it is to run the application.
The next really important thing to note is the attributes panel, which is docked to the right of the layout editor by default. When you select a component in the design area, the attributes panel allows tweaking of all the attributes that can be changed in XML, and of course, you get to see the results of any changes live in the layout editor:
The number of attributes is generally kept well under control by Android Studio. The default panel only shows the most commonly used attributes for the selected widget. To toggle between this shortlist and the list of all the available attributes (something you'll do more often than you think), you'll want to use the toggle button (
) at the top of the attributes panel.
However, when you look at the
All Attributes view, you'll note that their sheer number makes the view rather difficult to use. The easiest way to solve this is to use the search button (
) to find the attribute you're looking for. This will allow you to search for attributes by name, and is the quickest way to filter the list and get to the attribute, or group of attributes, that you're looking for (that is,
scroll will give you all the attributes containing the word
scrollbarStyle, and so on).
Android Studio gives you a fairly standard Java project structure, that is, you have your main source sets, tests, a resources directory, and so on, but that doesn't really cover all of your organizational needs. If you check the project structure we created, you might note some patterns:
You'll first note that only a single
MainActivity, but this
Activitytemplate has generated four layout files.
activity_main.xmlis actually referenced by
MainActivity; all the other files are included via the resource system.
The next thing to note is that the layout file referenced by
MainActivityis named as
actvitity_main.xml; this is a standard naming pattern that Android Studio will actually suggest when creating new
Activityclasses. It's a good idea, because it helps separate layouts used for
Activityclasses from those used elsewhere.
Next, take a look at the names of the other layout files. Each of them is also prefixed with
content. These prefixes help group the layout files logically in a file manager and in the IDE.
Finally, you'll note that the
valuesdirectory has several XML files in it. The entire
valuesdirectory is actually treated as one big XML file by the resource compiler, but it helps keep it organized by the type of resources being declared.
Use filename prefixes in the resources directories (especially layouts) to keep things organized. You cannot break things down into subdirectories, so a prefix is the only way to group files together logically. Common prefixes are "activity", "fragment", "content", and "item", which are commonly used to prefix layouts that are used to render list items and so on.
- If you open the
MainActivityclass now, you'll see how the layout is loaded and bound. The first thing
MainActivitydoes when it's created is to call
onCreateto its parent class (which is a mandatory step, and failure to do so will result in an exception). Then, it loads its layout file using the
setContentViewmethod. This method call does two things at once: it loads the layout XML file, and adds its root widget as the root of the
Activity(replacing any widgets that were already there). The
Rclass is defined by the resource compiler, and kept in sync for you by Android Studio. Every file and value resource will have its own unique identifier, which allows you to keep things tightly bound together. Rename a resource file, and its corresponding field will change:
You'll then note that
MainActivityretrieves various widgets that were included in the layout files by their own IDs (also defined in the
findViewByIdmethod searches through the
Activitylayout for a widget with the corresponding
id, and then returns it:
// MainActivity.java Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar);
findviewById method works by traversing all of the widgets in an
Activity in a series of loops. There is no lookup table or optimize this process. As such, you should call the
findViewById method in
onCreate and keep a class-field reference to each of the
View objects you'll need.
The preceding code snippet will return the
Toolbarobject declared in the
app_bar_main.xmllayout resource file:
<!-- app_bar_main.xml --> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:popupTheme="@style/AppTheme.PopupOverlay" />
As you saw, an Android application comprises of more modular components, which assemble in layers, and are often directly accessible from the platform. The resource management system is your greatest ally and should be leveraged to provide your users with a consistent experience, and keep your user interface consistent. When it comes to arranging your application, Android Studio has a variety of tools and features that it will use to help you keep things organized and within commonly understood patterns. However, it's also important to stick to your own patterns and keep things organized. The Android toolkits have their own requirements, and you'll need to obey their rules if you want to benefit from them.
Android Studio also has an excellent collection of template projects and Activities, and they should be used to get your projects kick-started. They can also often serve with explanations for how common user interface design patterns are implemented in Android.
In the next chapter, we'll take a look at starting a layout from scratch and how to approach designing a form screen.