Professional Documents
Culture Documents
Learning Kotlin by Building Android Applications Explore The Fundamentals of Kotlin by Building Real-World Android... (Eunice Adutwumwaa Obugyei, Natarajan Raman) (Z-Library)
Learning Kotlin by Building Android Applications Explore The Fundamentals of Kotlin by Building Real-World Android... (Eunice Adutwumwaa Obugyei, Natarajan Raman) (Z-Library)
Android Applications
BIRMINGHAM - MUMBAI
Learning Kotlin by Building Android
Applications
Copyright © 2018 Packt Publishing
All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form
or by any means, without the prior written permission of the publisher, except in the case of brief quotations
embedded in critical articles or reviews.
Every effort has been made in the preparation of this book to ensure the accuracy of the information presented.
However, the information contained in this book is sold without warranty, either express or implied. Neither the
authors, nor Packt Publishing or its dealers and distributors, will be held liable for any damages caused or alleged to
have been caused directly or indirectly by this book.
Packt Publishing has endeavored to provide trademark information about all of the companies and products
mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy
of this information.
ISBN 978-1-78847-464-1
www.packtpub.com
mapt.io
Mapt is an online digital library that gives you full access to over 5,000 books and videos, as
well as industry leading tools to help you plan your personal development and advance
your career. For more information, please visit our website.
Why subscribe?
Spend less time learning and more time coding with practical eBooks and Videos
from over 4,000 industry professionals
Improve your learning with Skill Plans built especially for you
PacktPub.com
Did you know that Packt offers eBook versions of every book published, with PDF and
ePub files available? You can upgrade to the eBook version at www.PacktPub.com and as a
print book customer, you are entitled to a discount on the eBook copy. Get in touch with us
at service@packtpub.com for more details.
At www.PacktPub.com, you can also read a collection of free technical articles, sign up for a
range of free newsletters, and receive exclusive discounts and offers on Packt books and
eBooks.
Contributors
Natarajan Raman has close to 15 years of experience in software design and development.
He is a Google-certified Nano degree holder in Android development and was invited as a
guest by Google for the I/O 2017. His Android App Idea for special children got selected as
one of the top 6 ideas out of 80-odd ideas, and was also featured by Google in the Code it
possible program. He works for Patterns and is also the managing trustee of Dream India.
I would like to thank my parents, Mr. Raman and Mrs. Alamu Raman, my in-laws,
family, friends, employer, colleagues, publishers, and my children of Vasantham
(www.vasantham.org) for their support. Special thanks to Ashok and Mr. Suresh Kamath.
I dedicate this to my sweet little kid, Master Siddheshwar, and my wife, Ms. Krupa, who
make each day of my life beautiful.
About the reviewer
Aanand Shekhar Roy is the author of Kotlin Programming Cookbook. He has worked with
many start-ups in the domain of social media management, ad:tech, and FinTech. He is
currently working as a senior Android developer at PushCoin and loves to write articles
and blogs on Android development. You can reach out to him on Twitter
at @aanand_shekhar.
This book is dedicated to all the programmers who are interested in learning about Kotlin
and Android development. I would love to thank the Packt team for providing me with all
the necessary resources that helped me in the reviewing process.
Constructors 65
Data classes 67
Objects 69
Summary 72
Chapter 5: Type Checks and Null Safety 73
Null safety 73
Nullable and non-nullable types 74
Safe call operator 75
The Elvis operator 75
The !! operator 75
Type checks 76
Cast operator 76
Summary 77
Chapter 6: Functions and Lambdas 78
Functions 78
Parameters 79
Higher–order functions and lambdas 80
Lambda expressions 80
Implementing a game status check 82
Summary 85
Chapter 7: Developing Your Location-Based Alarm 86
Creating a project 86
Generation of a Google Maps API key 90
Running the app 96
Understanding the code 97
Customizing the code 98
Finding the Lat and Lng of a place 99
The XML layout 102
Developing a screen for user input 103
The AndroidManifest file 105
Build.gradle 107
Summary 108
Chapter 8: Working with Google's Location Services 109
Integrating shared preferences 110
Adding permissions 114
Integration of the location API 116
Classes and variables 116
The Google API client 117
Matching the location 121
Summary 123
Chapter 9: Connecting the Outside World – Networking 124
[ ii ]
Table of Contents
[ iii ]
Table of Contents
[ iv ]
Table of Contents
[v]
Preface
"The purpose of education is to make good human beings with skills and expertise. Real
education enhances the dignity of a human being and increases his or her self-respect. If
only the real sense of education could be realized by each individual and carried forward in
every field of human activity, the world will be so much a better place to live in."
Over 2 billion Android users are active daily, and this reach of data to billions across the
globe provides a real opportunity to make a difference. A phone, which was just a device
for voice communication until recently, is not just a phone anymore. Enabling and
empowering the next billion users of Android devices will play a significant role in the
future.
The purpose of technology is to enable humanity, and the availability of powerful devices
provides the opportunity for innovations and can improve the lives of people through
technology. Whether it is an app that is built for farmers that provides useful information
about the weather or prices of crops, or an app that is built for children with special needs
to express themselves, or an app that is built for economically challenged women to run a
small home-based business, the opportunities ahead are plenty and very exciting.
This book aims to enable developers who would like to explore the power of Android and
experience and enjoy the journey of building Android applications. The chapters have been
organized to enable fresh developers to understand and start from the basics, or if you are
an experienced developer, to get ahead and explore the power of Kotlin.
To make things much more interesting and easy on this app development journey, Google
has added Kotlin as one of the official languages for Android application development.
Kotlin is expressive, concise, and powerful. It also ensures seamless interoperability with
existing Android languages such as Java and the runtime.
Preface
This book will also certainly help those who are already into Android application
development using Java and trying to switch over to Kotlin or evaluate the ease of use of
Kotlin. Android app developers who are well versed with Java will find the comparisons of
code between Java and Kotlin very useful, and will be able to appreciate the power of
Kotlin.
Chapter 2, Configuring Your Environment for Kotlin, details the steps required to configure
the environment for Kotlin. Even though the latest and stable version of Android Studio
provides Kotlin support out of the box, the information in this chapter will help you
configure the development IDE of your choice.
Chapter 3, Data Types, Variables, and Constants, introduces and discusses the details of
Kotlin language constructs, such as data types, variables, and constants.
Chapter 4, Classes and Objects, enhances the discussion on language constructs further by
providing information on classes and objects and explains how to define and handle them.
Chapter 5, Type Checks and Null Safety, discusses the salient features of Kotlin – type checks
and Null Safety. Kotlin eliminates null reference completely by its design.
Chapter 6, Functions and Lambdas, provides information on defining a function and using it
in our program. This chapter also discusses Lambdas and provides information on their
use.
[2]
Preface
Chapter 8, Working with Google's Location Services, shows how to build apps using location-
based services and explains how to build our own location-based alarm.
Chapter 9, Connecting the Outside World – Networking, covers the concepts of networking
and communication with the outside world. We discuss the out-of-the-box options Android
framework provides, as well third-party libraries such as Picasso and Glide.
Chapter 10, Developing a Simple To-Do List App, discusses building a user interface using
Android Studio and provides information on working with ListViews and Dialogs.
Chapter 11, Persisting with Databases, provides a brief introduction on relational databases,
discusses CRUD operations on SQLite in detail, and looks at the use of ORMs, specifically
the Room ORM from Google.
Chapter 12, Setting Reminders for Tasks, discusses setting up and pushing notifications to
users from our app. It also explains how to utilize cloud services such as Firebase and
Amazon SNS. We also discuss services and broadcast receivers.
Chapter 13, Testing and Continuous Integration, discusses the importance of testing, the out-
of-the-box support provided by Android Studio through Android Testing Support Library,
how to use Crashlytics to track crash reports, and beta testing. This chapter also provides
an introduction to CI and provides detailed steps on the use of tools such as Jenkins,
Bamboo, and Fastlane.
Chapter 14, Making Your App Available to the World, explains how to publish your app to
Google Play Store and Amazon App Store.
Chapter 15, Building an App Using the Google Faces API, discusses the use of the Google
Faces API and how to build applications using it. This chapter also provides information on
creating Paint instances and using a canvas to draw images. This chapter also discusses
drawing shapes such as rectangles on the image.
[3]
Preface
The latest stable version of Android Studio (version 3.1.3 at the time of publishing)
provides support for Kotlin out of the box.
Once the file is downloaded, please make sure that you unzip or extract the folder using the
latest version of:
The code bundle for the book is also hosted on GitHub at https://github.com/
PacktPublishing/Learning-Kotlin-by-building-Android-Applications. In case there's
an update to the code, it will be updated on the existing GitHub repository.
We also have other code bundles from our rich catalog of books and videos available
at https://github.com/PacktPublishing/. Check them out!
Conventions used
There are a number of text conventions used throughout this book.
CodeInText: Indicates code words in text, database table names, folder names, filenames,
file extensions, pathnames, dummy URLs, user input, and Twitter handles. Here is an
example: "Mount the downloaded WebStorm-10*.dmg disk image file as another disk in
your system."
[4]
Preface
Bold: Indicates a new term, an important word, or words that you see onscreen. For
example, words in menus or dialog boxes appear in the text like this. Here is an example:
"Select System info from the Administration panel."
Get in touch
Feedback from our readers is always welcome.
General feedback: Email feedback@packtpub.com and mention the book title in the
subject of your message. If you have questions about any aspect of this book, please email
us at questions@packtpub.com.
Errata: Although we have taken every care to ensure the accuracy of our content, mistakes
do happen. If you have found a mistake in this book, we would be grateful if you would
report this to us. Please visit www.packtpub.com/submit-errata, selecting your book,
clicking on the Errata Submission Form link, and entering the details.
[5]
Preface
Piracy: If you come across any illegal copies of our works in any form on the Internet, we
would be grateful if you would provide us with the location address or website name.
Please contact us at copyright@packtpub.com with a link to the material.
If you are interested in becoming an author: If there is a topic that you have expertise in
and you are interested in either writing or contributing to a book, please visit
authors.packtpub.com.
Reviews
Please leave a review. Once you have read and used this book, why not leave a review on
the site that you purchased it from? Potential readers can then see and use your unbiased
opinion to make purchase decisions, we at Packt can understand what you think about our
products, and our authors can see your feedback on their book. Thank you!
[6]
1
Setting Up for Android
Development
Java is one of the most widely used languages worldwide and, until recently, was the
language of choice for Android development. Java, in all its greatness, still has some issues.
Over the years, we've seen the evolution of a number of JVM languages that have tried to
fix the issues that come with Java. A quite recent one is Kotlin. Kotlin is a new
programming language developed by JetBrains, a software development company that
produces software developer tools (one of their products is IntelliJ IDEA, which Android
Studio is based on).
Concise
One of Java's biggest issue is verbosity. Anyone who has ever tried writing a simple hello
world program in Java will tell you the number of lines of code that requires. Unlike Java,
Kotlin is not a verbose language. Kotlin eliminates a lot boilerplate code such as getters
and setters. For example, let's compare a POJO in Java to the same POJO in Kotlin.
As you can see, there's way less Kotlin code for the same functionality.
[8]
Setting Up for Android Development Chapter 1
Java interoperability
Kotlin is developed to be able to work comfortably with Java. What this means for
developers is that you can make use of the libraries written in Java. You can also work with
legacy Java code without worry. And, the fun part about it is you can also call Kotlin code
in Java.
This feature is very important for Android developers because, currently, Android APIs are
written in Java.
You can skip this section if you're not new to Android development.
[9]
Setting Up for Android Development Chapter 1
Java
Since Kotlin runs on the JVM, we have to make sure that our machine has the Java
Development Kit (JDK) installed. If you do not have Java installed, skip to the section on
installing the JDK. If you're not certain, you can follow the following instructions to check
the version of Java installed on your machine.
On Windows:
1. Open the Terminal app. To do this, open launchpad and type terminal in the
search box. The Terminal app will show up as shown in the following screenshot.
Select it:
[ 10 ]
Setting Up for Android Development Chapter 1
2. In Terminal, type the following command to check the JDK version on your
machine: java -version
3. If you have the JDK installed, the version of Java will be displayed as shown in
the following screenshot:
[ 11 ]
Setting Up for Android Development Chapter 1
3. On the next screen, select the Accept License Agreement checkbox and click on
the download link for the product that matches your operating system
4. When the download is complete, go ahead and install the JDK
5. When the installation is complete, you can run the version check command again
to be sure your installation was successful
Android Studio
A number of IDEs support Android development, but the best and most used Android IDE
is Android Studio. Android Studio is based on the IntelliJ IDE (developed by JetBrains).
[ 12 ]
Setting Up for Android Development Chapter 1
On the popup that appears, read and accept the terms and conditions and click
the DOWNLOAD ANDROID STUDIO FOR MAC button:
The download will begin and you'll be redirected to an instructions page (https://
developer.android.com/studio/install).
Follow the instructions specified for your operating system to install Android Studio. When
the installation is complete, open Android Studio and start the setup process.
[ 13 ]
Setting Up for Android Development Chapter 1
On the Welcome screen, click Next to move to the Install Type screen:
[ 14 ]
Setting Up for Android Development Chapter 1
[ 15 ]
Setting Up for Android Development Chapter 1
On the Verify Settings screen, confirm your setup by clicking the Finish button:
[ 16 ]
Setting Up for Android Development Chapter 1
The SDK components listed on the Verify Settings screen will start downloading. You can
click on the Show Details button to view the details of the components being downloaded:
When the download and installation is complete, click the Finish button. That's it. You're
done installing and setting up Android Studio.
[ 17 ]
Setting Up for Android Development Chapter 1
This starts the Create New Project wizard. On the Configure your new project screen,
enter TicTacToe as the Application name. Specify the Company domain. The Package
name is generated from the company domain and the application name.
Set the Project location to a location of your choice, and click Next:
[ 18 ]
Setting Up for Android Development Chapter 1
Choosing an SDK
On the Target Android Devices screen, you have to select the device types and the
corresponding minimum version of Android required to run your app. The
Android Software Development Kit (SDK) provides tools required to build your Android
app irrespective of your language of choice.
Each new version of the SDK comes with a new set of features to help developers provide
more awesome features in their apps. The difficulty, though, is Android runs on a very
wide range of devices, some of which do not have the capabilities to support the latest
versions of Android. This puts developers in a tough position of choosing between
implementing great new features or supporting a wider range of devices.
[ 19 ]
Setting Up for Android Development Chapter 1
Data on the percentage of devices using specific SDKs to help developers make
an informed choice. To view this data in Android Studio, click Help me
choose under the minimum SDK dropdown. This will show you a list of
currently supported Android SDK versions with their supported features, and
the percentage of Android devices your app will support if you select that as
your minimum SDK:
You can check out an up-to-date and more detailed version of that data on the
Android developer dashboard (https://developer.android.com/about/
dashboards/).
[ 20 ]
Setting Up for Android Development Chapter 1
We'll discuss SDK versions further in a later section. For now, you can select API 15:
Android 4.0.3 (IceCreamSandwich) and click Next:
[ 21 ]
Setting Up for Android Development Chapter 1
The next screen is the Add an Activity to Mobile screen. This is where you select your
default activity. Android Studio gives a number of options, from an activity with a blank
screen to an activity with a login screen. For now, select the Basic Activity option and click
Next:
On the next screen, enter the name and title of the activity, and the name of the activity
layout. Then, click Finish:
[ 22 ]
Setting Up for Android Development Chapter 1
Gradle
Gradle is a build automation system that is easy to use, and can be used to automate the life
cycle of your project, from building and testing to publishing. In Android, it takes your
source code and configured Android build tools and generates an Android Package
Kit (APK) file.
[ 23 ]
Setting Up for Android Development Chapter 1
Android Studio generates the basic Gradle configurations needed to build your initial
project. Let's take a look at those configurations. Open build.gradle:
[ 24 ]
Setting Up for Android Development Chapter 1
versionCode: Specifies the version number of your app. This should be changed
for every new version before publishing.
[ 25 ]
Setting Up for Android Development Chapter 1
You can read more about the emulator on the developer page, at https://
developer.android.com/studio/run/emulator.
[ 26 ]
Setting Up for Android Development Chapter 1
Or, alternatively, by selecting Tools | Android | AVD Manager from the menu:
[ 27 ]
Setting Up for Android Development Chapter 1
On the Your Virtual Devices screen, click the Create Virtual Device... button:
If you already have an emulator created, the button will be at the bottom of
the screen:
[ 28 ]
Setting Up for Android Development Chapter 1
The next step is to select the type of device you want to emulate. The AVD Manager allows
you to create emulators for TVs, phones, tablets, and Android wear devices.
Make sure the Phone is selected in the Category section on the left-hand side of the screen.
Go through the list of devices in the middle of the screen and choose one. Then, click Next:
[ 29 ]
Setting Up for Android Development Chapter 1
On the System Image screen, select the version of Android you want your device to run on,
and click Next:
If the SDK version you want to emulate is not downloaded, click on the
Download link next to it in order to download it.
[ 30 ]
Setting Up for Android Development Chapter 1
On the Verify Configuration screen, go through and confirm the virtual device settings by
clicking the Finish button:
[ 31 ]
Setting Up for Android Development Chapter 1
You will be sent back to the Your Virtual Devices screen, with your new emulator showing
the following:
You can click on the play icon under the Actions tab to start the emulator, or the pencil icon
to edit its configurations.
[ 32 ]
Setting Up for Android Development Chapter 1
Let's go ahead and start the emulator we just created by clicking on the play icon:
As you may have noticed, the virtual device comes with a toolbar on the right-hand side.
That toolbar is known as the emulator toolbar. It gives you the ability to emulate
functionalities such as shutdown, screen rotation, volume increase and decrease, and zoom
controls.
[ 33 ]
Setting Up for Android Development Chapter 1
Clicking on the More(...) icon at the bottom of the toolbar also gives you access to extra
controls to simulate functionalities such as fingerprint, device location, message sending,
phone calls, and battery power:
[ 34 ]
Setting Up for Android Development Chapter 1
On the Select Deployment Target screen that pops up, select the device you want to run
the app on and click OK:
[ 35 ]
Setting Up for Android Development Chapter 1
Android Studio will build and run your app on the emulator:
[ 36 ]
Setting Up for Android Development Chapter 1
If you do not already have an emulator running, your emulators will show
up under the Available Virtual Devices section. Selecting them will start the
emulator and then run your app on it:
[ 37 ]
Setting Up for Android Development Chapter 1
4. You will be prompted about the danger that comes with installing apps from
Unknown sources. Read carefully and click OK to confirm.
5. That's it. You can now upload your APK and run it on the phone.
You can easily disable the Unknown Sources setting by going back to
Settings | Security and turning off the option.
We can all agree that this way of running your app is not very ideal, especially for
debugging. With this in mind, Android devices come with the ability to run and debug
your app very easily without having to upload your app to the device. This can either be
done by connecting your device via a USB cable. To do this, Android requires Developer
Mode to be enabled. Follow the instructions below to enable Developer Mode:
[ 38 ]
Setting Up for Android Development Chapter 1
You're now set to run your app on the device. Once again, click the Run button on the
toolbar, select your device in the options shown in the Select Deployment Target dialog,
and click OK:
[ 39 ]
Setting Up for Android Development Chapter 1
That's it. You should now have your app showing on your device:
Summary
In this chapter, we went through the process of checking and installing the JDK, which is
required for Android development. We also installed and set up our Android Studio
environment. We created our first Android app and learned to run it on an emulator and
on an actual device.
In the next chapter, we'll learn to configure and set up Android Studio and our project for
development with Kotlin.
[ 40 ]
2
Configuring Your Environment
for Kotlin
In this chapter, we will go through the process of preparing Android Studio and
configuring the project we created in the previous chapter for Kotlin development.
2. On the Plugins window, click the Install JetBrains plugin... button at the bottom
of the screen:
[ 42 ]
Configuring Your Environment for Kotlin Chapter 2
3. On the Browse Jetbrains Plugins screen, search for Kotlin and select Kotlin
from the list of options. Then, click the Install button:
4. When the download and installation is complete, click on the Restart Android
Studio button to restart the IDE.
The latest version of Android Studio, and the one above version 3.0, offer
full support for Kotlin out of the box. In the versions below 3.0, Kotlin
support can be enabled by installing the plugin, as shown previously.
[ 43 ]
Configuring Your Environment for Kotlin Chapter 2
2. Next, select the Android with Gradle option in the Choose Configurator popup:
3. On the Configure Kotlin with Android with Gradle popup, select the version of
Kotlin to use, and click OK:
[ 44 ]
Configuring Your Environment for Kotlin Chapter 2
This will cause a number of changes in the build.gradle files in your project:
[ 45 ]
Configuring Your Environment for Kotlin Chapter 2
Starting from Android Studio 3.0, Android Studio comes with built-in
Kotlin support. As such, you won't have to install the Kotlin plugin to be
able to use it.
Now that we have Kotlin completely configured, let's take it for a spin.
[ 46 ]
Configuring Your Environment for Kotlin Chapter 2
Let's try creating a Kotlin class. The Android Studio Kotlin plugin makes this as easy as
creating a Java class. Select File | New | Kotlin File/Class:
On the New Kotlin File/Class popup, enter the name of your class, select Class from
the Kind dropdown, and click OK:
[ 47 ]
Configuring Your Environment for Kotlin Chapter 2
class HelloKotlin {
}
This ability of Kotlin to make use of Java code is called interoperability. This feature also
works the other way round, allowing Kotlin code to be called from a Java class. Let's try
that:
Open MainActivity.java. In the onCreate() method, replace the following line of code:
Snackbar.make(view, "Replace with your own action",
Snackbar.LENGTH_LONG).setAction("Action", null).show();
The preceding line of code creates an instance of the HelloKotlin class, and calls
its displayMessage() method.
[ 48 ]
Configuring Your Environment for Kotlin Chapter 2
Kotlin to Java?
So far, we've gone through the process of creating a Kotlin class and accessing its method in
our MainActivity.java class. Our project currently consists of a Java class and a Kotlin
class, but we want our entire project to be in Kotlin. So, what do we do? Do we have to
rewrite the MainActivity.java class in Kotlin? No. One of the functionalities the Kotlin
plugin adds to Android Studio is the ability to convert code from Java to Kotlin.
[ 49 ]
Configuring Your Environment for Kotlin Chapter 2
To do this, open the MainActivity.java class and go to Code | Convert Java File to Kotlin File:
You will be prompted with a warning message about the accuracy of the conversion. For
now, we don't need to worry about that. Just click OK to continue:
[ 50 ]
Configuring Your Environment for Kotlin Chapter 2
}
}
You will also notice that the extension of the file has also changed to .kt.
[ 51 ]
Configuring Your Environment for Kotlin Chapter 2
Summary
In this chapter, we learned to configure Android Studio and an Android project for Kotlin
development. We also learned to create and call a Kotlin class from Java. We also learned to
use the Kotlin plugin to convert a Java source file to Kotlin. These functionalities are
particularly helpful if you have legacy code written in Java and you want to gradually
switch to Kotlin.
[ 52 ]
Configuring Your Environment for Kotlin Chapter 2
In using the Convert Java to Kotlin functionality, keep in mind that in some cases,
you'll have to perform some corrections in the resulting Kotlin file.
In the next few chapters, we will add more functionality to our project (which, as you may
have guessed, is a simple TicTacToe game). In the process, we will delve more deeply into
the basics of Kotlin as a language. We will cover topics such as data types, classes,
functions, coroutines and Null safety.
[ 53 ]
3
Data Types, Variables, and
Constants
In this chapter, we will start building our TicTacToe game while learning about data types,
variables, and constants in Kotlin.
If you don't see the preview on the right-hand side, enable it by going
to View | Tool Windows | Preview.
Now, let's take a look at the separate elements of the main activity layout:
[ 55 ]
Data Types, Variables, and Constants Chapter 3
2. The element labeled 2 is the Toolbar, which acts as the top navigation for your
app. It's usually used to display the title of the app, the app logo, action menu,
and navigation button.
3. The include tag is used to embed a layout into another layout. In this case,
the res/layout/content_main.xml file contains the TextView (the one
displaying the Hello World! message) we see when we run the app. Most of our
UI changes will be done in the res/layout/content_main.xml file.
4. The FloatingActionButton, as you may have noticed, is an actionable
ImageView that floats on the screen.
<TableRow
android:id="@+id/r0"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:background="@android:color/black">
<TextView
android:layout_width="100dp"
android:layout_height="100dp"
android:gravity="center"
android:background="@android:color/white"
android:layout_marginBottom="2dp"
android:layout_marginTop="0dp"
[ 56 ]
Data Types, Variables, and Constants Chapter 3
android:layout_column="0"
android:layout_marginRight="2dp"
android:layout_marginEnd="2dp"
android:textSize="64sp"
android:textColor="@android:color/black"
android:clickable="true"/>
<TextView
android:layout_width="100dp"
android:layout_height="100dp"
android:gravity="center"
android:background="@android:color/white"
android:layout_marginBottom="2dp"
android:layout_marginTop="0dp"
android:layout_column="2"
android:layout_marginRight="2dp"
android:layout_marginEnd="2dp"
android:textSize="64sp"
android:textColor="@android:color/black"
android:clickable="true"/>
<TextView
android:layout_width="100dp"
android:layout_height="100dp"
android:gravity="center"
android:background="@android:color/white"
android:layout_marginBottom="2dp"
android:layout_marginTop="0dp"
android:layout_column="2"
android:layout_marginRight="2dp"
android:layout_marginEnd="2dp"
android:textSize="64sp"
android:textColor="@android:color/black"
android:clickable="true"/>
</TableRow>
</TableLayout>
The TableRow element represents a single row of the table. From the preceding
code, each element of the row is represented by a TextView.
Each TextView has similar attributes.
The previous code declares a 1x3 table, in other words, a table with a single row
and three columns. Since we want to create a 3x3 grid, we will need to add two
more TableRow elements.
[ 57 ]
Data Types, Variables, and Constants Chapter 3
The previous code already contains a lot of duplicated code. We need to find a way to
reduce the amount of duplicates. That's where res/values comes in.
Before adding the two extra TableRow elements, let's organize our code better. Open
res/values/styles.xml and add the following lines of code:
</style>
[ 58 ]
Data Types, Variables, and Constants Chapter 3
You can create child styles who inherit from the parent by naming them in
the format Parent.child, for instance, Cell.Left, Cell.Middle, and
Cell.Right all inherit the attributes of the Cell style.
Next, open res/values/dimens.xml. This is where you declare dimensions used in your
layouts. Add the following lines of code to the resource element:
<dimen name="board_padding">16dp</dimen>
<dimen name="cell_margin">2dp</dimen>
<dimen name="large_text">64sp</dimen>
Now, open the res/values/strings.xml. This is where you declare string resources
needed within the app. Add the following lines of code in the resource element:
<string name="x">X</string>
<string name="o">O</string>
<string name="turn">%1$s\'s Turn</string>
<string name="winner">%1$s Won</string>
<string name="draw">It\'s a Draw</string>
[ 59 ]
Data Types, Variables, and Constants Chapter 3
<TableRow
android:id="@+id/r2"
style="@style/TableRow">
<TextView
style="@style/Cell.Left"
android:layout_marginBottom="0dp"/>
<TextView
style="@style/Cell.Middle"
android:layout_marginBottom="0dp"/>
<TextView
style="@style/Cell.Right"
android:layout_marginBottom="0dp"/>
</TableRow>
We now have all three rows declared. As you can see, our code is looking more organized.
[ 60 ]
Data Types, Variables, and Constants Chapter 3
Let's go ahead and add a TextView that will be used to display whose turn it is.
Open res/layout/activity_main.xml and add the following TextView declaration
just before the include element:
<TextView
android:id="@+id/turnTextView"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/turn"
android:textSize="64sp"
android:textAlignment="center"
android:layout_marginTop="@dimen/fab_margin"/>
Let's change the icon and background color for the FloatingActionButton by replacing
the following code:
app:srcCompat="@android:drawable/ic_dialog_email"
[ 61 ]
Data Types, Variables, and Constants Chapter 3
Basic types
In Kotlin, there's no concept of primitive datatypes. All types are objects with member
functions and properties.
val c = "Constant"
var d: String? = null // nullable String
For instance, in the preceeding code, a and b will both be treated as String. When trying
to reassign a variable with an inferred type, a value of a different type will throw an
error. A val can only be initialized once.
Properties
In Kotlin, properties are accessed by simply referring to them by name. As much as
getters and setters are not a requirement, you are not prohibited from creating them. A
property's getter and/or setter can be created as part of its declaration. A setter is not
allowed if the property is a val. A property needs to be initialized at the point of creation:
var a: String = "" // required
get() = this.toString() // optional
set(v) { // optional
if (!v.isEmpty()) field = v
}
[ 62 ]
Data Types, Variables, and Constants Chapter 3
Let's go ahead and declare a few properties that we will require in our MainActivity
class:
var gameBoard : Array<CharArray> = Array(3) { CharArray(3) } // 1
var turn = 'X' // 2
var tableLayout: TableLayout? = null // 3
var turnTextView: TextView? = null // 4
1. gameBoard is a 3x3 matrix that represents a single TicTacToe game board. It will
be used to store the value of each cell on the board.
2. turn is a char that stores whose turn it is at the moment, X or O.
3. tableLayout is an android.widget.TableLayout that will be initialized in
the onCreate() method with the view from the xml layout.
4. turnTextView is an android.widget.TextView that is used to display whose
turn it is at the moment. This will also be initialized in the onCreate() method
with the view from the xml layout.
Summary
In this chapter, we designed the user interface for our simple TicTacToe game. We also
learned how to work with variables and constants in Kotlin.
In the next chapter, we will go ahead and start implementing the logic for our game while
learning about classes and objects.
[ 63 ]
4
Classes and Objects
In this chapter, we will continue to work on our TicTacToe game whilst learning about
classes and objects in Kotlin.
Structure of a class
Just like Java, classes in Kotlin are declared using the class keyword. The basic structure
of a class consists of:
The header can consist of a primary constructor, a parent class if applicable, and an
interface to implement if applicable.
Of all four parts, only the first two are compulsory. If the class has no
body, you can skip the curly braces.
Classes and Objects Chapter 4
Constructors
Just like in Java, a class can have multiple constructors but, in Kotlin, the primary
constructor can be added as part of the header of the class.
In the previous code, the HelloKotlin class has a primary constructor that takes a
string called message.
Since the constructor does not have any modifiers, we can rid it of the constructor
keyword altogether:
class HelloKotlin (message: String) {
In Kotlin, secondary constructors have to call the primary constructor. Let's take a look at
the code:
class HelloKotlin (message: String) {
[ 65 ]
Classes and Objects Chapter 4
There are two ways to go about this. You can create a field in HelloKotlin and initialize it
with the message parameter passed:
class HelloKotlin (message: String) {
You can also add the appropriate keyword to the message parameter to make it a field of
the class as well:
class HelloKotlin (private var message: String) {
constructor(): this("Hello Kotlin!!")
Let's take the changes we've made for a spin. In the onCreate() method in the
MainActivity class, let's replace the HelloKotlin initialization:
HelloKotlin().displayKotlinMessage(view)
[ 66 ]
Classes and Objects Chapter 4
The message passed is shown at the bottom when we click on the FloatingActionButton.
Data classes
When building apps, most of the time we require classes whose only function is to store
data. In Java, we usually use a POJO for this. In Kotlin, there's a special class for that known
as the data class.
Let's say we want to keep a scoreboard for our TicTacToe game. How will we store the data
for each game session?
[ 67 ]
Classes and Objects Chapter 4
In Java, we'll create a POJO that will store data about the game session (the board at the end
of the game and the winner of that game):
public class Game {
The previous single line of code does the same thing as the previous 26 lines of Java code. It
declares a Game class that takes two parameters in its primary constructor. As stated earlier,
the getters and setters are not needed.
The data class in Kotlin also comes with a number of other methods:
equals()/hashCode() pair
toString()
copy()
If you've ever written any Java code, you should be familiar with equals(), hashCode(),
and toString(). Let's go ahead and discuss copy().
[ 68 ]
Classes and Objects Chapter 4
The copy() method comes in handy when you want to create a copy of an object but with
part of its data altered, for example:
data class Student(var name: String, var classRoomNo: Int, var studentId:
Int) // 1
1. We declare a data class called Student. It takes three parameters in its primary
constructor: name, classRoomNo, and studentId.
2. The anna variable is an instance of the Student with the following properties:
name:Anna, classRoomNo:5, and studentId:1.
3. The variable joseph is created from copying anna and changing two of the
properties—name and studentId.
Objects
Before we delve into a discussion about objects, let's make some additions to the TicTacToe
game. Let's initialize our views. Add the following lines of code to the onCreate() method
in the MainActivity class:
turnTextView = findViewById(R.id.turnTextView) as TextView // 1
startNewGame(true)
[ 69 ]
Classes and Objects Chapter 4
}
}
}
}
We need to call cellClickListener() every time any of the cells are clicked. To do this,
we need to add a click listener to each one of them. In Android, we use the
View.OnClickListener. Since View.OnClickListener is an interface, we normally
create a class that implements its methods and set that class as our click listener.
[ 70 ]
Classes and Objects Chapter 4
Both Java and Kotlin have a way of simplifying this. In Java, you can get around it by using
an Anonymous Inner Class. An Anonymous Inner Class allows you to declare and create
an instance of a class at the same time:
// Java Anonymous Inner Class
cell.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
In the preceding code, we declare and create an instance of a class that implements the
View.OnClickListener interface.
Place the following lines of code in the body of the if (setClickListener) statement in
the startNewGame() method:
cell.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
cellClickListener(i, j)
}
})
Build and run. Now, when you click on any of the cells, the text in it will change to that of
the turnTextView and the turnTextView value will also change to that of the next player:
[ 71 ]
Classes and Objects Chapter 4
Summary
In this chapter, we learned about classes, data classes, and object expressions, while
initializing our views and adding extra logic to our game app.
In the next chapter, we will delve into the topic of type checks and null safety and why
these features of Kotlin make it one of the safest languages.
[ 72 ]
5
Type Checks and Null Safety
As mentioned in Chapter 1, Setting Up for Android Development, one of the great features
that Kotlin brings to the table is Null safety. In this chapter, we will learn about what
makes Kotlin a null-safe language and how we can take full advantage of it.
Null safety
One of the most common pain points developers have with Java and a number of other
languages has to do with accessing a member of a null reference. In most languages, this
results in a null reference exception at runtime. Most Java developers know this as the
NullPointerException.
Kotlin is designed to eliminate the possibility of running into null reference, exceptions as
much as possible. As stated in Chapter 1, Setting Up for Android Development, there are only
four possible reasons why you could run into a NullPointerException in Kotlin:
Kotlin's type system is designed to distinguish between these two types of references.
Nullable types are declared by appending a ? at the end of the type. For example:
var name: String = "Anna" // non-nullable String
var gender: String? = "Female" //nullable String
There are a number of ways to access a method or property of a nullable type. You can
check for null and access the method or property in the condition. For example:
if (gender != null) {
print("Length of gender is ${gender.length}")
}
The compiler keeps track of the results of the null check and therefore allows the call to
length with the body of the if condition. This is an example of a smart cast:
[ 74 ]
Type Checks and Null Safety Chapter 5
Smart cast is a smart feature in Kotlin where the compiler tracks the
results in an if statement and performs casts automatically when
needed.
In the preceding code, if the gender is not null, the value of len will be the result
of gender.length. Otherwise, the value of len will be null.
Using the safe call operator is great if you don't need to perform any actions when gender
is null. What if we want to assign len a different value when gender is null? We can
combine the safe call operator with the Elvis operator for that.
In the previous code, the value of len will be 0 if gender?.length evaluates to null.
The !! operator
What if we don't care about running into a null pointer exception? Then we can make use
of the !! operator. For example:
val len = gender!!.length
[ 75 ]
Type Checks and Null Safety Chapter 5
Only use the !! operator if you're either certain of the value of the
variable or you don't care about running into a null pointer exception.
Type checks
Just like in Java, you can confirm the type of a variable. In Kotlin, this is done using the is
operator. For example:
if (gender is String) {
println("Length of gender is ${gender.length}") // gender is
automatically cast to a String
}
Just as with the null check from earlier, the compiler keeps track of the results of the type
check and automatically casts gender to a String, therefore allowing the call to
gender.length. This is called a smart cast.
Cast operator
To cast a variable to another type, you have to use the cast operator (as):
var fullname: String = name as String
The cast operator will throw an error if the variable you're trying to cast is not of the type
you're trying to cast it to. To prevent this, you can make use of the safe cast operator (as?):
var gen: String? = gender as? String
The safe cast operator does not throw an error but returns a null if the cast is not possible.
[ 76 ]
Type Checks and Null Safety Chapter 5
Summary
In this chapter, we learned about the different ways Kotlin helps make your code null safe.
There are different operators used in Kotlin to achieve this and we covered how to use
them.
In the next chapter, we will complete work on our TicTacToe game while learning about
functions and lambdas in Kotlin.
[ 77 ]
6
Functions and Lambdas
In this chapter, we will be bringing work on the TicTacToe game to a close whilst learning
about functions in Kotlin.
Functions
In Kotlin, functions are declared in the following format:
Functions and Lambdas Chapter 6
The return type and parameters are optional. A function with no return type by default
returns a Unit. Unit is equivalent to void in Java.
A function with a single expression as its body can eliminate the curly braces as well:
fun addStudent(name: String, age:Int, classRoomNo: Int = 1, studentId: Int)
: Student = Student(name, classRoomNo, studentId, age)
The return type can also be left out if the type can be inferred by the compiler:
fun addStudent(name: String, age:Int, classRoomNo: Int = 1, studentId: Int)
= Student(name, classRoomNo, studentId, age)
Parameters
In Kotlin, function parameters are defined using the pascal notation
(parameter_name:Type). Each parameter's type has to be explicitly declared. Parameters
can be assigned a default value in the function declaration. This is done using the
format: parameter_name:Type = defaultValue. For example:
data class Student(var name: String, var classRoomNo: Int, var studentId:
Int, var age: Int)
In the example:
When calling the addStudent() function, you can leave out the
classRoomNo parameter. For example, joseph will have a default classRoomNo
value of 1.
In cases where you do not pass all the parameters to a function, the parameters
passed have to be preceded by their parameter names.
[ 79 ]
Functions and Lambdas Chapter 6
// 2
logStudent(name = "Anna", age = 20, createStudent = { name: String, age:
Int -> Student(name, 1, 3, age)})
Here, the logStudent() function takes three parameters: name, age, and createStudent.
createStudent is a function that takes a String and an Int as its parameters and returns
a Student object.
Lambda expressions
A lambda expression is an anonymous function that is not declared but passed
immediately as an expression.
Let's go ahead and make use of lambda expressions in our TicTacToe app. Open
MainActivity.kt. In the startNewGame() function, replace the following lines of code:
cell.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
cellClickListener(i, j)
}
})
[ 80 ]
Functions and Lambdas Chapter 6
In the previous lines of code, we have an anonymous object that is implementing a Java
interface that has a single abstract method (onClick()). All of this can be replaced with a
simple lambda expression.
Let's go ahead and make use of all we've learned so far to in order complete work on the
game.
[ 81 ]
Functions and Lambdas Chapter 6
Android Studio provides a default tool chain that supports most of the
JAVA 8 features, including lambda expressions. It is highly recommended
that you use the default tool chain and disable all other options such as
jackoptions, and retrolambda.
This function is used to check whether the game board is full. Here, we go through all the
cells on the board and return false if any of them are empty. If none of the cells are empty,
we return true.
[ 82 ]
Functions and Lambdas Chapter 6
gameBoard[2][0] == w)) {
return true
}
return false
}
Here, you check whether the character passed is the winner. The character is the winner if it
appears three times in either a horizontal, vertical or diagonal row.
if (state != null) {
turnTextView?.text = state
val builder = AlertDialog.Builder(this)
builder.setMessage(state)
builder.setPositiveButton(android.R.string.ok, { dialog, id ->
startNewGame(false)
})
val dialog = builder.create()
dialog.show()
}
}
The preceding function makes use of the isBoardFull() and isWinner() functions to
determine who the winner of the game is. If neither X nor O has won and the board is full,
then it's a draw. Show an alert displaying either the winner of the game or a message telling
the user that the game was a draw.
[ 83 ]
Functions and Lambdas Chapter 6
Replace it with:
fab.setOnClickListener {startNewGame(false)}
[ 84 ]
Functions and Lambdas Chapter 6
Again, build and run. Now, when you click the FloatingActionButton, the board will be
cleared for you to restart the game:
Summary
In this chapter, we learned how to work with functions and lambdas in Kotlin and
completed work on our TicTacToe game.
In the next few chapters, we will work on creating a location-based alarm whilst learning to
use Google location services and how to perform network calls on Android.
[ 85 ]
7
Developing Your Location-
Based Alarm
Getting to know the location of the user and providing customized services to them is one
of the powerful features of Android devices. Android app developers can take advantage
of this powerful feature and provide a fascinating service to the users of their app. Hence,
understanding Google location services, the Google Maps APIs, and location APIs, are very
important for the developers of Android applications.
In this chapter, we will develop our own location-based alarm (LBA) and, in the process of
developing the app, we will learn about the following:
Creating a project
We will look at the steps involved in creation of a LBA. We will use our favorite IDE,
Android Studio, for developing the LBA.
Let's get started by launching Android Studio. Once it's up and running, click on Start a
new Android Studio Project. If you have a project opened already, click on File | New
Project.
Developing Your Location-Based Alarm Chapter 7
[ 87 ]
Developing Your Location-Based Alarm Chapter 7
On the screen that follows, we will be making a decision about the Android devices we
target based on:
For our app, we will choose Phone and Tablet and the API as API 15. The text beneath the
API selection box informs us that by selecting API 15 and later, we will be choosing to have
our app run on approximately 100% of devices.
We will not be running our app on any other form factors; hence, we can skip those
selection areas and click on Next:
In the next screen, we will be provided with an option to add an activity to our app.
Android Studio makes it easier for developers to include the kind of activity they require
for their app by providing ready-made templates on the most frequently used activities.
We are developing a LBA, so we require a map that shows the location for which the alarm
is set.
[ 88 ]
Developing Your Location-Based Alarm Chapter 7
We will be configuring the activity on the next screen. Native Android apps are, in general,
a combination of the Kotlin/Java class and the user interface defined by XML. The following
inputs are provided on the screen to configure our app:
Activity Name: This is the name of the Kotlin class for our map's activity. The
name appears by default as MapsActivity when we select a maps activity; we
will use the same here.
Layout Name: The name of the XML layout we will use to design our user
interface.
Title: The title we want the app to display for this activity. We will leave this as
Map, which is shown by default.
[ 89 ]
Developing Your Location-Based Alarm Chapter 7
On clicking the button, we will see the Building 'LocationAlarm' Gradle project info
screen.
The file is named google_maps_api.xml by default. The file clearly indicates that before
we run our application, we need to get a Google Maps API key. The procedure to get the
Google Maps API key for the app is listed in detail.
[ 90 ]
Developing Your Location-Based Alarm Chapter 7
To get one, follow this link, follow the directions and press "Create" at
the end:
https://console.developers.google.com/flows/enableapi?apiid=maps_android_ba
ckend&keyType=CLIENT_SIDE_ANDROID&r=00:ED:1B:E2:03:B9:2E:F4:A9:0F:25:7A:2F:
40:2E:D2:89:96:AD:2D%3Bcom.natarajan.locationalarm
You can also add your credentials to an existing key, using these values:
Package name:
00:ED:1B:E2:03:B9:2E:F4:A9:0F:25:7A:2F:40:2E:D2:89:96:AD:2D
SHA-1 certificate fingerprint:
00:ED:1B:E2:03:B9:2E:F4:A9:0F:25:7A:2F:40:2E:D2:89:96:AD:2D
Once you have your key (it starts with "AIza"), replace the
"google_maps_key"
string in this file.
-->
<string name="google_maps_key" templateMergeStrategy="preserve"
translatable="false">YOUR_KEY_HERE</string>
</resources>
We will generate the key required for our app using the link provided in the file.
[ 91 ]
Developing Your Location-Based Alarm Chapter 7
Once the user signs in to the Console, the user will be asked to register the application for
the Google Maps Android API in the Google API Console.
Select a project
Create a project
As indicated in the text below Select a project where your application will be registered,
the user can use one project to manage the API keys for all the applications developed, or
choose to have a different project for each application.
The choice of using one project to manage all API keys required for various Android
applications developed or having one project for each application, depends on the user. At
the time of writing, by default, the user will be allowed to create 12 projects for free.
[ 92 ]
Developing Your Location-Based Alarm Chapter 7
Following this, you need to read and agree to the terms of service of the Google Play
Android Developer API and the Firebase APIs/Services Terms of Service (https://
console.developers.google.com/flows/enableapi?apiid=maps_android_backend
keyType=CLIENT_SIDE_ANDROIDr=
00:ED:1B:E2:03:B9:2E:F4:A9:0F:25:7A:2F:40:2E:D2:89:96:AD:2D;com.natarajan.
locationalarm ):
Choose Create a project and agree to the terms and conditions. Once that is done, click on
Agree and continue:
[ 93 ]
Developing Your Location-Based Alarm Chapter 7
Once the creation of the project is successful, the user will see a screen. The screen reads
The project has been created and Google Maps Android API has been enabled. Next
you'll need to create an API key in order to call the API. The user will also see a button
that reads Create API Key:
On clicking the Create API Key button, the user will be shown a console with a message
pop up that reads API key created. This is the API key that we need to use in our app:
[ 94 ]
Developing Your Location-Based Alarm Chapter 7
Copy the API key and then replace the YOUR_API_KEY text in the
google_maps_api.xml file with the API key generated, as shown:
The modified file with the generated Google Maps API key should look like the one
shown here:
[ 95 ]
Developing Your Location-Based Alarm Chapter 7
Developers can always check the API key generated by logging into the
Google API console and cross checking the usage of the correct API key
generated specifically for the project:
Now that we have got the API key generated and have modified the file to reflect this, we
are all set for analyzing the code and running the app.
To quickly recap, we created the app to include Google Maps activity and created a layout
file. Then we generated the Google Maps API key and replaced that in the file.
Android Studio will prompt us to select the deployment target, the physical device that has
developer options and USB debugging enabled OR the emulator, also known as the
virtual device the user has set up.
[ 96 ]
Developing Your Location-Based Alarm Chapter 7
Once we select one of the options and click on Okay, the app will be built and run on the
deployment target. The app will be up and running and we should see map activity being
loaded with Marker in Sydney:
[ 97 ]
Developing Your Location-Based Alarm Chapter 7
The MapActivity class extends the AppCompatActivity class and also implements the
OnMapReadCallback interface. We have a couple of variables, GoogleMap, mMap and btn
button initialized.
Overriding the onCreate method, the app is set to load the content from the XML file,
activity_maps.xml, as and when the app is launched.
The resources for the mapFragment and btn are set from the resources file:
class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
Let's now customize this code to display the marker over Chennai, Tamil Nadu, India. To
make the change, the first step is to understand what Lat and Lng stand for.
[ 98 ]
Developing Your Location-Based Alarm Chapter 7
Latitude and longitude are used together to specify the precise location of any part of the
earth. In Android, the class LatLng is used for specifying the location.
Search for the location for which you need to find the latitude and longitude. Here, we
search for Vasantham, a special school for mentally challenged children in Chennai, Tamil
Nadu, India.
Once the location we've searched for is found, we can see the latitude and longitude value
in the URL, as shown:
The latitude and longitude values we see for the place we searched for are 13.07 and 80.17.
Let us go ahead and make the following changes in the code.
[ 99 ]
Developing Your Location-Based Alarm Chapter 7
As we save the changes done and run the app once again, we will be able to see that the
map now loads with the marker on Chennai, India:
[ 100 ]
Developing Your Location-Based Alarm Chapter 7
Once we touch the marker, we should be able to see the text Marker in Chennai displayed
on top of the red marker:
[ 101 ]
Developing Your Location-Based Alarm Chapter 7
Let us quickly check out the XML layout files. We will also understand the process to add a
button that will take us to the screen through which the user will be able to enter the Lat
and Lng input for the alarm.
In the activity_maps.xml file, we have the map fragment and button element wrapped
in the LinearLayoutCompat as shown here. We have the button element linked to the
onClickSettingsButton method:
<android.support.v7.widget.LinearLayoutCompat
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="vertical"
android:layout_weight="1.0">
<fragment
android:id="@+id/map"
android:layout_weight="0.8"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.natarajan.locationalarm.MapsActivity" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0.2"
android:id="@+id/settingsbtn"
android:onClick="onClickSettingsButton"
android:text="@string/Settings"/>
</android.support.v7.widget.LinearLayoutCompat>
[ 102 ]
Developing Your Location-Based Alarm Chapter 7
We have a very simple screen for input purposes. We have a LinearLayout that has a
couple of EditText, one for the latitude and the other for longitude input. These edit texts
are followed by a button that allows for the submission of the new location coordinates
entered by the user.
We also have an onClickButton method linked to the button to be called upon as and
when the user clicks on the button:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="@+id/latText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:hint='Latitude'
android:inputType="numberDecimal" />
<EditText
android:id="@+id/langText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:hint="Longitude"
android:inputType="numberDecimal" />
<Button
android:id="@+id/alarmbtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClickButton"
android:text="Ok" />
</LinearLayout>
We have the XML layout for the user input ready; now let's create a new Kotlin activity
class that will use this setting's XML and interact with the user.
[ 103 ]
Developing Your Location-Based Alarm Chapter 7
In the onClickButton method, we have a simple Toast message that says Alarm Set. In
the following chapter, we will be saving the input entered and will have the alarm trigger
when the user enters the location of interest:
class SettingsActivity : AppCompatActivity() {
}
fun onClickButton(view: View) {
Toast.makeText(this, "Alarm Set", Toast.LENGTH_LONG).show()
}
}
[ 104 ]
Developing Your Location-Based Alarm Chapter 7
When the user clicks on the OK button after entering the Lat and Lng details, the Toast
message will be displayed, as shown:
[ 105 ]
Developing Your Location-Based Alarm Chapter 7
Our app uses the permission ACCESS_FINE_LOCATION. This is to get the details
about the user's location; we require this to enable the alarm as and when the
user reaches the location the alarm is set for.
We have the metadata for the Android geo API Key which is nothing but the API
key we generated and placed in google_maps_api.xml.
We have a Launcher MAIN activity, the one that launches the map with the
marker on the Chennai location.
We also have the default activity settings that get triggered when clicking the
Submit button:
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<!--
The API key for Google Maps-based APIs is defined as a string
resource.
(See the file "res/values/google_maps_api.xml").
Note that the API key is linked to the encryption key used to
sign the APK.
You need a different API key for each encryption key,
including the release key that is used to
[ 106 ]
Developing Your Location-Based Alarm Chapter 7
<activity
android:name=".MapsActivity"
android:label="@string/title_activity_maps">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"
/>
</intent-filter>
</activity>
<activity android:name=".SettingsActivity">
<intent-filter>
<action
android:name="android.intent.action.SETTINGACTIVITY" />
<category android:name="android.intent.category.DEFAULT"
/>
</intent-filter>
</activity>
</application>
</manifest>
Build.gradle
The build.gradle file includes the dependencies required for the Google Maps services.
We must include the Play services maps from the Google Play services. From the Google
Play services, we include the services of interest to us. Here, we would like to have a map
service available to us, and, hence we are including play-services-maps:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 26
defaultConfig {
[ 107 ]
Developing Your Location-Based Alarm Chapter 7
applicationId "com.natarajan.locationalarm"
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-
rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-
jre7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.google.android.gms:play-services-maps:11.8.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-
core:3.0.1'
}
Summary
In this chapter, we discussed and learned about creating our own LBA. We understood the
details of the Google Maps API, the generation of API keys, the creation of the user
interface for maps, adding markers to the map, customizing the marker, creating user
interface screens for input from the user, and so on.
We also discussed the important components in the manifest file, the build.gradle file,
and the XML layout files and corresponding Kotlin classes. In the next chapter, we will save
the input we received from the user using shared preferences, use the location-based
services from Google APIs, and enable and trigger the alarm as and when the user enters
the location.
[ 108 ]
8
Working with Google's Location
Services
In the previous chapter, we built our location-based alarm (LBA) app to include Google
Maps, added a marker and customization of the location, and also built the UI for receiving
user input for the alarm.
We will focus now on the integration of the Google Location API with our app and
receiving updates on the user's location. The location of interest entered by the user will be
saved and compared with the location updates received for the alarm to be triggered as the
user reaches the area of interest.
Google provides various means to access and identify the location of the user. The Google
Location APIs provides information on the user's last known location, display the location
address, receive continuous updates on the location changes, and so on. Developers can
add GeoFence—a fence around a geographical region—and any time users pass through
the GeoFence an alert can be generated.
The primary focus of this chapter is to introduce and explain the concept and usage of the
location in our app. With this objective in mind, these concepts are explained with the app
receiving the location updates when running in the foreground. The handling of required
permissions is also handled in a simpler way.
Working with Google's Location Services Chapter 8
Shared preferences are file-based storage that contain key-value pairs and provide an easier
means to read and write. The shared preferences file is managed by the Android
framework and the file can be either private or shared.
Let's first integrate shared preferences into our code and save the latitude and longitude the
user entered in the UI screen for the alarm.
Shared preferences give us an option of saving the data in key-value pairs. While we can
use the generically available shared preference file, it is better to have a specific shared
preference file for our app.
We need to define a string for our shared preference file to be used by our app. Navigate to
app | src | main | res | values | strings.xml. Let us add a new string, PREFS_NAME, and
name it as LocationAlarmFile:
<resources>
<string name="app_name">LocationAlarm</string>
<string name="title_activity_maps">Map</string>
<string name="Settings">Settings</string>
<string name="PREFS_NAME">LocationAlarmFile</string>
</resources>
We will add the following code in our SettingsActivity class to capture the user input
and save that in the shared preferences file. The shared preferences file is opened by
referring to the string PREFS_NAME in the resources file and the file is opened with
MODE_PRIVATE, which indicates the file is available only for our app.
Once the file is available, we open the editor and share the latitude and longitude input
from the user as a string using putString as key-value pairs:
val sharedPref =
this?.getSharedPreferences(getString(R.string.PREFS_NAME),Context.MODE_PRIV
ATE) ?: return
with(sharedPref.edit()){
putString("userLat", Lat?.text.toString())
putString("userLang",Lang?.text.toString())
commit()
[ 110 ]
Working with Google's Location Services Chapter 8
[ 111 ]
Working with Google's Location Services Chapter 8
The latitude that is entered by the user is stored and read from shared preference and
displayed:
[ 112 ]
Working with Google's Location Services Chapter 8
The longitude entered by the user is also read from shared preferences and displayed:
[ 113 ]
Working with Google's Location Services Chapter 8
Adding permissions
Google Play Services provide location-based services that can be integrated and used by
our app. Adding the location services and using them requires permissions to identify and
get the location updates from the user.
To use the Google Location services from Play Services, we need to include the play-
services-location in the build.gradle file:
dependencies {
compile 'com.google.android.gms:play-services-location:11.8.0'
}
It is important to include only the specific feature required for the app
from Google Play Services. For example, here we require location services,
so we need to specify the services for the location. Including all Google
Play Services will make the app size huge; ask for permissions that are not
really required for the app.
We also need to add the permission for accessing the fine location in our
AndroidManifest.xml file. This gives us access to obtain the location details from the
network provider and also the GPS provider:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
During runtime, we need to check whether the device has the location enabled; if not, we
will display a message requesting the user to enable the location and grant permission for
the app.
The checkLocation Boolean function finds out if the device has the location enabled or
not for us:
private fun checkLocation(): Boolean {
if(!isLocationEnabled())
Toast.makeText(this,"Please enable Location and grant
permission for this app for Location",Toast.LENGTH_LONG).show()
return isLocationEnabled();
}
[ 114 ]
Working with Google's Location Services Chapter 8
[ 115 ]
Working with Google's Location Services Chapter 8
We declare the variables required for GoogleMap and other variables to store the latitude
and longitude received from the user and also from the location API:
private lateinit var mMap: GoogleMap
private var newLat: Double? = null
private var newLang: Double? = null
private var chennai: LatLng? = null
//location variables
[ 116 ]
Working with Google's Location Services Chapter 8
We declare the UPDATE_INTERVAL, the interval in which we would like to receive the
updates from the location API, and FASTEST_INTERVAL, the rate at which our app can
handle the update. We also declare the LocationManager variable:
private val UPDATE_INTERVAL = 10000.toLong() // 10 seconds rate at
// which we would like to receive the updates
private val FASTEST_INTERVAL: Long = 5000 // 5 seconds - rate at
// which app can handle the update
lateinit var locationManager: LocationManager
In the onCreate function, we set the content view for the UI and also ensure the
GoogleApiClient is instantiated. We also request that the user enables the location is as
follows:
onCreate():
mGoogleApiClient = GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build()
mLocationManager =
this.getSystemService(Context.LOCATION_SERVICE) as
LocationManager
checkLocation()
}
[ 117 ]
Working with Google's Location Services Chapter 8
In the onStart method, we check if the mGoogleAPIClient instance is not null and
request the connection be initiated:
override fun onStart() {
super.onStart();
if (mGoogleApiClient != null) {
mGoogleApiClient.connect();
}
}
In case something goes wrong and the connection gets suspended, we request a reconnect
in the onConnectionSuspended method:
override fun onConnectionSuspended(p0: Int) {
In case the Google Location API cannot establish the connection, we log the reason for this
by getting the error code:
override fun onConnectionFailed(connectionResult:
ConnectionResult) {
Log.i(TAG, "Connection failed. Error: " +
connectionResult.getErrorCode());
}
[ 118 ]
Working with Google's Location Services Chapter 8
if (ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION) !=
PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_COARSE_LOCATION) !=
PackageManager.PERMISSION_GRANTED) {
return;
}
startLocationUpdates();
[ 119 ]
Working with Google's Location Services Chapter 8
}
LocationServices.FusedLocationApi.requestLocationUpdates(
mGoogleApiClient, mLocationRequest, this);
}
The onLocationChanged method is the important method in which we get the details of
the current location of the user. We also read the latitude and longitude the user input for
the alarm from the shared preferences. Once we get both sets of details, we call
the CheckAlarmLocation method which matches the latitude/longitude and alert the user
as and when they reach the area of interest:
override fun onLocationChanged(location: Location) {
val sharedPref =
this?.getSharedPreferences(getString(R.string.PREFS_NAME),
Context.MODE_PRIVATE)
?: return
AlarmLat =
java.lang.Double.parseDouble(sharedPref.getString("userLat",
"13.07975"))
AlarmLong =
java.lang.Double.parseDouble(sharedPref.getString("userLang",
"80.1798347"))
UserLat = location.latitude
UserLong = location.longitude
checkAlarmLocation(AlarmLat1,AlarmLong1,UserLat1,UserLong1)
}
}
[ 120 ]
Working with Google's Location Services Chapter 8
[ 121 ]
Working with Google's Location Services Chapter 8
As and when the user enters the location of interest, we display an alert message that the
User has reached the area for which the alarm has been set:
fun checkAlarmLocation(AlarmLat : Double, AlarmLong : Double, UserLat :
Double,UserLong : Double) {
[ 122 ]
Working with Google's Location Services Chapter 8
Summary
In this chapter, we continued developing our location-based alarm app to make use of the
Google Location APIs from Google Play Services and utilized the functionality of providing
an alert as when the user entered the area of interest.
We learned how to use shared preferences for the persistence of the data entered by the
user, retrieving the same and using the Location APIs to match the current location of the
user with the area of interest.
[ 123 ]
9
Connecting the Outside World –
Networking
We live in the era of digital communication. Handheld devices play a huge role in
communication and impact the way people interact. In the previous chapter, we discussed
one of the powerful functions of Android—identifying the location of the user and
customizing services based on location. In this chapter, we will focus on one of the most
useful and powerful features of the Android devices—networking and connecting to the
outside world.
While we will provide brief coverage on the important concepts of network connectivity
and the Android framework support for networking, we will specifically focus on
configuration and the usage of various built-in, third-party libraries. We will also learn how
to load an image from a URL and display it in the sample app we create.
Network connectivity
Android framework support for networking
Usage of built-in libraries
Usage of third-party libraries
Network connectivity
Understanding and identifying the status and type of network the user is connected to is
very vital for delivering an enriching experience to our users. The Android framework
provides us with a couple of classes that we can use to find the details of the network:
ConnectivityManager
NetworkInfo
Connecting the Outside World – Networking Chapter 9
While the ConnectivityManager provides information about the state of the network's
connectivity and the changes it undergoes, NetworkInfo provides information on the type
of network—mobile or Wi-Fi.
The following code snippet helps to establish whether a network is indeed available and
whether the device is connected to the network:
fun isOnline(): Boolean {
val connMgr = getSystemService(Context.CONNECTIVITY_SERVICE) as
ConnectivityManager
val networkInfo = connMgr.activeNetworkInfo
return networkInfo != null && networkInfo.isConnected
}
The isOnline() method returns a Boolean—true or false based on the result returned by
the ConnectivityManager. The connMgr instance is used along with NetworkInfo to
find the information about the network.
Manifest permissions
Accessing the network and sending/receiving the data would require permission to access
the internet and the network state. The following permissions must be defined in the
manifest file for the app to take advantage of the network of the device:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
The internet permission allows the app to communicate by enabling network sockets while
the access network state permission enables it to find information about available networks.
The Android framework provides a default intent, MANAGE_NETWORK_USAGE, for the app to
manage the network data. The activity for handling the intent can be implemented specific
to the app:
<intent-filter>
<action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
[ 125 ]
Connecting the Outside World – Networking Chapter 9
Volley library
The ability to communicate with web servers via an HTTP protocol and exchange
information in the form of strings, JSON, and images makes an app much more interactive
and provides users with a rich experience. Android has a built-in HTTP library called
Volley that does this information exchange out of the box.
In addition to making the information exchange easier, Volley also provides easier means
for one to handle the entire lifecycle of a request viz schedule, cancel, prioritize, and so on.
Sync adapter
Keeping the data in the app in sync with the web server enables developers to provide a
rich experience for users. The Android framework provides sync adapters that enable data
sync to happen at a defined periodic interval.
Similar to Volley, sync adapters have all the facilities to handle the life cycle of data
transfer and provide a seamless means of data exchange.
Third-party libraries
In addition to the built-in support of the Android framework, we do have quite a few third-
party libraries available to handle network operations. Out of those, Picasso from Square
and Glide from bumptech are widely used image downloading and caching libraries.
In this section, we will focus on the implementation of these two libraries—Picasso and
Glide—to load an image from a specific URL and display it in our sample application.
[ 126 ]
Connecting the Outside World – Networking Chapter 9
Networking calls should never be done on the main thread. Doing so will
result in the app becoming less responsive and create Application Not
Responding conditions. Instead, we should create separate worker threads
which handle such network calls and provide information as and when
the request is attended to.
Picasso
In this sample project, let's understand how to use the Picasso library from Square and
load an image from a specified URL.
Let's create a new Android project and call it ImageLoader. We need to ensure Kotlin
support is checked.
For the Image Loader sample, we can proceed by selecting Empty Activity:
Let us name the activity MainActivity, the one appears by default and have the XML
named as activity_main:
[ 127 ]
Connecting the Outside World – Networking Chapter 9
The XML code that follows shows that the default XML contains TextView; we shall be
replacing the TextView with ImageView:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.natarajan.imageloader.MainActivity">
[ 128 ]
Connecting the Outside World – Networking Chapter 9
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
The modified XML that has an ImageView looks like the one shown in the code block that
follows. We can easily add this by dragging the ImageView from the widgets or by typing
in the code in XML layout. In the ImageView, we have marked it to display the launcher
icon as the placeholder:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.natarajan.imageloader.MainActivity">
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@mipmap/ic_launcher"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:layout_editor_absoluteX="139dp"
tools:layout_editor_absoluteY="219dp" />
</android.support.constraint.ConstraintLayout>
ImageViewer displays the launcher icon in the placeholder marked for display of image on
loading from the URL. The launcher icon is displayed as soon as we make the change in the
XML:
[ 129 ]
Connecting the Outside World – Networking Chapter 9
build.gradle
We need to add the implementation com.square.picasso.picasso:2.71828 in the
build.gradle dependencies. Version 2.71828 is the latest at the time of writing. To make
sure we use the latest version, it is prudent to check http://square.github.io/picasso/
and use the latest version in the Gradle dependencies.
We need to add the following line in the dependencies section of the build.gradle file to
have Picasso available for our app:
implementation com.squareup.picasso:picasso:2.71828
The modified build.gradle file should look like the one shown here:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.0'
[ 130 ]
Connecting the Outside World – Networking Chapter 9
implementation 'com.squareup.picasso:picasso:2.71828'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-
core:3.0.1'
}
Kotlin code
The default Kotlin code generated will have the class file named MainActivity. This class
file extends AppCompatActivity, which provides the support library action bar features.
The code loads the XML defined in the activity_main during the onCreate method and
displays it on load. The setContentView reads the XML content defined in
activity_main and displays the ImageView on load:
package com.natarajan.imageloader
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
We have made changes to the XML already by replacing the default TextView with
ImageView. We need to reflect those changes in our Kotlin code and also implement the
loading of the image by using Picasso.
We need to add the ImageView and Picasso imports for our program to use these
components:
import android.widget.ImageView
import com.squareup.picasso.Picasso
As we have already imported Picasso and ensured the dependencies were added, we
should be able to load the date by a single line of
code, Picasso.get().load("URL").into(ImageView):
Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(imageView);
[ 131 ]
Connecting the Outside World – Networking Chapter 9
The final modified Kotlin class for Picasso image load should be as shown:
package com.natarajan.imageloader
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.widget.ImageView
import com.squareup.picasso.Picasso
import kotlinx.android.synthetic.main.activity_main.*
Manifest permissions
We need to ensure our app has added the permission to access the internet. This is required
as we are about to download the image from a specified URL and will have it displayed in
our ImageViewer.
We have already covered the manifest permissions required in detail. Let us go ahead and
add this permission:
<uses-permission android:name="android.permission.INTERNET"></uses-
permission>
The modified XML should look like the one shown here:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.natarajan.imageloader">
<uses-permission android:name="android.permission.INTERNET">
</uses-permission>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
[ 132 ]
Connecting the Outside World – Networking Chapter 9
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category
android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Now that we are all set with the changes made to XML, the Kotlin Code, build.gradle,
and the AndroidManifest file, it is time to launch our app and understand the seamless
loading process of the image via Picasso.
Once we run the app, we should be able to see our device load the page, display the app
name ImageLoader and display the image shown here from the URL:
[ 133 ]
Connecting the Outside World – Networking Chapter 9
Glide
Glide from bumptech is another image loading library that is very popular. We shall look
at using Glide and loading an image from a specific URL.
Let us go ahead and make the changes required for Glide in our build.gradle and other
related files.
build.gradle
We need to add the plugin kotlin-kapt and add dependencies in our app's
build.gradle file. Once we sync the changes made, we should be able to use Glide in
our code and load the image.
The Glide library uses annotation processing. Annotation processing helps with the
generation of boilerplate code and makes the code easier to understand. To observe the
actual code that works at runtime, developers can check the generated code and
understand the boilerplate code generated by the library:
apply plugin: 'kotlin-kapt'
implementation 'com.github.bumptech.glide:glide:4.7.1'
kapt "com.github.bumptech.glide:compiler:4.7.1"
The Glide library talks about adding the annotation processor along with
Glide in the dependencies. This is applicable for Java. For Kotlin, we
need to add the kapt Glide compiler as shown in the code block.
[ 134 ]
Connecting the Outside World – Networking Chapter 9
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation
'com.android.support.test.espresso:espresso-core:3.0.1'
}
allprojects {
repositories {
google()
mavenCentral()
jcenter()
}
We are done with changes to the build.gradle files; we should make the following
additions to proguard-rules.pro file. The proguard-rules.pro file enables developers
to shrink the APK size by removing references to unused and unwanted code in the
application.
In order to ensure the Glide module is not affected by proguard shrinking, we need to
explicitly mention that app requires we keep references to Glide. The -keep command
ensures that the reference to Glide and respective modules are retained in the build:
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
# for DexGuard only
-keepresourcexmlelements manifest/application/meta-data@value=GlideModule
Kotlin code
We define a separate class called ImageLoaderGlideModule which extends
AppGlideModule(). The annotation @GlideModule on the class enables the app to have
access to the GlideApp instance. The GlideApp instance can be used across various
activities in our application:
package com.natarajan.imageloader
/**
* Created by admin on 4/14/2018.
*/
[ 135 ]
Connecting the Outside World – Networking Chapter 9
import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.module.AppGlideModule
@GlideModule
class ImageLoaderGlideModule : AppGlideModule()
We need to make the following changes in the MainActivity Kotlin class to have the
image loaded by Glide and display it when the app is launched.
Similar to Picasso, Glide also has a simple syntax for loading an image from a specified
URL:
GlideApp.with(this).load("URL").into(imageView);
The modified MainActivity Kotlin class should look like the one shown here:
package com.natarajan.imageloader
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
if(imageView != null){
GlideApp.with(this).load("http://goo.gl/gEgYUd").into(imageView);
}
}
}
[ 136 ]
Connecting the Outside World – Networking Chapter 9
We have done all the changes required for Glide—build.gradle, Proguard.rules, and
the Kotlin class files. We should see the app load the image from the specified URL and
display it in the ImageView as shown:
Summary
Networking and connecting to the outside world is a very powerful feature of Android
devices. We covered the basics of networking, checking for the status of the network, the
type of network available, and the built-in capabilities provided by the Android framework
to perform network operations.
We also discussed the third-party libraries Picasso and Glide, the image loading libraries
in detail, and covered the implementation of those libraries in our app.
In the next chapter, we will work on developing a simple to-do list app and discuss various
concepts such as listview, dialog, and so on, and learn how to use them in the app.
[ 137 ]
10
Developing a Simple To-Do List
App
In this chapter, we will build a simple to-do list app that allows a user to add, update, and
delete tasks.
[ 139 ]
Developing a Simple To-Do List App Chapter 10
When the project creation is complete, create a Kotlin Activity by selecting File | New |
Kotlin Activity, as shown in the following screenshot:
[ 140 ]
Developing a Simple To-Do List App Chapter 10
This will start a New Android Activity wizard. On the Add an Activity to Mobile screen,
select Basic Activity, as shown in the following screenshot:
Now, check Launcher Activity on the Customize the Activity screen, and click the Finish
button:
[ 141 ]
Developing a Simple To-Do List App Chapter 10
Building your UI
In Android, the code for your user interface is written in XML. You can build your UI by
doing either of the following:
[ 142 ]
Developing a Simple To-Do List App Chapter 10
Make sure the Design tab at the bottom of the screen is selected, as shown in the following
screenshot:
To add a component to your layout, you just drag the item from the Palette on the left side
of the screen. To find a component, either scroll through the items on the Palette, or click on
the Palette search icon and search for the item you need.
[ 143 ]
Developing a Simple To-Do List App Chapter 10
If the Palette is not showing on your screen, select View | Tool Windows |
Palette to display it.
Go ahead and add a ListView to your view. When a view is selected, its attributes are
displayed in the XML Attributes editor on the right side of the screen. The Attributes editor
allows you to view and edit the attributes of the selected component. Go ahead and make
the following changes:
[ 144 ]
Developing a Simple To-Do List App Chapter 10
Now, select Text at the bottom of the editor window to view the generated XML code.
You'll notice that the XML code now has a ListView placed within the
ConstraintLayout:
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout_editor_absoluteX="4dp"
tools:layout_editor_absoluteY="8dp" />
</android.support.constraint.ConstraintLayout>
Instead of using the layout editor, you could have written the previous code yourself. The
choice between using the layout editor or writing the XML code is up to you. You can use
the option that you're most comfortable with. We'll continue to make additions to the UI as
we go along.
[ 145 ]
Developing a Simple To-Do List App Chapter 10
Now, build and run your code. as shown in the following screenshot:
As you can see, the app currently doesn't have much to it. Let's go ahead and add a little
more flesh to it.
Since we'll use the FloatingActionButton as the button the user uses to add a new item
to their to-do list, we need to change its icon to one that makes its purpose quite clear.
[ 146 ]
Developing a Simple To-Do List App Chapter 10
Build and run again. The FloatingActionButton at the bottom now looks like an Add icon,
as shown in the following screenshot:
[ 147 ]
Developing a Simple To-Do List App Chapter 10
This is not ideal for our to-do list app. Let's go ahead and create a new method in
the MainActivity class that will handle the click event:
fun showNewTaskUI() {
}
The method currently does nothing. We'll add code to show the appropriate UI soon. Now,
replace the code within the setOnClickListener() call with a call to the new method:
fab.setOnClickListener { showNewTaskUI() }
Let's start by building the UI for the dialog. Right-click the res/layout directory and
select New | Layout resource file, as shown in the following screenshot:
[ 148 ]
Developing a Simple To-Do List App Chapter 10
On the New Resource File window, change the Root element to LinearLayout and set
the File name as dialog_new_task. Click OK to create the layout, as shown in the
following screenshot:
Open the dialog_new_task layout and add an EditText view to the LinearLayout. The
XML code in the layout should now look like this:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/task"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"/>
</LinearLayout>
[ 149 ]
Developing a Simple To-Do List App Chapter 10
The inputType attribute is used to specify what kind of data the field can
take. By specifying this attribute, the user is shown an appropriate keyboard.
For example, if the inputType is set to number, the numbers keyboard is
displayed:
Now, let's go ahead and add a few string resources we'll need for the next section. Open the
res/values/strings.xml file and add the following lines of code to the resources tag:
Create a new Kotlin class with the name NewTaskDialogFragment, and replace the class
definition with the following lines of code:
class NewTaskDialogFragment: DialogFragment() { // 1
// 2
interface NewTaskDialogListener {
fun onDialogPositiveClick(dialog: DialogFragment, task: String)
fun onDialogNegativeClick(dialog: DialogFragment)
}
var newTaskDialogListener: NewTaskDialogListener? = null // 3
// 4
companion object {
fun newInstance(title: Int): NewTaskDialogFragment {
[ 150 ]
Developing a Simple To-Do List App Chapter 10
newTaskDialogFragment.arguments = args
return newTaskDialogFragment
}
}
val dialogView =
activity.layoutInflater.inflate(R.layout.dialog_new_task, null)
val task = dialogView.findViewById<EditText>(R.id.task)
builder.setView(dialogView)
.setPositiveButton(R.string.save, { dialog, id ->
newTaskDialogListener?.onDialogPositiveClick(this,
task.text.toString);
})
.setNegativeButton(android.R.string.cancel, { dialog,
id ->
newTaskDialogListener?.onDialogNegativeClick(this)
})
return builder.create()
}
}
}
[ 151 ]
Developing a Simple To-Do List App Chapter 10
[ 152 ]
Developing a Simple To-Do List App Chapter 10
Now, open the MainActivity class. Change the class declaration to include
implementation of the NewTaskDialogListener. Your class declaration should now look
like this:
class MainActivity : AppCompatActivity(),
NewTaskDialogFragment.NewTaskDialogListener {
Build and run. Now, when you click the Add button, you should see a dialog on your
screen, as shown in the following screenshot:
[ 153 ]
Developing a Simple To-Do List App Chapter 10
As you may have noticed, nothing happens when you click the SAVE button. In
the onDialogPositiveClick() method, add the line of code shown here:
Snackbar.make(fab, "Task Added Successfully",
Snackbar.LENGTH_LONG).setAction("Action", null).show()
As we may remember, this line of code displays a ticker at the bottom of the screen.
[ 154 ]
Developing a Simple To-Do List App Chapter 10
Build and run. Now, when you click the SAVE button on the New Task dialog, a ticker
shows at the bottom of the screen.
We're currently not storing the task the user enters. Let's create a collection variable to store
any task the user adds. In the MainActivity class, add a new variable of type
ArrayList<String>, and instantiate it with an empty ArrayList:
This will add the task variable passed to the todoListItems data, and call
notifyDataSetChanged() on the listAdapter to update the ListView.
Saving the data is great, but our ListView is still empty. Let's go ahead and rectify that.
[ 155 ]
Developing a Simple To-Do List App Chapter 10
Open MainActivity.kt, and declare a new ListView instance variable at the top of the
class:
private var listView: ListView? = null
Next, instantiate the ListView variable with its corresponding element in the layout. Do
this by adding the following line of code at the end of the onCreate() method:
listView = findViewById(R.id.list_view)
To display data in a ListView, you need to create an Adapter, and give it the data to
display and information on how to display that data. Depending on how you want the data
displayed in your ListView, you can either use one of the existing Android Adapters, or
create your own. For now, we'll use one of the simplest Android Adapters, ArrayAdapter.
The ArrayAdapter takes an array or list of items, a layout ID, and displays your data
based on the layout passed to it.
[ 156 ]
Developing a Simple To-Do List App Chapter 10
Build and run. Now, when you click the Add button, you'll see your entry show up on the
ListView, as shown in the following screenshot:
If the user chooses the edit option, we will display our task dialog with the task field
prefilled for the user to make the required changes.
[ 157 ]
Developing a Simple To-Do List App Chapter 10
Let's start by adding the following set of strings to the strings.xml resource file:
<string name="update_task_dialog_title">Edit Task</string>
<string name="edit">Edit</string>
<string name="delete">Delete</string>
Adding a menu
Let's start by creating the menu resource file. Right-click the res directory, and select New
| Android resource file. Enter to_do_list_menu as the File name. Change the Resource
type to Menu, and click OK, as shown in the following screenshot:
Replace the code in the to_do_list_menu,xml file with the lines of code shown as
following:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
[ 158 ]
Developing a Simple To-Do List App Chapter 10
<item
android:id="@+id/edit_item"
android:title="@string/edit"
android:icon="@android:drawable/ic_menu_edit"
android:visible="false"
app:showAsAction="always"/>
<item
android:id="@+id/delete_item"
android:title="@string/delete"
android:icon="@android:drawable/ic_menu_delete"
android:visible="false"
app:showAsAction="always"/>
</menu>
In the preceding lines of code, we create two menu items, the edit and delete items. We
also set the visibility of each of the menu items to false.
Next, open the MainActivity class and add the following two new variables at the top of
the class:
private var showMenuItems = false
private var selectedItem = -1
The showMenuItems variable will be used to track the visibility state of the menu items,
while the selectedItem variable stores the position of the currently selected list item.
Then, override the onCreateOptionsMenu() method to enable the menu items if the
showMenuItems variable is set to true:
if (showMenuItems) {
editItem.isVisible = true
deleteItem.isVisible = true
}
return true
}
Next, open the MainActivity class and add the method shown here:
private fun showUpdateTaskUI(selected: Int) {
selectedItem = selected
[ 159 ]
Developing a Simple To-Do List App Chapter 10
showMenuItems = true
invalidateOptionsMenu()
}
When this method is called, it assigns the parameter passed to it to the selectedItem
variable and changes the value of showMenuItems to true. It then calls the
invalidateOptionsMenu() method. The invalidateOptionsMenu() method informs
the OS that changes have been made to the menu associated with the Activity. This
causes the menu to be recreated.
In these lines of code, the showUpdateTaskUI() method is called when an item is clicked.
Build and run again. This time, when you click a list item, the menu items will appear, as
shown in the following screenshot:
[ 160 ]
Developing a Simple To-Do List App Chapter 10
Next, we need to update the NewTaskDialogFragment class to accept and process the
selected task. Open the NewTaskDialogFragment class.
Update the newInstance() method to take an extra parameter of type String, and pass
that parameter as part of the DialogFragment arguments by using following code:
fun newInstance(title: Int, selected: String?): NewTaskDialogFragment { //
1
val newTaskDialogFragment = NewTaskDialogFragment()
val args = Bundle()
args.putInt("dialog_title", title)
args.putString("selected_item", selected) // 2
newTaskDialogFragment.arguments = args
return newTaskDialogFragment
}
Note: The points of change are the ones labeled with the numbers.
Next, update the onCreateDialog() method to retrieve and display the text for the
selected task, as shown in the following code:
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val title = arguments.getInt("dialog_title")
val selectedText = arguments.getString("selected_item") // 1
val builder = AlertDialog.Builder(activity)
builder.setTitle(title)
val dialogView =
activity.layoutInflater.inflate(R.layout.dialog_new_task, null)
task.setText(selectedText) // 2
builder.setView(dialogView)
.setPositiveButton(R.string.save, { dialog, id ->
newTaskDialogListener?.onDialogPositiveClick(this,
task.text.toString());
})
.setNegativeButton(android.R.string.cancel, { dialog, id ->
newTaskDialogListener?.onDialogNegativeClick(this)
})
[ 161 ]
Developing a Simple To-Do List App Chapter 10
return builder.create()
}
Next, we need to implement functionality for when the user selects a menu item. This is
done by overriding the onOptionsItemSelected() method:
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
if (-1 != selectedItem) {
if (R.id.edit_item == item?.itemId) { // 1
val updateFragment =
NewTaskDialogFragment.newInstance(R.string.update_task_dialog_title,
todoListItems[selectedItem])
updateFragment.show(fragmentManager, "updatetask")
todoListItems.removeAt(selectedItem)
listAdapter?.notifyDataSetChanged()
selectedItem = -1
Snackbar.make(fab, "Task deleted successfully",
Snackbar.LENGTH_LONG).setAction("Action", null).show()
}
}
return super.onOptionsItemSelected(item)
}
In the preceding method, the ID of the selected menu item is checked against the IDs of the
two menu items.
[ 162 ]
Developing a Simple To-Do List App Chapter 10
As you may have noticed, in the call to the show() method takes a String as a second
parameter. This parameter is the tag. The tag acts as a sort of ID used to differentiate
between the different fragments managed by the Activity. We'll use the tag to decide
which action to perform when the onDialogPositiveClick() method is called.
if("newtask" == dialog.tag) {
todoListItems.add(task)
listAdapter?.notifyDataSetChanged()
listAdapter?.notifyDataSetChanged()
selectedItem = -1
[ 163 ]
Developing a Simple To-Do List App Chapter 10
Build and run. Select a task and click the Edit menu item. This will pop up your Edit Task
dialog with the selected task's details already populated, as shown in the following
screenshot:
[ 164 ]
Developing a Simple To-Do List App Chapter 10
Make changes to the task details and click on the SAVE button. This takes away the
dialog, updates your ListView with the updated task, and displays a ticker with the
message, Task updated successfully at the bottom of the screen, as shown in the following
screenshot:
Next, select a task and click the Delete menu item. This removes the selected task and
displays a ticker with the message Task deleted successfully at the bottom of the screen, as
shown in the following screenshot:
[ 165 ]
Developing a Simple To-Do List App Chapter 10
Summary
In this chapter, we built a simple TodoList app that allows a user to add new tasks, and edit
or delete an already added task. In the process, we learned to use ListViews and Dialogs.
With the current state of the TodoList app, the data resets itself whenever the app restarts.
This is not ideal, since a user will most likely want to view their old tasks even after
restarting the app.
In the next chapter, we will learn about the different datastore options available and how to
use them to make our app more usable. We'll extend the TodoList app to persist the user's
task into a database.
[ 166 ]
11
Persisting with Databases
In this chapter, we will work on improving the to-do list app from the previous chapter by
properly persisting the tasks the user enters into a database.
Introduction to databases
A database is simply a collection of data organized in a way that makes accessing and/or
updating it easy. Organizing data can be done in a lot of ways, but they can be grouped
into two main types:
Relational databases
Non-relational databases
Persisting with Databases Chapter 11
Relational databases
A relational database is a database that organizes its data based on the relations that exist
between the data. In a relational database, data is presented in the form of tables with rows
and columns. A table stores a collection of data of the same type. Each column in a table
represents an attribute of an object stored in the table. Each row in a table represents an
object stored. A table has a heading that specifies the names and types of the different
attributes of the objects to be stored in the database. In relational databases, the data types
of each attribute are specified at the point of creation of the table.
Let's take a look at an example. The table here represents a collection of students:
Each row of the table represents a single student. The columns represent different attributes
of each of the students.
[ 168 ]
Persisting with Databases Chapter 11
The RDBMS of choice for Android development is SQLite. This is because the Android OS
comes bundled with SQLite.
In the previous chapter, we built a to-do list app that lets the user add, update, and delete
tasks. We used an ArrayList as our data store. Let's go ahead and extend the app to use a
relational database instead.
Using SQLite
The first thing to do is to define a schema of your database. The schema of a database is
what defines how the data in the database is organized. It defines the tables into which the
data is organized, and restrictions on those tables (such as allowed data types for the
columns). It is advisable to create a contract class that specifies the details of your database.
Create a new Kotlin object, with the name TodoListDBContract, and replace its contents
with the following lines of code:
object TodoListDBContract {
In the preceding code, the TodoListItem class represents a table in our database, and is
used to declare the name of the table and names of its columns.
[ 169 ]
Persisting with Databases Chapter 11
To create a new Kotlin object, first, right-click the package and select New
| Kotlin File/Class. Then in the New Kotlin File/Class dialog,
select Object in the Kind field:
[ 170 ]
Persisting with Databases Chapter 11
The next thing to do is to create a database helper class. This will help us abstract the
connection to the database and not keep the database connection logic in our Activity. Go
ahead and create a new Kotlin class with the name TodoListDBHelper. The class should
take a Context parameter in its default constructor and extend
the android.database.sqlite.SQLiteOpenHelper class as shown in the following
code:
class TodoListDBHelper(context: Context): SQLiteOpenHelper(context,
DATABASE_NAME, null, DATABASE_VERSION) {
Now, add the following lines of code to the TodoListDBHelper class as shown in the
following code:
private val SQL_CREATE_ENTRIES = "CREATE TABLE " +
TodoListDBContract.TodoListItem.TABLE_NAME + " (" +
BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
TodoListDBContract.TodoListItem.COLUMN_NAME_TASK + " TEXT, " +
TodoListDBContract.TodoListItem.COLUMN_NAME_DEADLINE + " TEXT, " +
TodoListDBContract.TodoListItem.COLUMN_NAME_COMPLETED + " INTEGER)"
// 1
[ 171 ]
Persisting with Databases Chapter 11
Since the table will have an ID field in the database, we have to add an extra field in the
Task class to track it. Open Task.kt, add a new field of Long type, named taskId.
// 2
val values = ContentValues()
values.put(TodoListDBContract.TodoListItem.COLUMN_NAME_TASK,
task.taskDetails)
values.put(TodoListDBContract.TodoListItem.COLUMN_NAME_DEADLINE,
task.taskDeadline)
values.put(TodoListDBContract.TodoListItem.COLUMN_NAME_COMPLETED,
task.completed)
[ 172 ]
Persisting with Databases Chapter 11
null, values); // 3
task.taskId = taskId
return task
}
First, add an instance of the TodoListDBHelper class as a new field at the top of the class:
private var dbHelper: TodoListDBHelper = TodoListDBHelper(this)
This closes the database connection when the Activity's onDestroy() method is called.
Calling dbHelper.addNewTask() will save the new task to the database instead of just
adding it to the todoListItems field.
[ 173 ]
Persisting with Databases Chapter 11
Now that we've been able to save to the database, we need to be able to view the data when
the app starts up.
[ 174 ]
Persisting with Databases Chapter 11
return taskList
}
Now, open MainActivity, and add the following line of code at the beginning of
the populateListView() method:
todoListItems = dbHelper.retrieveTaskList();
[ 175 ]
Persisting with Databases Chapter 11
Now, build and run again. You'll notice that, unlike in the previous chapter, when you
restarted the application, the tasks you saved earlier are preserved:
Updating a task
In this section, we will learn to update the details of an already saved task in the database.
Open TodoListDBHelper, and add the method shown as follows:
fun updateTask(task: Task) {
val db = this.writableDatabase // 1
// 2
val values = ContentValues()
values.put(TodoListDBContract.TodoListItem.COLUMN_NAME_TASK,
task.taskDetails)
values.put(TodoListDBContract.TodoListItem.COLUMN_NAME_DEADLINE,
task.taskDeadline)
[ 176 ]
Persisting with Databases Chapter 11
values.put(TodoListDBContract.TodoListItem.COLUMN_NAME_COMPLETED,
task.completed)
db.update(TodoListDBContract.TodoListItem.TABLE_NAME, values,
selection, selectionArgs) // 5
[ 177 ]
Persisting with Databases Chapter 11
listAdapter?.notifyDataSetChanged()
selectedItem = -1
Build and run. When you click on the Mark as Complete menu item, the selected task will
be updated as completed, and the listView updated accordingly:
[ 178 ]
Persisting with Databases Chapter 11
Deleting a task
In this section, we will learn to delete a saved task in the database.
Open TodoListDBHelper, and add the method shown here:
fun deleteTask(task:Task) {
val db = this.writableDatabase // 1
val selection = BaseColumns._ID + " = ?" // 2
val selectionArgs = arrayOf(task.taskId.toString()) // 3
db.delete(TodoListDBContract.TodoListItem.TABLE_NAME, selection,
selectionArgs) // 4
}
[ 179 ]
Persisting with Databases Chapter 11
3. Then, specify the argument for the selection query which, in our case, is
the taskId of the selected Task
4. Then, we invoke the delete() method on the database object, passing the table
name, the select query, and the selection value to it
In the method in the MainActivity class, locate the following line of code:
todoListItems.removeAt(selectedItem)
Build and run. When you add a new item, the entry isn't just added to the ListView, it is
also saved in the database:
[ 180 ]
Persisting with Databases Chapter 11
Writing your own SQL queries can be error-prone, especially if you're building an app that
depends heavily on the database or requires very complex queries. It also requires a lot of
effort and SQL query knowledge. To help with this, you can use an ORM library.
ORM libraries
An ORM (Object Relational Mapping) Library provides a better way for you to persist
objects to a database instead, without worrying much about the SQL queries, and opening
and closing database connections.
ORMLite
GreenDAO
DbFlow
Room
But, in this book, we will focus on Room, which is an ORM introduced by Google.
Open build.gradle, and add the following lines of code to the dependencies section:
implementation 'android.arch.persistence.room:runtime:1.0.0'
annotationProcessor 'android.arch.persistence.room:compiler:1.0.0'
kapt "android.arch.persistence.room:compiler:1.0.0"
Click on Sync Now. For Room to be able to save tasks to the database, we need to specify
which class represents a table. This is done by annotating the class as an Entity. Open
the Task class and replace its contents with the following lines of code:
@Entity(tableName = TodoListDBContract.TodoListItem.TABLE_NAME)
class Task() {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = BaseColumns._ID)
var taskId: Long? = null
@ColumnInfo(name = TodoListDBContract.TodoListItem.COLUMN_NAME_TASK)
var taskDetails: String? = null
@ColumnInfo(name =
[ 181 ]
Persisting with Databases Chapter 11
TodoListDBContract.TodoListItem.COLUMN_NAME_DEADLINE)
var taskDeadline: String? = null
@ColumnInfo(name =
TodoListDBContract.TodoListItem.COLUMN_NAME_COMPLETED)
var completed: Boolean? = false
@Ignore
constructor(taskDetails: String?, taskDeadline: String?): this() {
this.taskDetails = taskDetails
this.taskDeadline = taskDeadline
}
The next thing is to create a DAO (Data Access Object). Create a new Kotlin interface with
the name TaskDAO, and replace its contents with the following lines of code:
@Dao
interface TaskDAO {
@Insert
fun addNewTask(task: Task): Long
@Update
fun updateTask(task: Task)
@Delete
fun deleteTask(task: Task)
[ 182 ]
Persisting with Databases Chapter 11
Room provides Insert, Update, and Delete annotations, so you don't have to
write queries for those
For select operations, you have to annotate the method with the query
Next, we have to create a database class that will connect our application to the database.
Create a new Kotlin class with the name AppDatabase, and replace its contents with the
following code:
@Database(entities = arrayOf(Task::class), version =
TodoListDBContract.DATABASE_VERSION)
abstract class AppDatabase : RoomDatabase() {
abstract fun taskDao(): TaskDAO
}
To use the database, open MainActivity. First, create a field of AppDatabase type:
private var database: AppDatabase? = null
Here, you specify your database class and the name of the database.
[ 183 ]
Persisting with Databases Chapter 11
Here, we make a call to the taskDao to retrieve the task list from the database in
the doInBackground() method.
Room creates and manages a master table, which it uses to track the version of your
database. As such, even though we need to perform a migration of the database if we want
to preserve the data currently in our database.
Then, replace the database instantiation in the MainActivity with the following code:
database = Room.databaseBuilder(applicationContext,
AppDatabase::class.java, DATABASE_NAME)
.addMigrations(object :
Migration(TodoListDBContract.DATABASE_VERSION - 1,
TodoListDBContract.DATABASE_VERSION) {
override fun migrate(database: SupportSQLiteDatabase) {
}
}).build()
Here, we add a new Migration object to the databaseBuilder, while specifying the
current version of the database and the new version.
Now, build and run. Your app will start up with the previously saved Tasks showing:
[ 184 ]
Persisting with Databases Chapter 11
Here, we make a call to the taskDao to insert the new task in the database in the
doInBackground() method.
[ 185 ]
Persisting with Databases Chapter 11
Next, in the onDialogPositiveClick() method, locate the line of code shown here:
val addNewTask = dbHelper.addNewTask(Task(taskDetails, ""))
Now, build and run. Just like in the previous section, when you add a new item, the entry
isn't just added to the ListView, it is also saved in the database:
[ 186 ]
Persisting with Databases Chapter 11
Updating a task
To update a task, create a new AsyncTask in the MainActivity:
private class UpdateTaskAsyncTask(private val database: AppDatabase?,
private val selectedTask: Task) : AsyncTask<Void, Void, Unit>() {
Here, we make a call to the taskDao to insert the new task in the database in
the doInBackground() method.
Now, build and run. Just like in the previous chapter, select a task and click the Edit menu
item. In the Edit Task dialog that shows up, make changes to the task details and click on
the SAVE button.
[ 187 ]
Persisting with Databases Chapter 11
This takes away the dialog, saves the changes to the database, updates your ListView with
the updated task, and displays a ticker with the message Task Updated Successfully at the
bottom of the screen:
Deleting a task
To delete a task, create a new AsyncTask in the MainActivity:
private class DeleteTaskAsyncTask(private val database: AppDatabase?,
private val selectedTask: Task) : AsyncTask<Void, Void, Unit>() {
[ 188 ]
Persisting with Databases Chapter 11
Build and run. Select a task and click the Delete menu item. This removes the selected task
from the ListView, removes it from the database as well, and displays a ticker with the
message Task deleted successfully at the bottom of the screen:
That's it. As you can see, using the ORM lets you write less code and reduces SQL errors.
[ 189 ]
Persisting with Databases Chapter 11
Non-relational databases
A non-relational, or NoSQL database, is a database that does not organize its data based on
relations. Unlike relational databases, there are different ways in which the different non-
relational databases store and manage their data. Some store data as key-value pairs, while
others store data as objects. A number of these database options support Android. In most
cases, these databases come with the ability to synchronize your data to an online server.
Two of the most popular No-SQL mobile databases are:
CouchBase Mobile
Realm
Document databases are schema-less, which means they are unstructured and as such do
not have restrictions on what can go into a document. They store their data as key-value
pairs.
Summary
In this chapter, we added the functionality of storing our tasks into a database. We also
learned about the different types of databases we can use. The most-used database among
Android developers is SQLite, but that does not prevent you from exploring other
options. There are also database services such as Firebase that provide a backend as a
service functionality.
When choosing a database, you should consider the data needs of your application. Is there
a need to store the data on an online server? Or, is it data that's only used locally for that
instance of the app? Do you want to or have the capability to set up and manage a custom
data server, or will you rather opt for a service that does that work for you? These are some
of the considerations when choosing a database for your Android application.
In the next chapter, we will work on adding a reminder functionality to our To-do List
application.
[ 190 ]
12
Setting Reminders for Tasks
With many real-world apps, there is the need to remind the user at some point to, say, take
some action or give out some information. For example, a fitness app may alert the user to
begin some workout sessions.
Here, you will build on the ToDoList app from the previous chapter by setting a reminder
for a task, then pop up a notification when the reminder is due. You will learn a lot as we
implement these features, using classes such as IntentService,
BroadcastReceiver, and Notification.
In this chapter, you are going to create a feature that allows the user to set reminders for
tasks.
By the end of this chapter, you will have learned the following:
Services
Broadcast receivers
In-app notifications
Push notifications
Setting Reminders for Tasks Chapter 12
AlarmManager
Reminders in android can best be implemented by using the AlarmManager. Why? See
what the official documentation has to say about it:
These allow you to schedule your application to be run at some point in the future.
Additionally:
The Alarm Manager is intended for cases where you want to have your application code run at a
specific time, even if your application is not currently running. For normal timing operations (ticks,
timeouts, and so on) it is easier and much more efficient to use Handler.
This means you have come to the right place if you want to implement such a feature as a
reminder. The alternative class to handle such a task, Handler, is best suited for tasks that
are expected to be completed while the app is still in use. Your app will definitely have
reminders spanning days, and probably weeks or months, so it is best to use
the AlarmManager class.
This is how it will work, your app will start a background service to kick off the timer for
the reminder, then send a broadcast to the app when it's due. Move on to see how to
implement this.
Elapsed Realtime: This fires the pending intent based on the amount of time
since the device was booted, but doesn't wake up the device. The elapsed time
includes any time during which the device was asleep.
Elapsed Realtime Wakeup: This wakes up the device and fires the pending
intent after the specified length of time has elapsed since device boot.
RTC: This fires the pending intent at the specified time, but does not wake up the
device.
RTC Wakeup: This wakes up the device to fire the pending intent at the
specified time.
You are going to use RTC Wakeup alarm type to wake up the device to fire the alarm at the
precise time the user sets.
[ 192 ]
Setting Reminders for Tasks Chapter 12
First, create a dialog for the user to select the time the alarm should go off. Create a new
class called TimePickerFragment. Then, update it with the code shown here:
import android.app.AlarmManager
import android.app.Dialog
import android.app.PendingIntent
import android.app.TimePickerDialog
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.support.v4.app.DialogFragment
import android.text.format.DateFormat
import android.util.Log
import android.widget.TimePicker
import android.widget.Toast
import java.util.Calendar
[ 193 ]
Setting Reminders for Tasks Chapter 12
alarmMgr.set(AlarmManager.RTC_WAKEUP, calendar.timeInMillis,
alarmIntent)
}
}
companion object {
val ARG_TASK_DESCRIPTION = "task-description"
Then, you overrode the onTimeSet method to handle the user's set time. You first logged
the time, then displayed a toast saying that the time had been set successfully and is
recorded.
Then, you created an intent to execute the AlarmReceiver (you will create it very soon).
Next was a PendingIntent, to be triggered when the alarm goes off. Then you (finally)
created the alarm passing in the user's time. This alarm will be triggered at the exact time
the user set. And, it will run only once.
if (showMenuItems) {
...
reminderItem.isVisible = true
}
[ 194 ]
Setting Reminders for Tasks Chapter 12
return true
}
You have just added a reminder menu item to be displayed when the user clicks on a task.
Now, go to onOptionsItemSelected to start the time picker when this menu item is
selected. Use the following code to achieve that:
} else if (R.id.delete_item == item?.itemId) {
...
} else if (R.id.reminder_item == item?.itemId) {
TimePickerFragment.newInstance("Time picker argument")
.show(fragmentManager, "MainActivity")
}
Next, update the menu items in the to_do_list_menu.xml with the following code:
<item
android:id="@+id/reminder_item"
android:title="@string/reminder"
android:icon="@android:drawable/ic_menu_agenda"
android:visible="false"
app:showAsAction="ifRoom"/>
Now, add the "reminder" string resource in your strings.xml file using the code shown
here:
<resources>
...
<string name="reminder">Reminder</string>
</resources>
Okay, that was great. Now, remember the AlarmReceiver class above? What does it do?
Move on to find out.
BroadcastReceiver
This is where you get to learn about the BroadcastReceiver class. According to the
official documentation, it is the base class for code that receives and handles broadcast
intents sent by sendBroadcast(Intent).
[ 195 ]
Setting Reminders for Tasks Chapter 12
Basically, it is responsible for receiving broadcast events in your app. There are two ways of
registering this receiver:
Sending broadcasts
You will use the LocalBroadcastManager to send the notification to the user when the
alarm sets off. This tip from the documentation is why it is better to use this method of
broadcasting:
"If you don't need to send broadcasts across apps, use local broadcasts. The implementation is much
more efficient (no interprocess communication needed) and you don't need to worry about any
security issues related to other apps being able to receive or send your broadcasts."
[ 196 ]
Setting Reminders for Tasks Chapter 12
All you did was to override the onReceive method to start an IntentService called
AlarmService (this class will be responsible for showing the notification). Well, the log
statement is only there to help with debugging.
Before moving on, register the service in your AndroidManifest.xml just as the
MainActivity component is. Here, you only need the name property, though:
<application>
...
<service android:name=".AlarmReceiver"/>
</application>
"IntentService is a base class for Services that handles asynchronous requests (expressed as
Intents) on demand. Clients send requests through startService(Intent) calls; the service is
started as needed, handles each Intent in turn using a worker thread, and stops itself when it runs
out of work."
The key thing here is the ability it gives your app to do some work without any
interference. This is unlike the Activity component, for instance, which has to be in the
foreground to run a task. AsyncTasks help with that but it's still not as flexible and is just
not suitable for such a long-running task like this. Move on to see it in action.
Note:
IntentService has it's own single worker thread for handling the requests
Only one request will be processed at a time
[ 197 ]
Setting Reminders for Tasks Chapter 12
Creating an IntentService
Create a subclass of the IntentService called ReminderService. You will have to
override the onHandleIntent() method to well, handle the Intent. Then, you would
build a Notification instance to notify the user that the reminder is due:
import android.app.IntentService
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.support.v4.app.NotificationCompat
import android.util.Log
if(null == intent){
Log.d("AlarmService", "onHandleIntent( OH How? )")
}
}
[ 198 ]
Setting Reminders for Tasks Chapter 12
In the onCreate(), you saved an instance of the applicationContext for later use.
In onHandleIntent(), you used the Kotlin safety check feature to ensure you call the
showNotification() method on a non-null instance.
You should be familiar with this, except for android:exported. That just means we are
disallowing any external application from interacting with this service.
Here are a few important limitations to note about the IntentService class.
It can't interact directly with your user interface. To put its results in the UI, you
have to send them to an Activity.
Work requests run sequentially. If an operation is running in an
IntentService, and you send it another request, the request waits until the first
operation is finished.
An operation running on an IntentService can't be interrupted.
It is now time to run your app. The alarm should fire and you should see a notification
indicating that.
There are other ways to send notifications to your app. Keep reading to learn about push
notifications.
[ 199 ]
Setting Reminders for Tasks Chapter 12
You have integrated In-app notifications, and now you shall see how to implement push
notifications using FCM.
In-app notifications basically means that the notification is triggered by and from within
the app. Push notifications, on the other hand, are sent from an external source.
Integrating FCM
1. Set up FCM SDK
You first have to add the SDK (Software Development Kit) to your app. You should make
sure you are targeting at least Android 4.0 (Ice Cream Sandwich). It should have the Google
Play Store app installed, or an emulator running Android 4.0 and Google APIs. Your
Android Studio version should be at least 2.2. You will use the Firebase Assistant window
within Android Studio to do the integration.
Also, make sure you have installed Google Repository version 26 or higher, using the
following steps:
You can now open and use the Assistant window in Android Studio by following these
steps:
[ 200 ]
Setting Reminders for Tasks Chapter 12
2. Click to expand and select Cloud Messaging, then click the Set up Firebase
Cloud Messaging tutorial to connect to Firebase and add the necessary code to
your app:
[ 201 ]
Setting Reminders for Tasks Chapter 12
[ 202 ]
Setting Reminders for Tasks Chapter 12
If you complete the walk-through with the Firebase assistant successfully, you will have the
following things done:
allprojects {
// ...
repositories {
// ...
maven {
url "https://maven.google.com" // Google's Maven repository
}
}
}
Then, in your module's build.gradle file, it will add the apply plugin line at the
bottom of the file, like so:
apply plugin: 'com.android.application'
android {
// ...
}
dependencies {
// ...
compile 'com.google.firebase:firebase-core:11.8.0'
}
// ADD THIS AT THE BOTTOM
apply plugin: 'com.google.gms.google-services'
[ 203 ]
Setting Reminders for Tasks Chapter 12
</intent-filter>
</service>
This is required if you want to manually handle the message received from the FCM while
the app is running. You, however, will not need this now as there is one a way of showing
the notification without your intervention.
If, for some reason, any of these are not done, you can manually log in to
Firebase website and follow the steps following to create the project on
Firebase and update your app's build files.
Using the Firebase website, the first thing to do after login is to add your project:
You will then be asked to enter the name of the project. Enter ToDoList for the Project
name. It will automatically generate a globally unique Project ID for you. Then, choose
your country of residence, and hit the CREATE PROJECT button:
[ 204 ]
Setting Reminders for Tasks Chapter 12
After this, choose the desired platform. Note that Firebase is not only used for Android but
iOS and web as well. Therefore, choose the Add Firebase to your Android app option:
[ 205 ]
Setting Reminders for Tasks Chapter 12
1. The first step is to register your app by supplying your package name:
[ 206 ]
Setting Reminders for Tasks Chapter 12
[ 207 ]
Setting Reminders for Tasks Chapter 12
3. Then, in the final step, you add the SDK to your app. Note that this is not
necessary if you have already done so:
That's it. You have added your app on Firebase. You will now be shown the page for your
newly created project. Here, you will see all the services available to your app. Select
the Notifications service and click GET STARTED:
[ 208 ]
Setting Reminders for Tasks Chapter 12
You will now be shown the following page. Click on the SEND YOUR FIRST MESSAGE
button:
[ 209 ]
Setting Reminders for Tasks Chapter 12
Then, choose Compose message. Here, enter the message to be sent in the Message text
box. Select Single device as the target. After entering the FCM registration token, you will
hit the SEND MESSAGE button to send the notification. Read on to find out how to get the
registration token:
Registration Token
When you run your app for the first time after setting up the FCM, the FCM SDK will
generate a token for your app. This token will change under the following circumstances,
and a new one will be generated accordingly:
[ 210 ]
Setting Reminders for Tasks Chapter 12
This token must be kept private. To access this token, you will log it to your Logcat
console. First, open MyFirebaseInstanceIDservice and update it with the following
code:
override fun onTokenRefresh() {
// Get updated InstanceID token.
val refreshedToken = FirebaseInstanceId.getInstance().getToken()
Log.d(FragmentActivity.TAG, "Refreshed token: " + refreshedToken)
Now that you have the key, paste it into the Compose message box above and hit
the SEND MESSAGE button. You should see the notification on your phone shortly after
that.
Summary
In this chapter, you learned how to create background services, send broadcast messages,
show in-app notifications and push notifications using Firebase. There are a few things you
can do on your own to deepen your understanding of these topics:
Instead of notifying the user using some static message, use the description of the
task for which the reminder was set
Using Firebase, you can also try sending the push notification to a group of
people instead of a single device
[ 211 ]
13
Testing and Continuous
Integration
In this chapter, you will be introduced to the concept of Continuous Integration (CI) and
the importance of testing. Never heard of CI? Well, what about testing?
Testing
Software testing is the process of evaluating software, or a piece of it, to ensure it works as
expected. The product has to satisfy the given requirements for which it was built.
Therefore, the report from a test gives an indication of the quality of the software. Another
main reason for testing is to find bugs and fix them.
At times, there is the temptation to treat testing as an afterthought. This is mostly as a result
of issues such as time constraints, but considering the importance of testing, it should form
a part of the development process. Writing tests much later in the life of the software can be
a very terrible experience. You may have to commit huge amounts of time refactoring it to
make it testable before you even get to write the tests. The frustration involved in all of
these factors makes it difficult for most software to have proper tests.
Testing and Continuous Integration Chapter 13
Importance of testing
Testing is a very broad topic, and you could easily write a book about it. The importance of
testing cannot be emphasized enough. Here are some reasons why all software needs
testing:
Model-View-Presenter architecture
As mentioned earlier, the software needs to be testable. Only then can we write efficient
tests for them. For this reason, you will architect your app using the Model-View-
Presenter (MVP) architecture. This architecture employs some design best practices such as
inversion of control and dependency injection, thus making it suitable for testing. For an
app to be testable, it has to have its parts decoupled as much as possible.
[ 213 ]
Testing and Continuous Integration Chapter 13
Check out the high-level diagrammatic view of an MVP architecture in the following
diagram:
You could also easily swap out other parts and mock them during testing. In software
testing, mocks are objects which mimic real objects. You will provide its behavior instead of
relying on the actual implementation of the code. This way, you get to focus on the class
under test, which is doing exactly as expected. You will see them in action in the following
sections.
[ 214 ]
Testing and Continuous Integration Chapter 13
Test-Driven Development
You will be building a Notes app using a type of software development known as Test-
Driven Development (TDD). Take a look at the following diagram, and the following
explanation:
TDD is a software development method where tests are written before the actual program
code.
RED: Red is the first stage of the TDD process. Here, you write tests. Since this is the first
test, it means you basically have nothing to test. Therefore, you must write the minimum
piece of code that you can test. Now, since it's the least amount of code required to be able
to write a test, it will most likely fail when you write the code. But that's totally fine. In
TDD, your tests must fail before anything else happens! When your tests fail, that's the first
stage of the TDD cycle—the red stage.
GREEN: Now, you have to write the minimum piece of code required to pass the test.
When the test passes, that's great, you have completed the second phase of the TDD cycle.
Passing the test means that you have a part of your program working just as you expect it
to. And as you keep building your app this way, at any point in time you will have every
part of your code tested. Can you see how this works? By the time you have completed a
feature, you have sufficient tests for testing various parts of that feature.
[ 215 ]
Testing and Continuous Integration Chapter 13
REFACTOR: The final stage of the TDD process is to refactor the code you wrote earlier to
pass the test. Here, you remove redundant code, clean up, and write the full
implementation for mocks. Afterwards, run the tests again. They may likely fail. In TDD,
failing tests is a good thing. When you write tests and they pass, you can be certain a
particular requirement or expectation has been met.
There are other forms of development models built around testing, such as Behavior-
Driven Testing, black-box testing, and smoke testing. However, they can basically be
categorized under functional testing and non-functional testing.
Unit testing
Integration testing
Acceptance testing
And with non-functional tests, you test the application against its operational environment.
For instance, the app will connect to a real data source and use an HTTP connection. These
include:
Security testing
Usability testing
Compatibility testing
[ 216 ]
Testing and Continuous Integration Chapter 13
Notes app
To start building our Notes app, create a new application and call it notes-app. Switch to
the Project view using the tab at the top left corner of Android Studio. This view allows
you to see the full project structure as it exists on your file system. It should look like the
following screenshot:
Unit tests test small pieces of code, without any other parts of the product. In this case, it
means your unit tests will not need a physical device, nor the Android jar, database, or
network; just the source code you have written. These are the kind of tests that are to be
written in the test directory.
On the other hand, integration tests include all of the components required to run the app,
and these tests will go into the androidTest directory.
[ 217 ]
Testing and Continuous Integration Chapter 13
Test dependencies
Currently, there is only one testing library, Junit, which you will use for unit testing. But,
since your code will interact with other components, even though they may not be the ones
under test, you will have to mock them. Junit is still not enough for writing the test cases.
Therefore, you will also need to add Hamcrest to help with creating assertion matches and
more. Let's go ahead and add the libraries we will need.
Open your module's build file, update the dependencies to match the following code, and
sync the project:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
testImplementation 'junit:junit:4.12'
testImplementation "org.mockito:mockito-all:1.10.19"
testImplementation "org.hamcrest:hamcrest-all:1.3"
testImplementation "org.powermock:powermock-module-junit4:1.6.2"
testImplementation "org.powermock:powermock-api-mockito:1.6.2"
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-
core:3.0.1'
}
For now, use the exact library versions as shown in the preceding code.
This means you will have to ignore suggestions from the IDE to upgrade
your library versions.
Later, you can update to newer, stable versions which are compatible with
each other.
Since the Presenter coordinates between the Model and View, you will have to mock them
so you can focus on the class under test.
[ 218 ]
Testing and Continuous Integration Chapter 13
In this test, you will verify that asking the NotesPresenter to Add a new note will trigger
a call to the View to show the add-note screen. Let's implement the `should display
note when button is clicked`() test method.
You will first add a call to the presenter's addNewNote() method. Then, you will verify
that the View's showAddNote() is called. Therefore, you call on one method and verify that
it, in turn, calls another method (recall how the MVP pattern works; the presenter
coordinates with the views).
For now, we will not worry about what the second call method does; this is unit testing,
and you test one small thing (unit) at a time. So, you will have to mock out the View, and
you don't need to implement it now. A few interfaces can achieve this; that is, an API or
contract without necessarily implementing them. See the following final pieces of code:
import com.packtpub.eunice.notesapp.notes.NotesContract
import com.packtpub.eunice.notesapp.notes.NotesPresenter
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@Mock
private lateinit var notesView: NotesContract.View
private lateinit var notesPresenter: NotesPresenter
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@Test
fun `should display note view when button is clicked`() {
// When adding a new note
notesPresenter.addNewNote()
[ 219 ]
Testing and Continuous Integration Chapter 13
Now, create NotesContract, which is the View part of the MVP architecture. It will be an
interface where only the methods are required to make the test pass:
interface NotesContract {
interface View {
fun showAddNote()
}
interface UserActionsListener {
fun addNewNote()
Next, create the Note class. It represents the Model in the MVP architecture. It defines the
structure of the notes for the notes-app you're building:
import java.util.UUID
Create the NotesPresenter, which represents the Presenter in the MVP architecture. Let it
implement the UserActionsListener in the NotesContract class:
class NotesPresenter: NotesContract.UserActionsListener {
override fun loadNotes(forceUpdate: Boolean) {
}
[ 220 ]
Testing and Continuous Integration Chapter 13
That's enough for the first test. Are you ready? Okay, now click the right arrowhead beside
the number on which the test method is defined. Or, you could equally right-click in or on
the NotesPresenterTest file and select Run:
[ 221 ]
Testing and Continuous Integration Chapter 13
[ 222 ]
Testing and Continuous Integration Chapter 13
That's one complete cycle using TDD. Now, you need to keep going, and there are a few
more tests to complete before the feature will be fully implemented.
Your next test is to validate that the presenter displays the notes as expected. In this
process, the notes will have to be retrieved from the repository first before you update the
view.
You will use similar test APIs from the previous test. There is a new one you'll learn here,
however, which is called ArgumentCaptor. As you may have guessed, it captures the
arguments passed to a method. You will use these to call another method and pass them in
as parameters. Let's have a look:
@Mock
private lateinit var notesRepository: NotesRepository
@Captor
private var loadNotesCallbackCaptor:
ArgumentCaptor<NotesRepository.LoadNotesCallback>? = null
@Test
fun `should load notes from repository into view`() {
// When loading of Notes is requested
notesPresenter.loadNotes(true)
[ 223 ]
Testing and Continuous Integration Chapter 13
You first called the method you are testing, which is loadNotes(). Then, you verified that
that action, in turn, gets the notes (getNotes()) using the NotesRepository instance, just
like the previous test. You then verified that the instance passed to the getNotes()
method, which is again used to load the notes (onNotesLoaded()). Afterwards, you verify
that notesView hides the progress indicator (setProgressIndicator(false)) and
displays the notes (showNotes()).
This results in much cleaner code because then you don't have to have
nullability checks everywhere, nor do you have to use the elvis operator,
either.
interface LoadNotesCallback {
...
}
...
}
[ 224 ]
Testing and Continuous Integration Chapter 13
You are all set to test your second test case now. Go ahead and run it:
Okay, it fails. And again, with TDD, that's perfect! You realize that this tells us exactly what
is missing, and thus what needs to be done. You only have the contract (interface)
implemented, but no further action goes on there.
Open up your NotesPresenter and refactor the code to make this test pass. You will first
add the NotesRepository as part of the constructor parameters, and then you will make
the call within the appropriate method. See the following code for the full implementation:
import com.packtpub.eunice.notesapp.data.NotesRepository
import com.packtpub.eunice.notesapp.util.EspressoIdlingResource
[ 225 ]
Testing and Continuous Integration Chapter 13
notesView.setProgressIndicator(true)
if (forceUpdate) {
notesRepository.refreshData()
}
EspressoIdlingResource.increment()
notesRepository.getNotes(object : NotesRepository.LoadNotesCallback {
override fun onNotesLoaded(notes: List<Note>) {
EspressoIdlingResource.decrement()
notesView.setProgressIndicator(false)
notesView.showNotes(notes)
}
})
}
...
}
In the loadNotes() method, you displayed the progress indicator and refreshed the data
depending on the forceUpdate field.
import android.support.test.espresso.IdlingResource
object EspressoIdlingResource {
[ 226 ]
Testing and Continuous Integration Chapter 13
import android.support.test.espresso.IdlingResource
import java.util.concurrent.atomic.AtomicInteger
class SimpleCountingIdlingResource
@Volatile
private var resourceCallback: IdlingResource.ResourceCallback? =
null
fun decrement() {
val counterVal = counter.decrementAndGet()
if (counterVal == 0) {
// we've gone from non-zero to zero. That means we're idle
now! Tell espresso.
resourceCallback?.onTransitionToIdle()
}
if (counterVal < 0) {
throw IllegalArgumentException("Counter has been
corrupted!")
[ 227 ]
Testing and Continuous Integration Chapter 13
}
}
}
Make sure to update your app's build dependencies with the EspressoIdlingResource
library:
dependencies {
...
implementation "com.android.support.test.espresso:espresso-idling-
resource:3.0.1"
...
}
Next, update the setUp method to initialize correctly the NotesPresenter class:
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
Great! Really awesome stuff. You have successfully written the business logic for the
NotesApp using a TDD approach.
Crashlytics
From the official website:
Firebase Crashlytics is a lightweight, real-time crash reporter that helps you track, prioritize, and fix
stability issues that erode your app quality. Crashlytics saves you troubleshooting time by
intelligently grouping crashes and highlighting the circumstances that lead up to them.
[ 228 ]
Testing and Continuous Integration Chapter 13
There you have it, that's basically what Crashlytics is about. It works on iOS and
Android. Here are some of its primary capabilities:
Crash reporting: Its main purpose is to report crashes, and it does it really well.
It can be customized to suit your needs. For example, you may not want it to
report certain kinds of crashes, among other customization options.
Analytics: It gives reports on crashes including data on the users affected, their
devices, the time the crash occurred, including clean stack traces and logs to aid
in debugging and fixing.
Real-time alerts: You are automatically alerted about new and recurring issues.
The real-time alerts are necessary as they help you mitigate issues very quickly.
Crashlytics is used to find out if a particular crash is impacting a lot of users. You also get
alerts when an issue suddenly increases in severity, and it allows you to figure out which
lines of code are causing crashes.
Connect
Integrate
Check console
Connect
You will start by adding Firebase to your app. Firebase is a platform for development for
both mobile and web applications. It has a lot of tools, of which one is Crashlytics.
A device running Android 4.0 (Ice Cream Sandwich) or newer, and Google Play
Services 12.0.1 or higher
Android Studio 2.2 or later
You will use the Firebase Assistant tool within Android Studio 2.2+ to connect your app to
Firebase. The Assistant tool will update your existing project or create a new one with all
the necessary Gradle dependencies. It provides a very nice intuitive UI guide which you
can follow:
[ 229 ]
Testing and Continuous Integration Chapter 13
Check out the full guide for adding your project to Firebase in Chapter 12, Setting
Reminders for Tasks. When you are done, log into the Firebase console from your browser.
On the side menu, select Crashlytics from the STABILITY section:
[ 230 ]
Testing and Continuous Integration Chapter 13
When the Crashlytics page opens up, you will be asked if the app is new to Crashlytics.
Select Yes, this app is new to Crashlytics (it doesn't have any version of the SDK):
The second step then gives you a link to the documentation page to set up Crashlytics for
your app. To add Crashlytics to the app, update your project-level build.gradle:
buildscript {
repositories {
// ...
maven {
url 'https://maven.fabric.io/public'
}
}
dependencies {
// ...
classpath 'io.fabric.tools:gradle:1.25.1'
}
[ 231 ]
Testing and Continuous Integration Chapter 13
allprojects {
// ...
repositories {
// ...
maven {
url 'https://maven.google.com/'
}
}
}
Then, update your app module's build.gradle file with the Crashlytics plugin and
dependency:
apply plugin: 'com.android.application'
apply plugin: 'io.fabric'
dependencies {
// ...
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.1'
}
That's it, Crashlytics is ready to listen for crashes in your app. This is its default behavior,
but if you want to control the initialization yourself, you will have to disable it in your
manifest file:
<application
...
<meta-data android:name="firebase_crashlytics_collection_enabled"
android:value="false" />
</application>
Then, in your Activity class, you can enable it, even with a debugger like so:
val fabric = Fabric.Builder(this)
.kits(Crashlytics())
.debuggable(true)
.build()
Fabric.with(fabric)
[ 232 ]
Testing and Continuous Integration Chapter 13
Since your app will need to send reports to your console, add the internet permissions in
your manifest file as well:
<manifest ...>
<application ...
As usual, sync Gradle to update your project with the dependency updates you just made.
After this, you should see the Fabric plugin integrated with Android Studio. Sign up with
your email and password:
[ 233 ]
Testing and Continuous Integration Chapter 13
After confirming your account, the Fabric API key will be generated for you. It should look
something like this:
<meta-data
android:name="io.fabric.ApiKey"
android:value="xxYYxx6afd23n6XYf9ff6000383b4ddxxx2220faspi0x"/>
You will now force a crash in your app in order to test it. Create a new blank activity and
add only one button. Then, set its clicklistener to force the crash. The Crashlytics SDK
has a simple API for doing just this:
import kotlinx.android.synthetic.main.activity_main.*
...
Since you're testing, reopen the app after it crashes so the report can be sent to your
console.
[ 234 ]
Testing and Continuous Integration Chapter 13
Go ahead and run the app. Your test activity should look like this:
[ 235 ]
Testing and Continuous Integration Chapter 13
Click on the CRASH! button to force the crash. Your app will crash. Click OK and reopen
the app:
[ 236 ]
Testing and Continuous Integration Chapter 13
Check your inbox, that is, the one you signed up with on Crashlytics:
Click the Learn more button. It will open up the Crashlytics console. From there, you can
find more details about the crash. From there, you can resolve it.
Stages of testing
There are two main stages in testing: alpha and beta testing. The main idea is to get a set of
people to test the app at a stage in the development of the app. It usually begins just after
the app is beginning to take shape, so that the feedback can be harnessed to make the app
more stable. Stability is the key here. One key thing that distinguishes the various testing
stages is the number of people involved in the testing.
[ 237 ]
Testing and Continuous Integration Chapter 13
Alpha testing
Alpha testing is considered to be the first phase of testing software. This test usually
involves very few numbers of testers. At this stage, the app is highly unstable, so a few
people close to the developers will be involved at this stage to test and provide constructive
feedback. After the app becomes stabilized, it is ready to move into beta testing.
Beta testing
Beta testing is a phase of software testing where a larger group of people test out the
application. It could involve 10, 100, or 1,000 people or more, depending on the nature of
the app and the size of the team working on the app. If an app has lots of users worldwide,
it'll most likely have a large team working on it and thus can afford to have lots of people
engaged in beta testing the app.
Here, you will be able to upload your APK, distribute it to a selected group of people, and
track feedback as they test. You can manage both alpha and beta testing stages, too.
[ 238 ]
Testing and Continuous Integration Chapter 13
Continuous integration
Usually, more than one person (team) works on an app. For instance, person A may work
on the UI, while person B works on feature 1 and person C works on feature 2 in the
business logic. Such a project will still have one code base along with its tests and
everything else. All committers will likely run tests locally against what each has worked
on before pushing the code. The code in a shared repository with different committers has
to be unified and built as one complete app (integration). The tests have to be run for the
whole app as well. This has to be done regularly, and in the case of CI, per every commit.
So in a day, the code in the shared repository will have been built and tested many times.
This is the concept of Continuous Integration. The following is a very simple diagram
showing the flow of the CI process. It begins from the left (Development):
[ 239 ]
Testing and Continuous Integration Chapter 13
Definition
CI is a software development practice where an automated system is set up to build, test,
and report on a piece of software after it has been checked into version
control. Integration occurs because the various branches are merged into the main branch.
This means whatever is in the main branch effectively represents the current state of the
entire app, and since this happens every time code enters the main repository,
it's continuous; hence, Continuous Integration.
In CI, whenever code is committed, an automated build system automatically grabs the
latest code from the shared repository (main branch) and builds, tests, and validates the
whole branch. By doing this regularly, errors are quickly detected and thus can be fixed
quickly. Knowing that your commit could cause an unstable build, you are forced to
commit small changes only. This also makes it easy to identify and fix bugs.
This is very important because, though the different parts of the app are tested and built
individually, it may not be necessary after they have been merged into a shared repository.
Each check-in is then verified by an automated build, allowing teams to detect problems
early.
[ 240 ]
Testing and Continuous Integration Chapter 13
In the same light, there is also Continuous Deployment as well as Continuous Delivery.
Tools
There is a wide array of tools available for CI. Some are open source, some are self-hosted,
some are more suitable for web frontend, some for web backend, and some are more
suitable for mobile development.
Examples include Jenkins, Bamboo, and Fastlane. You will use Fastlane to integrate your
app and run your tests. Fastlane is self-hosted, which means that you run it on your
development machine. Ideally, you should install it on a CI server, a dedicated server for CI
tasks.
First, let's install it locally and use it to run tests for the Notes app.
Fastlane, at the time of writing this book, runs on MacOS only. There is
work in progress to get it to work on Linux and Windows as well. Some
CI services include Jenkins, Bamboo, GitLab CI, Circle CI, and Travis.
Installing fastlane
To install fastlane, follow these steps:
1. You should already have gem on your path in your Terminal since x-code uses
Ruby and comes bundled with Mac OS X. Run the following command to install
it:
brew cask install fastlane
You may need to use sudo depending on your user account's privileges.
2. After a successful install, export the path to the bin directory to your PATH
environment variable:
export PATH="$HOME/.fastlane/bin:$PATH"
[ 241 ]
Testing and Continuous Integration Chapter 13
4. Open a new session within your Terminal. This new session will load the
changes you just made to your environment variables. First, ensure that you have
bundler installed. Use the following command if you don't have it already:
5. Then, switch to the root of your working directory. There, initialize fastlane
with the following command:
fastlane init
You will be asked a few questions as part of the process. The first is for your
package name. A default one will be provided when you leave it blank, so enter
your package name:
6. Next, you will be asked to supply the path to a certain service action JSON secret
file. Just press Enter, as we will not be needing it just yet; it can be supplied later.
Finally, you will be asked whether you want to upload some metadata among
other things. Humbly decline; you can set it up later with:
fastlane supply init
There will be a few other prompts, for which you will just have to hit the Enter
key.
7. When you are done, use the following command to run your test:
fastlane test
[ 242 ]
Testing and Continuous Integration Chapter 13
When all goes well, you should see the results as follows:
Summary
In this chapter, you have been introduced to the concept of CI and testing. You have
learned how to use the ATSL to write tests.
You learned about the two most popular stages in testing and how to set them up in the
Google Play console. You tried out Crashlytics and experienced its crash reporting feature
among others. Then you learned about CI, and as an example, you used one of the CI tools
called Fastlane.
Wow, this chapter was really packed and here you are—you have made it to the end. In the
next chapter, you will learn how to "make your app available to the world". Interesting,
right? Well, let's move on; we'll meet again in the next chapter.
[ 243 ]
14
Making Your App Available to
the World
Having built the app after working many long hours and learning many new things, the
ultimate satisfaction for a developer is to see users download it with ease and enjoy the
experience of using the app while getting the most out of its usage.
In this chapter, we will learn various steps involved in distributing our app via the Google
Play Store and Amazon App Store. We will also learn about digitally signing our app to
verify its authenticity of our application.
Key store generation through Android Studio and the command line
Publishing the App via the Google Play Store
Publishing the App via the Amazon App Store
The owner of the app, that is, the developer, will have the private key, and the distribution
channel signs this with the corresponding public key. This combination of the public and
private key means that the digital signature is stored in the keyStore file. The keyStore
is a binary file where the digital keys for signing the app are stored.
Making Your App Available to the World Chapter 14
It is important and mandatory to have the APKs digitally signed before we release the app
to the Google Play Store for distribution. The digital signature serves as authentication of
the developer and ensures updates to the app can only be made through a trusted source.
It's important to keep the keystore file safe and to remember the key
password. Once an app is signed and released with the keystore file, any
further updates to the app can only be done by using the same key.
Android Studio
The command line
Let's discuss the steps involved in the generation of the key store in detail.
1. Once we open the project we wish to generate the APK for, click on Build |
Generate Signed APK. This will result in the Generate Signed APK screen to be
displayed. Android Studio expects the user to select the Key Store Path:
[ 245 ]
Making Your App Available to the World Chapter 14
2. Since we would be generating a new key store, click on Create new button. This
will display the New Key Store window as follows:
[ 246 ]
Making Your App Available to the World Chapter 14
3. Select the Key Store path and provide a name for the .jks (Java Key Store) file:
[ 247 ]
Making Your App Available to the World Chapter 14
4. Once we confirm the Key Store path, we are required to fill in the Key Store
Password, Key Alias, Password for the key alias, First and Last Name,
Organizational Unit, Organization, City or Locality, State or Province, and
Country Code (XX):
[ 248 ]
Making Your App Available to the World Chapter 14
5. Once the required details are filled in and the OK button is clicked, we should be
able to proceed with the Generate Signed APK screen. Click Next:
6. On the next screen, we will have the option to choose the APK destination
folder and the build type. Afterwards, click on the Finish button:
[ 249 ]
Making Your App Available to the World Chapter 14
7. Upon completion, the confirmation of the generation of the signed APK along
with the option to locate or analyze the APK is displayed in the console:
8. The signed APK that is digitally signed and eligible for release via the Google
Play store and other release platforms can be found at the destination folder:
9. Now that we have the keystore generated, whenever we make an update to the
app in the future, Android Studio will provide us with the Generate Signed
APK screen with the path, and expects the password to be filled in:
[ 250 ]
Making Your App Available to the World Chapter 14
Following the same process described in new key store generation, users should be able to
generate the signed APK.
Adding the signing config will result in signing details being added to the build.gradle
file as plain text:
android {
signingConfigs {
config {
keyAlias 'packtkey'
keyPassword 'vasantham'
storeFile file('G:/newkey/dreamindia.jks')
storePassword 'vasantham'
}
[ 251 ]
Making Your App Available to the World Chapter 14
It is prudent to move this information out of build files to ensure that sensitive information
is not easily accessible by others. In the root directory of the project, we should create a file
called keystore.properties. This file will contain the following information:
storePassword = OurStorePassword
KeyPassword = ourKeyPassword
keyAlias = ourKeyAlias
storeFile = ourStoreFileLocation
As we have moved the Key Store details to a separate file, we should now add the
following code in the build.gradle file to ensure that the signing config is available for
auto signing the APKs. We should load the keystore.properties file before the
android{} block.
android {
.......
}
With the addition of the preceding code, we can refer to all properties stored in the
keystoreProperties using the syntax keystoreProperties['propertyName']. We
should be able to modify the signing config present in the build.gradle file as plain text,
as shown in the following code:
android {
signingConfigs {
config {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
}
..........
}
[ 252 ]
Making Your App Available to the World Chapter 14
The build file is now secure and does not include any sensitive
information. If we are to use any source control or share the code base, we
need to ensure that the keystore.properties file is removed and kept
secure.
Debug: Turns debug options ON and can also sign the app with the debug key
Release: Turns debug options OFF, signs the app with the release key, and also
reduces or removes code related to debugging from the final APK
[ 253 ]
Making Your App Available to the World Chapter 14
Providing basic features on the free app and preimum features of the paid app is a generic
norm in the app world. Android provides the option of defining them as productFlavors.
Free and paid are common product flavors used by developers. By defining various
product flavors, developers will have the option to maintain different code and thereby
different or extra features for the same app. The code base for the common functionality
offered by free and Paid versions can be the same, while the premium features can be
enabled for the paid product flavor version of the app.
To define a product flavor, right click on app | Project Structure, and in the Flavors tab,
you can define the product flavors—free or paid. The signing configuration can also be
customized to match the productFlavors:
[ 254 ]
Making Your App Available to the World Chapter 14
The preceding command takes a keystore path, the type of security algorithm to be used for
the key sign, the size of the key, validity, and the key alias name. Once the preceding
command is executed, we are be required to provide the password and a few more
additional details, as shown in the following screenshot:
[ 255 ]
Making Your App Available to the World Chapter 14
On successful execution of the command, we can find the keystore file being generated at
the same location of the keytool:
Registration for Google Play Store developer accounts requires a one time fee of $25. Log on
to https://play.google.com/apps/publish/ and complete the registration process.
[ 256 ]
Making Your App Available to the World Chapter 14
The Google Play Store provides an excellent console for the developers called the Google
Play Console. This developer console contains all the features required to manage the
Android application publishing life cycle. We shall look at the important features that
enable us to publish our app.
The first step for publishing the app is to create the app in the Google Play Console. The
console provides an option to create the application, which kick-starts the process of
publication:
Once we click on Create Application, we will be prompted to enter the default language
and the title for the app. Clicking the create button will create the app for us:
[ 257 ]
Making Your App Available to the World Chapter 14
The developer console provides a host of options for the developer to fill in in the menu.
However, there are four important and mandatory sections that one needs to fill in to
ensure that the app can be published.
These four sections are App releases, Store listing, Content rating, and Pricing &
distribution:
Now, let's focus on the details that need to be filled in for these mandatory sections.
[ 258 ]
Making Your App Available to the World Chapter 14
We need to upload the signed APK that was generated for the build type release in order
for the production to roll out. One can browse the APK files and get them uploaded to the
Play Store:
[ 259 ]
Making Your App Available to the World Chapter 14
Once the APK is uploaded, the same can be found in the release section with a version code
and the option to remove the APK. Uploading the signed APK completes the details
required for the app release section:
[ 260 ]
Making Your App Available to the World Chapter 14
In the Google Play Store, the store listing section mandates the following:
[ 261 ]
Making Your App Available to the World Chapter 14
Creation of the graphical assets can be done using free image editors such
as gimp. It is important and mandatory to follow the guidelines of the
graphic specification.
Developers will be required to provide the type and category of the application along with
contact details and the privacy policy, if there is one. Once all of the details are provided,
the store listing section will be complete.
[ 262 ]
Making Your App Available to the World Chapter 14
[ 263 ]
Making Your App Available to the World Chapter 14
Once the developer provides all the necessary details in the pricing and distribution
section, a message stating Ready to publish will appear. Also, note that all four mandatory
sections are marked in green, stating that they have been completed:
Once the app is submitted for publishing, it is reviewed and made available for download
within a few hours. In case any queries are raised, developers needs to address them and
resubmit the app for release.
[ 264 ]
Making Your App Available to the World Chapter 14
Once we log in to the appstore, we will need to click on the Add Android App button in
the amazon appstore:
The Amazon Appstore requires the following sections to be filled in: General Information,
Availability & Pricing, Description, Images & Multimedia, Content Rating, and Binary
File(s).
[ 265 ]
Making Your App Available to the World Chapter 14
General information
In the general information section, developers are required to provide information about
the app title, package name, app ID, release ID, category of the app, and contact details of
the developer:
[ 266 ]
Making Your App Available to the World Chapter 14
[ 267 ]
Making Your App Available to the World Chapter 14
Description section
In the Description section, developers are expected fill in details about the title, short
description, and long description:
[ 268 ]
Making Your App Available to the World Chapter 14
This section also enables developers to provide Product feature bullets and specific
Keywords which identify the app. Users will also have the option to add localized
descriptions:
[ 269 ]
Making Your App Available to the World Chapter 14
There is also an option to provide graphics related to form factors such as tablets and
phones:
[ 270 ]
Making Your App Available to the World Chapter 14
[ 271 ]
Making Your App Available to the World Chapter 14
The developers are expected to answer questions about the usage of location-based
services, ads in the app, provide a privacy policy (if any), and disclose whether the app is
directed at children under the age of 13:
[ 272 ]
Making Your App Available to the World Chapter 14
[ 273 ]
Making Your App Available to the World Chapter 14
Developers also get the option to decide on Device Support, Language Support, Export
Compliance, and Use Amazon Maps Redirection:
[ 274 ]
Making Your App Available to the World Chapter 14
Having filled out all the required information, it is time to actually publish the app in the
Amazon Appstore. Developers will have an option to review all the information they have
entered:
Summary
The store listing, key words, description, and so on play a huge role in the identification of
apps and ultimately toward the success of the app and the developer.
In this chapter, we discussed the various steps involved in the generation of the keystore
file using Android Studio, signing APKs automatically, generating the keystore file from
the command line, and publishing the app via the Google Play Store and the Amazon
Appstore.
In the next chapter, we will learn about working with one of the most interesting and
important APIs available to us—the Google Faces API. The Google Faces API enables
developers to provide cool features such as facial detection, identification of people in a
photograph, and so on.
[ 275 ]
15
Building an App Using the
Google Faces API
The ability of computers to perform tasks such as identifying objects has always been a
humongous task for both the software and the required architecture. This isn't the case
anymore since the likes of Google, Amazon, and a few other companies have done all the
hard work, providing the infrastructure and making it available as a cloud service. It
should be noted that they are as easy to access as making REST API calls.
In this chapter, you will learn how to use the face detection API from Google's Mobile
Vision API to detect faces and add fun functionalities such as adding rabbit ears to a user's
picture.
Currently, the Mobile Vision API includes face, barcode, and text detectors.
Building an App Using the Google Faces API Chapter 15
Face detection is the process of automatically locating human faces in visual media (digital images or
video). A face that is detected is reported at a position with an associated size and orientation. Once a
face is detected, it can be searched for landmarks such as the eyes and nose.
A key point to note is that only after a face is detected will landmarks such as eyes and a
nose be searched for. As part of the API, you could opt out of detecting these landmarks.
Note the difference between face detection and face recognition. While the
former is able to recognize a face from an image or video, the latter does
the same and is also able to tell that a face has been seen before. The
former has no memory of a face it has detected before.
We will be using a couple of terms in this section, so let me give you an overview of each of
these before we go any further:
Face tracking extends face detection to video sequences. When a face appears in a video for
any length of time, it can be identified as the same person and can be tracked.
It is important to note that the face that you are tracking must appear in the same video.
Also, this is not a form of face recognition; this mechanism just makes inferences based on
the position and motion of the face(s) in a video sequence.
A landmark is a point of interest within a face. The left eye, right eye, and nose base are all
examples of landmarks. The Face API provides the ability to find landmarks on a detected
face.
[ 277 ]
Building an App Using the Google Faces API Chapter 15
With a minimum of Google Play Services 7.8, you can use the Mobile Vision APIs, which
provide the face detection APIs. Make sure you update your Google Play Services from the
SDK manager so that you meet this requirement.
Get an Android device that runs Android 4.2.2 or later or a configured Android
Emulator. The latest version of the Android SDK includes the SDK tools component.
Now, update your AndroidManifest.xml to include meta data for the faces API:
<meta-data
android:name="com.google.android.gms.vision.DEPENDENCIES"
android:value="face" />
To keep things simple, for this lab, you're just going to process an image that is already
present in your app. Add the following image to your res/drawable folder:
[ 278 ]
Building an App Using the Google Faces API Chapter 15
You will first load the image into memory, get a Paint instance, and create a temporary
bitmap based on the original, from which you will create a canvas. Create a frame using the
bitmap and then call the detect method on FaceDetector, using this frame to get
back SparseArray of face objects.
Well, let's get down to business—this is where you will see how all of these play out.
First, open up your activity_main.xml file and update the layout so that it has an image
view and a button. See the following code:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
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"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context="com.packtpub.eunice.funyface.MainActivity">
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@mipmap/ic_launcher_round"
app:layout_constraintBottom_toTopOf="parent"
android:scaleType="fitCenter"/>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center"
android:text="Detect Face"/>
</FrameLayout>
That is all you need to do here so that you have FrameLayout with ImageView and a
button. Now, open up MainActivity.kt and add the following import statements. This is
just to make sure that you import from the right packages as you move along. In
your onCreate() method, attach a click listener to the button in your MainActivity
layout file:
package com.packtpub.eunice.funface
import android.graphics.*
[ 279 ]
Building an App Using the Google Faces API Chapter 15
import android.graphics.drawable.BitmapDrawable
import android.os.Bundle
import android.support.v7.app.AlertDialog
import android.support.v7.app.AppCompatActivity
import com.google.android.gms.vision.Frame
import com.google.android.gms.vision.face.FaceDetector
import kotlinx.android.synthetic.main.activity_main.*
button.setOnClickListener {
detectFace()
}
}
}
[ 280 ]
Building an App Using the Google Faces API Chapter 15
The Paint class holds the information related to the style and color related
to the text, bitmap, and various shapes.
Creating a canvas
To get the canvas, first create a bitmap using the dimensions from the bitmap you created
earlier. With this canvas, you will paint over the bitmap to draw the outline of the face after
it has been detected:
// Create a canvas using the dimensions from the image's bitmap
val tempBitmap = Bitmap.createBitmap(myBitmap.width, myBitmap.height,
Bitmap.Config.RGB_565)
val tempCanvas = Canvas(tempBitmap)
tempCanvas.drawBitmap(myBitmap, 0F, 0F, null)
The Canvas class is used to hold the call made to draw. A canvas is a
drawing surface and it provides various methods for drawing onto a
bitmap.
[ 281 ]
Building an App Using the Google Faces API Chapter 15
Note that on its first run, the Play Services SDK will take some time to initialize the Faces
API. It may or may not have completed this process at the time you intend to use it.
Therefore, as a safety check, you need to ensure its availability before using it. In this case,
you will show a simple dialog to the user if the FaceDetector is not ready at the time the
app is run.
Also note that you may need an internet connection as the SDK initializes. You also need to
ensure you have enough space, as the initialization may download some native library onto
the device:
// Create a FaceDetector
val faceDetector =
FaceDetector.Builder(applicationContext).setTrackingEnabled(false)
.build()
if (!faceDetector.isOperational) {
AlertDialog.Builder(this)
.setMessage("Could not set up the face detector!")
.show()
return
}
[ 282 ]
Building an App Using the Google Faces API Chapter 15
imageView.setImageDrawable(BitmapDrawable(resources, tempBitmap))
Results
All set. Run the app, press the DETECT FACE button, and wait a while...:
[ 283 ]
Building an App Using the Google Faces API Chapter 15
The app should detect the face and a square box should appear around the face, voila:
Okay, let's move on and add some fun to their faces. To do this, you need to identify the
position of the specific landmark you want, then draw over it.
To find out the landmark's representation, you label them this time around, then
afterwards draw your filter to the desired position.
To label, update the for loop which drew the rectangle around the face:
// Mark out the identified face
for (i in 0 until faces.size()) {
...
when (landmark.type) {
[ 284 ]
Building an App Using the Google Faces API Chapter 15
NOSE_BASE -> {
val scaledWidth =
eyePatchBitmap.getScaledWidth(tempCanvas)
val scaledHeight =
eyePatchBitmap.getScaledHeight(tempCanvas)
tempCanvas.drawBitmap(eyePatchBitmap,
x - scaledWidth / 2,
y - scaledHeight / 2,
null)
}
}
}
}
Run the app and take note of the labels of the various landmarks:
[ 285 ]
Building an App Using the Google Faces API Chapter 15
Summary
In this chapter, you learned how to use the Mobile Vision APIs, in this case, the Faces API.
There are a few things to note here. This program is not optimized for production. Some
things you can do on your own are load the image and do the processing in a background
thread. You can also provide a functionality to allow the user to pick and choose images
from different sources other than the static one used. You can get more creative with the
filters and how they are applied too. Also, you can enable the tracking feature on the
FaceDetector instance, and feed in a video to try out face tracking.
[ 286 ]
Other Books You May Enjoy
If you enjoyed this book, you may be interested in these other books by Packt:
ISBN: 9781788394802
Explore Spring 5 concepts with Kotlin
Learn both dependency injections and complex configurations
Utilize Spring Data, Spring Cloud, and Spring Security in your applications
Create efficient reactive systems with Project Reactor
Write unit tests for your Spring/Kotlin applications
Deploy applications on cloud platforms like AWS
[ 288 ]
Other Books You May Enjoy
[ 289 ]
Index
[ 291 ]
Google location API environment, setting up 9
classes 116 features 7
integrating, into app 116 used, for developing Android 7
variables 116 using, alongside Java 46, 48
Google Maps API key
app, executing 96 L
generating 90, 94 lambda expression 80
reference 91 Lambdas 71
Google Play Store landmark 277
App release section 259 latitude
app, publishing 256 finding 99
Content rating section 263 location-based alarm (LBA)
Pricing and distribution section 263 about 86, 109
Store listing section 261 creating 86, 89
location
H matching 121
higher–order functions 80 longitude
higher–order lambdas 80 finding 99
I M
interoperability 48 Mobile Vision
about 276
J faces API concepts 277
Java Development Kit (JDK ) faces, detecting 277
about 10 rectangles, drawing on face 282
installing 11 results 283, 285
installing, on Windows 10 Model-View-Presenter (MVP) 213
reference 11
N
K network connectivity
key store generation about 124
about 244 manifest permissions 125
APK, auto signing through Android Studio 251 sync adapter 126
through Android Studio 245, 249 volley library 126
through command line 255 non-relational database 190
Kotlin environment setup NoSQL database
Android project, creating 18 about 190
Android Studio 12 CouchBase Mobile 190
Java 10 Realm 190
Kotlin plugin Null safety
installing 41, 43 !! operator 75
Kotlin about 73
code, adding to project 44, 45, 46 Elvis operator 75
connecting, to Java 49, 51 non-nullable types 74
[ 292 ]
nullable type 74 integrating 110
safe call operator 75 Single Abstract Method (SAM) 81
nullable type 74 smart cast
example 74
O Software Development Kit (SDK) 19, 200
object databases 190 software testing 212
Object Expressions 71 SQLite
Object Relational Mapping (ORM) library data, inserting into database 172, 173, 174
about 181 data, retrieving from database 174, 175
data, inserting into database 185 task, deleting 179, 181
data, retrieving from database 183, 184 task, updating 176, 178
task, deleting 188, 189 using 169, 171, 172
task, updating 187, 188 Structured Query Language (SQL) 168
using 182, 183
objects 69, 71 T
Test-Driven Development (TDD) 215
P testing
parameters 79 about 212
permissions alpha testing 238
adding 114 beta testing 238
Picasso Continuous Integration 239
about 127 importance 213
build.gradle file 130 stages 237
Kotlin code 131 third-party libraries
Manifest permissions 132 about 126
XML code 128 Glide 134
Primary Key 172 Picasso 127
properties 62 TodoList app
Firebase Cloud Messaging (FCM), integrating
R 200, 202, 203, 204, 206, 208, 210, 211
project, creating 139, 140, 141
Relational Database Management System
User Interface (UI), building 142
(RDBMS) 168
type checks
relational database
about 76
about 168, 169
cast operator, using 76
Object Relational Mapping (ORM) library 181,
182, 183
SQLite, using 169, 171, 172
U
User Interface (UI), TodoList app
S building 142
building, with Android Studio layout editor 143,
safe call operator 75
144, 145, 147
safe cast operator (as?) 76
data, displaying in ListView 156, 157
screen, for user input
functionality, adding 147
developing 103, 105
menu, adding 158, 159, 160, 161, 162, 163,
shared preferences
[ 293 ]
165
task, adding 148, 149, 150, 152, 153, 155 V
Todo item, deleting 157 variables 62
Todo item, updating 157
User Interface (UI) X
about 26, 54 XML layout files
game UI, building 56, 60 checking 102