Download as pdf or txt
Download as pdf or txt
You are on page 1of 305

Learning Kotlin by Building

Android Applications

Explore the fundamentals of Kotlin by building real-world


Android applications

Eunice Adutwumwaa Obugyei


Natarajan Raman

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.

Acquisition Editor: Nigel Fernandes


Content Development Editor: Flavian Vaz
Technical Editor: Diksha Wakode
Copy Editor: Safis Editing
Project Coordinator: Devanshi Doshi
Proofreader: Safis Editing
Indexer: Rekha Nair
Graphics: Jason Monteiro
Production Coordinator: Shantanu Zagade

First published: June 2018

Production reference: 1200618

Published by Packt Publishing Ltd.


Livery Place
35 Livery Street
Birmingham
B3 2PB, UK.

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

Get a free eBook or video every month

Mapt is fully searchable

Copy and paste, print, and bookmark content

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

About the authors


Eunice Adutwumwaa Obugyei is a software engineer from Accra, Ghana, with over 7
years of experience in Android development. Eunice is currently a senior software engineer
at DreamOval Ltd, where she focuses on backend development and native mobile
development. She is young, energetic, and always looking for an opportunity to learn and
improve the skills in her arsenal. She is passionate about figuring out solutions to problems
and finding the simplest way out, and her love for TV series and movies is her escape from
the rigors of work life. Feel free to reach out on Twitter or LinkedIn.

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.

Packt is searching for authors like you


If you're interested in becoming an author for Packt, please visit authors.packtpub.com
and apply today. We have worked with thousands of developers and tech professionals,
just like you, to help them share their insight with the global tech community. You can
make a general application, apply for a specific hot topic that we are recruiting an author
for, or submit your own idea.
Table of Contents
Preface 1
Chapter 1: Setting Up for Android Development 7
Why develop Android with Kotlin? 7
Concise 8
Say bye bye to the NullPointerException 9
Java interoperability 9
Setting up your environment 9
Java 10
Installing the JDK 11
Android Studio 12
Installing Android Studio 12
Getting Android Studio ready 14
Creating your first Android project 18
Choosing an SDK 19
Building your project 23
Gradle 23
Parts of an Android project 25
Running your app 26
The Android emulator 26
Creating an Android emulator 27
Running from an emulator 34
Running on an actual device 37
Summary 40
Chapter 2: Configuring Your Environment for Kotlin 41
Installing the Kotlin plugin 41
Making our project Kotlin-ready 44
Kotlin alongside Java? 46
Kotlin to Java? 49
Summary 52
Chapter 3: Data Types, Variables, and Constants 54
The user interface 54
Building our game UI 56
Basic types 62
Variables and constants 62
Properties 62
Summary 63
Chapter 4: Classes and Objects 64
Structure of a class 64
Table of Contents

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

Network connectivity 124


Manifest permissions 125
Volley library 126
Sync adapter 126
Third-party libraries 126
Picasso 127
User interface – XML 128
build.gradle 130
Kotlin code 131
Manifest permissions 132
Glide 134
build.gradle 134
Kotlin code 135
Summary 137
Chapter 10: Developing a Simple To-Do List App 138
Creating the project 139
Building your UI 142
Using the Android Studio layout editor 143
Adding functionality to the user interface 147
Adding a new task 148
Displaying data in the ListView 156
Updating/deleting a Todo item 157
Adding a menu 158
Summary 166
Chapter 11: Persisting with Databases 167
Introduction to databases 167
Relational databases 168
Using SQLite 169
Inserting data into the database 172
Retrieving data from the database 174
Updating a task 176
Deleting a task 179
ORM libraries 181
Retrieving data from the database 183
Inserting data into the database 185
Updating a task 187
Deleting a task 188
Non-relational databases 190
Summary 190
Chapter 12: Setting Reminders for Tasks 191
AlarmManager 192
Creating the alarm 192
Starting the reminder dialog 194
BroadcastReceiver 195
Sending broadcasts 196

[ iii ]
Table of Contents

Creating a broadcast receiver 196


Creating the AlarmService 197
Creating an IntentService 198
Firebase Cloud Messaging 199
Integrating FCM 200
Summary 211
Chapter 13: Testing and Continuous Integration 212
Testing 212
Importance of testing 213
Android Testing Support Library 213
Model-View-Presenter architecture 213
Test-Driven Development 215
Functional versus non-functional testing 216
Notes app 217
Test dependencies 218
Your first test 218
Crashlytics 228
Connect 229
Stages of testing 237
Alpha testing 238
Beta testing 238
Setting up for beta testing 238
Creating the beta test track 238
The opt-in URL 239
Continuous integration 239
Definition 240
Tools 241
Installing fastlane 241
Summary 243
Chapter 14: Making Your App Available to the World 244
Key store generation 244
Key store generation through Android Studio 245
Auto signing the APK through Android Studio 251
Build types and flavors 253
Key store generation through the command line 255
Publishing the app in Google Play Store 256
App release section 259
Store listing section 260
Content rating section 263
Pricing and distribution section 263
Publishing the app in Amazon Appstore 265
General information 266
Availability & Pricing section 267
Description section 268
Images & Multimedia section 270

[ iv ]
Table of Contents

Content Rating section 271


Binary File(s) section 273
Summary 275
Chapter 15: Building an App Using the Google Faces API 276
Introduction to Mobile Vision 276
Faces API concepts 277
Getting started – detecting faces 277
Creating the FunyFace project 278
Loading the image 280
Creating a Paint instance 281
Creating a canvas 281
Creating the face detector 281
Detecting the faces 282
Drawing rectangles on the faces 282
Results 283
Summary 286
Other Books You May Enjoy 287
Index 290

[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."

— Dr. APJ Abdul Kalam

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

Who this book is for


This book will be useful for anyone who is starting fresh on Android application
development. Learning Android app development has never been this exciting, as we have
more than one choice language being officially supported by Google. This book covers all
the details required for Android app development using Kotlin, one step at a time, and
enables readers to experience the journey as they make progress.

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.

What this book covers


The objective of the book has been to ensure that both advanced Android app developers
using Java and those starting to learn Android app development enjoy reading the book.

Chapter 1, Setting Up for Android Development, provides step-by-step information on setting


up a system for Android app development, listing all details required to get started.

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 7, Developing Your Location-Based Alarm, discusses the fundamentals of Google's


location-based services, using the Google Map API, and customizing a marker on map.

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.

To get the most out of this book


Having knowledge of object-oriented programming and the Android activity lifecycle will
be useful, but is not mandatory.

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

Download the example code files


You can download the example code files for this book from your account at
www.packtpub.com. If you purchased this book elsewhere, you can visit
www.packtpub.com/support and register to have the files emailed directly to you.

You can download the code files by following these steps:

1. Log in or register at www.packtpub.com.


2. Select the SUPPORT tab.
3. Click on Code Downloads & Errata.
4. Enter the name of the book in the Search box and follow the onscreen
instructions.

Once the file is downloaded, please make sure that you unzip or extract the folder using the
latest version of:

WinRAR/7-Zip for Windows


Zipeg/iZip/UnRarX for Mac
7-Zip/PeaZip for Linux

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

A block of code is set as follows:


<dimen name="board_padding">16dp</dimen>
<dimen name="cell_margin">2dp</dimen>
<dimen name="large_text">64sp</dimen>

Any command-line input or output is written as follows:


brew cask install fastlane

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

Warnings or important notes appear like this.

Tips and tricks appear like this.

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!

For more information about Packt, please visit packtpub.com.

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

In this chapter, we'll take a look at:

What makes Kotlin great for Android development


What you need to be ready for Android development

Why develop Android with Kotlin?


Of all the JVM languages, Kotlin is the only one that offers a lot more to Android
developers. Kotlin is the only JVM language, other than Java, which offers integrations with
Android Studio.

Let's take a look at some of Kotlin's amazing features.


Setting Up for Android Development Chapter 1

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.

Student POJO in Java:


public class Student {
private String name;
private String id;

public String getName() {


return name;
}

public void setName(String name) {


this.name = name;
}

public String getId() {


return id;
}

public void setId(String id) {


this.id = id;
}
}

Student POJO in Kotlin:


class Student() {
var name:String
var id:String
}

As you can see, there's way less Kotlin code for the same functionality.

[8]
Setting Up for Android Development Chapter 1

Say bye bye to the NullPointerException


One of the major pain points with using Java and a number of other languages has to do
with accessing a null reference. This can result in your application crashing without
showing the user an adequate error message. If you're a Java developer, I'm pretty sure
you're well acquainted with the almighty NullPointerException. One of the most
amazing things about Kotlin is null safety.

With Kotlin, a NullPointerException can only be caused by one of the following:

An external Java code


An explicit call to throw the NullPointerException
Usage of the !! operator (we'll learn more about this operator later)
Data inconsistency regarding initialization

How cool is that?

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.

Setting up your environment


Before beginning your Android development journey, there are number things you have to
do to make your machine Android developer-ready. We'll go through them in this section.

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 Windows Start menu


2. Under the Java Program listing, select About Java
3. A popup will show, with details about the version of Java on the machine:

On a Mac or any other Linux machine:

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:

Installing the JDK


1. Open your browser and go to the Java
website: http://www.oracle.com/technetwork/java/javase/downloads/index.
html
2. Under the Downloads tab, click on the Download button under the JDK, 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).

Installing Android Studio


Go over to the Android Studio page, https:/​/​developer.​android.​com/​sdk/​installing/
studio.​html, and click the DOWNLOAD ANDROID STUDIO button:

[ 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 name of the button is different depending on the operating system


you're using.

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

Getting Android Studio ready


On the Complete Installation screen, make sure the I do not have a previous version of
Studio or I do not want to import my settings option is selected, and click the OK button:

On the Welcome screen, click Next to move to the Install Type screen:

[ 14 ]
Setting Up for Android Development Chapter 1

Then, select the Standard option and click Next to continue:

[ 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

Creating your first Android project


On the Welcome to Android Studio screen, click Start a new Android Studio project:

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

Android tries to make this decision easier by providing the following:

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

Android also provides support libraries to help with backward compatibility of


certain new features added in newer SDK versions. Each support library is
backward compatible to a specific API Level. Support libraries are usually named
based on the API level with which they're backward compatible with. An
example is appcompat-v7, which provides backward compatibility to API Level
7.

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

Building your project


After clicking the Finish button, Android Studio generates and configures the project in the
background for you. One of the background processes Android Studio performs is
configuring Gradle.

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:

The Android section specifies all Android-specific configurations, such as:

compileSdkVersion: Specifies the Android API level the app should be


compiled with.
buildToolsVersion: Specifies the build tool version your app should be built
with.
applicationId: This is used to uniquely identify the application when
publishing to the Play Store. As you may have noticed, it is currently the same as
the package name you specified when creating the app. The applicationId
defaults to the package name on creation, but that doesn't mean you can't make
them different. You can. Just remember, you shouldn't change the
applicationId again after you publish the first version of the app. The package
name can be found in the app's Manifest file.

[ 24 ]
Setting Up for Android Development Chapter 1

minSdkVersion: As specified earlier, this specifies the minimum API level


required to run the app.

targetSdkVersion: Specifies the API level used to test your app.

versionCode: Specifies the version number of your app. This should be changed
for every new version before publishing.

versionName: Specifies a user-friendly version name for your app.

The Dependencies section specifies dependencies needed to build your app.

Parts of an Android project


We will have a look at the different parts of our project. The screenshot depicts our project:

[ 25 ]
Setting Up for Android Development Chapter 1

Let's take a further look at the different parts of our project:

The manifests/AndroidManifest.xml: Specifies important details about your


app required by the Android system to run the app. Part of these details are:
The package name
Describing the components of the app, including the activities,
services, and many more
Declaring the permissions required by your app
The res directory: Contains application resources such as images, xml layouts,
colors, dimensions, and string resources:
The res/layout directory: Contains xml layouts that define the
app's User Interface (UI)
The res/menu directory: Contains layouts that define the content
of the app's menus
The res/values directory: Contains resources such as colors
(res/values/colors.xml) and strings
(res/values/strings.xml)
And, your Java and/or Kotlin source files

Running your app


Android gives you the ability to run your app on an actual device or a virtual one even
before publishing it on the Google Play Store.

The Android emulator


The Android SDK comes with a virtual mobile device that runs on your computer and
makes use of its resources. This virtual mobile device is called the emulator. The emulator
is basically a configurable mobile device. You can configure its RAM size, screen size, and
so on. You can also run more than one emulator. This is most helpful when you want to test
your app on different device configurations (such as screen sizes and Android versions) but
can't afford to get actual ones.

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

Creating an Android emulator


An Android emulator can be created from the Android Virtual Device (AVD) Manager.
You can start the AVD Manager by clicking on its icon on the Android Studio toolbar, as
shown in the following screenshot:

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:

Running from an emulator


Running your app from an emulator is pretty easy. Click on the play icon on the Android
Studio toolbar, as shown in the following screenshot:

[ 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:

Running on an actual device


To run your app on an actual device, you can build and copy the APK onto the device and
run it from there. To do this, Android requires that the device is enabled to allow the
installation of apps from unknown sources. To do this, perform the following steps:

1. Open the Settings app on your device.


2. Select Security.
3. Look for and turn on the Unknown Sources option.

[ 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:

1. Open the Settings app on your device.


2. Scroll down and select About phone.
3. On the Phone status screen, scroll down and tap Build number multiple times
until you see a toast that says You're now a developer!
4. Go back to the Settings screen. You should now see a Developer options entry.
5. Select Developer options.
6. On the Developer options screen, turn on the switch at the top of the screen. If
it's off, you'll be prompted with an Allow development settings? dialog. Click
OK to confirm.
7. Scroll down and turn on USB debugging. You'll be prompted with an Allow
USB debugging? dialog. Click OK to confirm.
8. Next, connect your device to your computer via the USB.
9. You'll be prompted with another Allow USB debugging? dialog that has your
computer's RSA key fingerprint. Check the Always allow from this computer
option, and click OK to confirm.

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

In the process, we will learn how to:

Download and install the Kotlin plugin in Android Studio


Configure Kotlin in an Android project
Reference Java code in a Kotlin class, and vice versa
Convert a Java class to a Kotlin class

Installing the Kotlin plugin


To use Kotlin in your project, you have to first install the Kotlin plugin in Android Studio:

1. Select Android Studio | Preferences and select Plugins on the Preferences


window:
Configuring Your Environment for Kotlin Chapter 2

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

Making our project Kotlin-ready


To be able to start adding Kotlin code to our project, we first have to configure our project
to support Kotlin.

1. First, select Tools | Kotlin | Configure Kotlin in Project:

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

It is advisable to leave the version that is selected by default, since this is


usually the latest version.

This will cause a number of changes in the build.gradle files in your project:

In the project's build.gradle(Project:TicTacToe) file, the following


changes are applied:
The version of the Kotlin plugin in use in the project is declared
The Kotlin Gradle plugin is declared as part of the project's
classpath dependencies:

And, in the app module's build.gradle(Module:app) file, the following


changes are applied:
The kotlin-android plugin is applied to the module

[ 45 ]
Configuring Your Environment for Kotlin Chapter 2

The Kotlin Standard library is declared as a compile time


dependency for the app module:

Click on Sync Now to build the project

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.

Kotlin alongside Java?


One of the amazing things about Kotlin is its ability to reside and work with Java in the
same project.

[ 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

The new class looks like this:


package com.packtpub.eunice.tictactoe

class HelloKotlin {
}

The default visibility modifier in Kotlin is public, so there's no need to


specify the public modifier like you would in a Java class.

Let's add the following method to our new Kotlin class:

fun displayMessage(view: View) {


Snackbar.make(view, "Hello Kotlin!!",
Snackbar.LENGTH_LONG).setAction("Action", null).show()
}

The previous method takes an Android view (android.view.View) as a parameter and


passes it along with a message to the Snackbar make() method to display the message.

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();

With the following:


new HelloKotlin().diplayMessage(view);

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

Build and run your app:

Yes, it's that easy.

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

YourMainActivity.javaclass should now look like this:

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {


super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val toolbar = findViewById(R.id.toolbar) as Toolbar
setSupportActionBar(toolbar)

val fab = findViewById(R.id.fab) as FloatingActionButton


fab.setOnClickListener { view ->
HelloKotlin().displayKotlinMessage(view) }
}

override fun onCreateOptionsMenu(menu: Menu): Boolean {


// Inflate the menu; this adds items to the action bar if it is
//present.
menuInflater.inflate(R.menu.menu_main, menu)
return true
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {


// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
val id = item.itemId

return if (id == R.id.action_settings) {


true
} else super.onOptionsItemSelected(item)

}
}

You will also notice that the extension of the file has also changed to .kt.

[ 51 ]
Configuring Your Environment for Kotlin Chapter 2

Once again, build and run your app:

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.

By the end of the chapter, we will have:

Examined the current UI of the app


Designed the UI for the game
Learned about basic types in Kotlin

The user interface


In Android, the code for the app UI is written in XML and stored in layout files. Let's take a
look at the default UI that was created when we created our project.
Data Types, Variables, and Constants Chapter 3

Open res/layout/activity_main.xml. Make sure Text is selected at the bottom of the


screen. Android Studio should show you the XML code for the UI with a preview on the
right-hand side:

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:

1. The parent element is a CoordinatorLayout. The CoordinatorLayout was


introduced in Android 5.0 as part of the design library. It provides better control
over touch events between its child views. We've already seen this functionality
at work with the way the SnackBar appears under the
FloatingActionButton (instead of covering it) when the button is clicked.

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

Building our game UI


Our TicTacToe game screen will consist of the game board (which is a 3x3 grid), a
TextView displaying whose turn it is, and a FloatingActionButton used to restart the
game.

We will use a TableLayout to design the game board.


Open the res/layout/content_main.xml file and replace the TextView declaration
with the TableLayout declaration, as shown in the following:
<TableLayout
android:id="@+id/table_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:gravity="center">

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

A number of things should be noted here:

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:

<!--Table Row Attributes-->


<style name="TableRow">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:gravity">center_horizontal</item>
<item name="android:background">@android:color/black</item>
</style>

<!--General Cell Attributes-->


<style name="Cell">
<item name="android:layout_width">100dp</item>
<item name="android:layout_height">100dp</item>
<item name="android:gravity">center</item>
<item name="android:background">@android:color/white</item>
<item name="android:layout_marginTop">@dimen/cell_margin</item>
<item name="android:layout_marginBottom">@dimen/cell_margin</item>
<item name="android:textSize">@dimen/large_text</item>
<item name="android:textColor">@android:color/black</item>
<item name="android:clickable">true</item>

</style>

<!--Custom Left Cell Attributes-->


<style name="Cell.Left">
<item name="android:layout_column">0</item>
<item name="android:layout_marginRight">@dimen/cell_margin</item>
</style>

<!--Custom Middle Cell Attributes-->


<style name="Cell.Middle">
<item name="android:layout_column">1</item>
<item name="android:layout_marginRight">@dimen/cell_margin</item>
<item name="android:layout_marginLeft">@dimen/cell_margin</item>
</style>

<!--Custom Right Cell Attributes-->


<style name="Cell.Right">
<item name="android:layout_column">2</item>
<item name="android:layout_marginLeft">@dimen/cell_margin</item>
</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>

Then, open the res/layout/content_main.xml file and replace the TableRow


declaration with the following:
<TableRow
android:id="@+id/r0"
style="@style/TableRow">
<TextView
style="@style/Cell.Left"
android:layout_marginTop="0dp"/>
<TextView
style="@style/Cell.Middle"
android:layout_marginTop="0dp"/>
<TextView
style="@style/Cell.Right"
android:layout_marginTop="0dp"/>
</TableRow>
<TableRow
android:id="@+id/r1"
style="@style/TableRow">
<TextView
style="@style/Cell.Left"/>
<TextView
style="@style/Cell.Middle"/>
<TextView
style="@style/Cell.Right"/>
</TableRow>

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

Build and run to view your progress so far:

[ 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"

With the following:


app:srcCompat="@android:drawable/ic_input_add"
app:backgroundTint="@color/colorPrimary"

Once again, build and run:

That's it. We're done with the UI design.

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

Variables and constants


You declare a variable using the var keyword, and declare constants with the val
keyword. When declaring a variable or constant, its type doesn't have to be explicitly
defined. The type will be inferred from context. A val can only be initialized once. A
variable or constant can only be assigned a null value if it's explicitly declared as a nullable
type. You declare a nullable type by appending a ? to the end of the type:
var a: String = "Hello"
var b = "Hello"

val c = "Constant"
var d: String? = null // nullable String

b = null // will not compile

b = 0 // will not compile


c = "changed" // will not compile

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.

By the end of the chapter, we will have:

Learned about classes and objects in Kotlin


Worked on part of the logic for the game

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 class keyword


The name of the class
The header
The body of the class enclosed in curly braces

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.

As an example, let's add a constructor to the HelloKotlin class:


import kotlinx.android.synthetic.main.activity_main.*

class HelloKotlin constructor(message: String) {

fun displayKotlinMessage(view: View) {


Snackbar.make(view, "Hello Kotlin!!",
Snackbar.LENGTH_LONG).setAction("Action", null).show()
}
}

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

fun displayKotlinMessage(view: View) {


Snackbar.make(view, "Hello Kotlin!!",
Snackbar.LENGTH_LONG).setAction("Action", null).show()
}

In Kotlin, secondary constructors have to call the primary constructor. Let's take a look at
the code:
class HelloKotlin (message: String) {

constructor(): this("Hello Kotlin!!")


fun displayKotlinMessage(view: View) {
Snackbar.make(view, "Hello Kotlin!!",
Snackbar.LENGTH_LONG).setAction("Action", null).show()
}
}

[ 65 ]
Classes and Objects Chapter 4

A few things to note about the secondary constructor:

It does not take any parameters.


It calls the primary constructor with a default message.
It does not make use of the curly braces. This is because it has no body and
therefore has no use for the curly braces. If we add a body, we'll be required to
make use of the curly braces.

What if the displayKotlinMessage() method wants to make use of the message


parameter passed in the constructor?

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

private var msg = message

constructor(): this("Hello Kotlin!!")

fun displayKotlinMessage(view: View) {


Snackbar.make(view, msg,
Snackbar.LENGTH_LONG).setAction("Action", null).show()
}
}

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!!")

fun displayKotlinMessage(view: View) {


Snackbar.make(view, message,
Snackbar.LENGTH_LONG).setAction("Action", null).show()
}
}

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

We'll replace it with an initialization that passes a message as well:


HelloKotlin("Get ready for a fun game of Tic Tac
Toe").displayKotlinMessage(view)

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 {

private char[][] gameBoard;


private char winner;

public Game(char[][] gameBoard, char winner) {


setGameBoard(gameBoard);
setWinner(winner);
}

public char[][] getGameBoard() {


return gameBoard;
}

public void setGameBoard(char[][] gameBoard) {


this.gameBoard = gameBoard;
}

public char getWinner() {


return winner;
}

public void setWinner(char winner) {


this.winner = winner;
}
}

In Kotlin, this is greatly simplified to:


data class Game(var gameBoard: Array<CharArray>, var winner: Char)

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

var anna = Student("Anna", 5, 1) // 2


var joseph = anna.copy("Joseph", studentId = 2) // 3

In the preceding code snippet:

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

tableLayout = findViewById(R.id.table_layout) as TableLayout // 2

startNewGame(true)

Add the following methods to the MainActivity class:


private fun startNewGame(setClickListener: Boolean) {
turn = 'X'
turnTextView?.text =
String.format(resources.getString(R.string.turn), turn)
for (i in 0 until gameBoard.size) {
for (j in 0 until gameBoard[i].size) {
gameBoard[i][j] = ' '
val cell = (tableLayout?.getChildAt(i) as
TableRow).getChildAt(j) as TextView
cell.text = ""
if (setClickListener) {

[ 69 ]
Classes and Objects Chapter 4

}
}
}
}

private fun cellClickListener(row: Int, column: Int) {


gameBoard[row][column] = turn
((tableLayout?.getChildAt(row) as TableRow).getChildAt(column) as
TextView).text = turn.toString()
turn = if ('X' == turn) 'O' else 'X'
turnTextView?.text = String.format(resources.getString(R.string.turn),
turn)
}

1. In one and two, we initialize turnTextView and tableLayout with their


corresponding views in the XML layout.
2. In startNewGame():
We re-initialize turn
We set turnTextView to show the value of turn
We reset all the values of gameBoard
We reset all the cells of the tableLayout to an empty string
3. In cellClickListener():
We set the value of turn to a specific element of gameBoard based on
the parameters passed to cellClickListener()
We also change the value of the corresponding cell on the
tableLayout to turn
We change the value of turn to the next player, depending on the
previous value of turn
We change the value shown on turnTextView to the new value of
turn

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.

In Kotlin, this is done using Object Expressions.

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

Kotlin allows us to further simplify the previous lines of code. We'll


discuss this in Chapter 6, Functions and Lambdas, when we talk about
Lambdas.

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.

By the end of this chapter, we will have learned about:

Non-nullable and nullable types


The safe call operator
The Elvis operator
The !! operator
The safe and unsafe cast operators

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:

An external Java code


An explicit call to throw NullPointerException
Usage of the !! operator (we'll learn more about this operator later)
Data inconsistency regarding initialization

So how does Kotlin ensure this?


Type Checks and Null Safety Chapter 5

Nullable and non-nullable types


A nullable type is a reference that is allowed to hold a null value while a non-nullable
type is a reference that cannot hold a null value.

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

name = null // will not compile


gender = null // will compile

print("Length of name is ${name.length}") // will compile

print("Length of gender is ${gender.length}") // will not compile

There are a number of things to take note of in the previous code:

name cannot be assigned a null value because it is a non-nullable type


gender, on the other hand, can be assigned a null value because it is declared
as a nullable type
Accessing a member method or property of gender cannot be done in the same
way as accessing one of the name member methods or properties

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:

Using the safe call operator (?.)


Using the Elvis operator (?:)
Using the !! operator
Performing 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.

Safe call operator


Another way of accessing a method or property of a nullable type is by using the safe call
operator:
val len = gender?.length
print("Length of gender is $len")

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.

The Elvis operator


The Elvis operator is similar to the ternary if operator in Java. It's a way of simplifying an
if-else statement. For example:

val len = if (gender != null) gender.length else 0

The code can be simplified to:


val len = gender?.length ?: 0

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

This will result in a null pointer exception if gender is null.

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.

In the process, we will:

Learn about functions


Learn about higher-order functions and how to use them
Learn about lambdas and how to use them

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)

fun addStudent(name: String, age:Int, classRoomNo: Int = 1, studentId: Int)


: Student {

return Student(name, classRoomNo, studentId, age)


}

var anna = addStudent("Anna", 18, 2, 1)


var joseph = addStudent(name = "Joseph", age = 19, studentId = 2)

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

Higher–order functions and lambdas


The term higher-order function refers to a function that either takes another function as a
parameter or returns a function or both. For example:
// 1
fun logStudent(name: String, age:Int, createStudent:(String, Int) ->
Student) {
Log.d("student creation", "About to create student with name $name")
val student = createStudent(name, age)
Log.d("student creation", "Student created with name ${student.name}
and age ${student.age}")
}

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

The createStudent function is not declared but passed as an expression to the


logStudent() function. This is called a lambda expression.

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

Replace them with this line of code:


cell.setOnClickListener { 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.

A Single Abstract Method (SAM), as it is often called, refers to the


functional method in an interface. The interface typically contains only
one abstract method that is known as SAM or a functional method.

Now, build and run to see the state of the app:

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.

Implementing a game status check


In this section, we will work on functions that will help us figure out the winner of our
game.

Start by adding the following function to the MainActivity class:


private fun isBoardFull(gameBoard:Array<CharArray>): Boolean {
for (i in 0 until gameBoard.size) {
for (j in 0 until gameBoard[i].size) {
if(gameBoard[i][j] == ' ') {
return false
}
}
}
return true
}

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.

Next, add the isWinner() method:


private fun isWinner(gameBoard:Array<CharArray>, w: Char): Boolean {
for (i in 0 until gameBoard.size) {
if (gameBoard[i][0] == w && gameBoard[i][1] == w &&
gameBoard[i][2] == w) {
return true
}

if (gameBoard[0][i] == w && gameBoard[1][i] == w &&


gameBoard[2][i] == w) {
return true
}
}
if ((gameBoard[0][0] == w && gameBoard[1][1] == w && gameBoard[2]
[2] == w) ||
(gameBoard[0][2] == w && gameBoard[1][1] == w &&

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

Now add the checkGameStatus() function:


private fun checkGameStatus() {
var state: String? = null
if(isWinner(gameBoard, 'X')) {
state = String.format(resources.getString(R.string.winner), 'X')
} else if (isWinner(gameBoard, 'O')) {
state = String.format(resources.getString(R.string.winner), 'O')
} else {
if (isBoardFull(gameBoard)) {
state = resources.getString(R.string.draw)
}
}

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.

Next, add a call to checkGameStatus() at the end of the


cellClickListener() function.

[ 83 ]
Functions and Lambdas Chapter 6

Build and run:

Lastly, implement the functionality for the FloatingActionButton. In the onCreate()


function, replace the following:
fab.setOnClickListener { view -> HelloKotlin("Get ready for a fun game of
Tic Tac Toe").displayKotlinMessage(view) }

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 map based on Android activity


Usage of Google Maps in Android applications
The process for registering and obtaining the key required for Google Maps
activity
Developing a screen for the user to provide input to the app
How to complete our app and make a workable model in the next chapter by
adding the alarm feature and using Google Location Services

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

On the following screen, enter the details shown here:

Application name: LocationAlarm.


Company domain: Android Studio uses the domain name to generate the
package name for the application we develop. The package ensures our app gets
a unique identifier in the Play Store. Generally, the package name will be the
reverse of the domain name, for example, it would be
com.natarajan.locationalarm in this case.
Project location: The path where we would like to have the project code
developed and saved. You can chose and select the path where you are
developing the app. As we are developing our app using Kotlin, we must select
Include Kotlin support:

[ 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:

APIs they provide


Form factors

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.

The Help me choose option would help you to understand the


distribution of Android devices across the globe grouped by the android
version (API) they run.

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

Click on Google Maps Activity and click Next:

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

Once these entries are made, click on the Finish button:

On clicking the button, we will see the Building 'LocationAlarm' Gradle project info
screen.

Generation of a Google Maps API key


As soon as the build process is complete, we will see the following resources file screen
being opened by default and displayed by Android Studio:

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

The key that is generated should be replaced by the placeholder


YOUR_KEY_HERE mentioned in the file:
<resources>
<!--
TODO: Before you run your application, you need a Google Maps API key.

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

Alternatively, follow the directions here:


https://developers.google.com/maps/documentation/android/start#get-key

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.

https:/​/​console.​developers.​google.​com requires the user to sign in


with their Google ID. Once they sign in, the option for creating the project
and enabling the API will be presented.

Select and copy the link in full (https:/​/​console.​developers.​google.​com/​flows/


enableapi?​apiid=​maps_​android_​backend​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;com.​natarajan.
locationalarm) and enter it into your favorite browser:

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

We will see couple of options:

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_​ANDROID​r=
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.

Running the app


To run the app, go to Run | Run app or click on the play button.

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:

Understanding the code


We could successfully run the app, and now it is time to delve deeper into the code and
understand how it works.

Let's start with the MapsActivity.kt Kotlin class file.

[ 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 {

private lateinit var mMap: GoogleMap

override fun onCreate(savedInstanceState: Bundle?) {


super.onCreate(savedInstanceState)
setContentView(R.layout.activity_maps)
// Obtain the SupportMapFragment and get notified when the map is ready to
be used.
val mapFragment = supportFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
}

Customizing the code


The default code generated shows the market in Sydney. The method onMapReady shown
here gets called when the map is ready and loaded and displays a marker. The location is
found based on the LatLng value mentioned:
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
// Add a marker in Sydney and move the camera
val sydney= LatLng(-33.852,151.211)
mMap.addMarker(MarkerOptions().position(sydney).title("Marker in Sydney"))
mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney))
}

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.

Finding the Lat and Lng of a place


Finding the latitude and longitude of a place can be done easily using Google Maps in the
browser. For our purpose, we will launch Google Maps in our favorite browser.

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.

In the onMapReady method, let us make the following changes:

Rename the Sydney variable to chennai


Change the Lat and Lng from Sydney's to Chennai's
Change Marker text to read Marker in Chennai
Change the newLatLng to take chennai as the input value
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
// Add a marker in Chennai and move the camera
val chennai = LatLng(13.07975, 80.1798347)
//val chennai = LatLng(-34.0, 151.0)
mMap.addMarker(MarkerOptions().position(chennai).title("Marker in
Chennai"))
mMap.moveCamera(CameraUpdateFactory.newLatLng(chennai))
}

[ 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

The XML layout


We have looked at the Kotlin class in detail and also ways to customize the Lat and Lng
input.

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>

In the MapsActivity Kotlin class, we can define a method called


onClickSettingsButton and, on invocation of the same, we fire up another activity
called SETTINGACTVITY as shown:
fun onClickSettingsButton(view: View) {
val intent = Intent("android.intent.action.SETTINGACTIVITY")
startActivity(intent)
}

[ 102 ]
Developing Your Location-Based Alarm Chapter 7

Developing a screen for user input


When clicking the Settings button, our app will take the user to a screen where they can
enter the Lat and Lng values for the new location that user wants the alarm to be set for.

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

The class SettingsActivity extends AppCompatActivity and contains a couple of edit


text elements and button element initialized. The variables are identified and set to the
correct resources from the resources file by their IDs. The activity loads the
settings_activity XML as and when the activity is invoked and loaded.

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() {

public override fun onCreate(savedInstanceState: Bundle?) {


super.onCreate(savedInstanceState)
setContentView(R.layout.settings_activity)

}
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:

The AndroidManifest file


A manifest file is one of the most important files of the project. In this file, we have to list all
the activities we intend to use in our app and also provide details about the API keys we
use for the Google Maps API.

[ 105 ]
Developing Your Location-Based Alarm Chapter 7

We have the following important pointers in the manifest file:

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.

ACCESS_COARSE_LOCATION is the permission-enabled app to get the


location details provided by NETWORK_PROVIDER.
The ACCESS_FINE_LOCATION permission enables the app to get the
location details provided by NETWORK_PROVIDER and GPS_PROVIDER.

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:

<?xml version="1.0" encoding="utf-8"?>


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.natarajan.locationalarm">
<!--
The ACCESS_COARSE/FINE_LOCATION permissions are not required to
use
Google Maps Android API v2, but you must specify either coarse or
fine
location permissions for the 'MyLocation' functionality.
-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<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

sign the APK for publishing.


You can define the keys for the debug and release targets in
src/debug/ and src/release/.
-->
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="@string/google_maps_key" />

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

In this chapter, we will learn how to:

Use the Google Location API


Receive updates on the user's current location
Utilize user shared preferences to persist the user's location of interest
Match and display the alert when the user reaches the location of interest

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

Integrating shared preferences


Users of our app will enter the desired location for which they want the alarm to be
triggered. Users enter the Lat and Lng of the location and for us to compare this with the
current location users are in, we need to store the details entered by them as the desired
location.

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

To read and display from shared preferences:


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

The user is alerted about the alarm being set:

[ 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();
}

private fun isLocationEnabled(): Boolean {


locationManager = getSystemService(Context.LOCATION_SERVICE) as
LocationManager
return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
|| locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
}

[ 114 ]
Working with Google's Location Services Chapter 8

[ 115 ]
Working with Google's Location Services Chapter 8

Integration of the location API


We will integrate the location API into our app to receive location updates. Integration of
the location API involves some changes in the code. Let us discuss them in detail.

Classes and variables


Integration of the Google location API requires implementation of GoogleAPIClient,
ConnectionCallbacks, and connection failed listeners by MapsActivity. Let us proceed
and make the changes to the MapsActivity. Earlier, we had the MapsActivity extend
AppCompatActivity and implement the OnMapReadyCallback interface. Now, since we
need to use the location API's we have to also implement the GoogleAPIClient,
ConnectionCallbacks, and onConnectionFailedListener as shown here:

class MapsActivity : AppCompatActivity(), OnMapReadyCallback


,GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener,
com.google.android.gms.location.LocationListener {

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

private var AlarmLat: Double? = null


private var AlarmLong: Double? = null
private var UserLat: Double? = null
private var UserLong: Double? = null

//location variables

private val TAG = "MapsActivity"


private lateinit var mGoogleApiClient: GoogleApiClient
private var mLocationManager: LocationManager? = null
lateinit var mLocation: Location
private var mLocationRequest: LocationRequest? = null

[ 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():

override fun onCreate(savedInstanceState: Bundle?) {


super.onCreate(savedInstanceState)
setContentView(R.layout.activity_maps)
// Obtain the SupportMapFragment and get notified when the map
is ready to be used.
val mapFragment = supportFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)

mGoogleApiClient = GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build()

mLocationManager =
this.getSystemService(Context.LOCATION_SERVICE) as
LocationManager
checkLocation()
}

The Google API client


Declaring, initializing, and managing the connection options of the Google API client is to
be handled along the lifecyle events of Android app. We also need to get the location
updates once the connection is established.

[ 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 the onStop method, we check whether the mGoogleAPIClient instance is connected


and if it is, we call the disconnect method:
override fun onStop() {
super.onStop();
if (mGoogleApiClient.isConnected()) {
mGoogleApiClient.disconnect();
}
}

In case something goes wrong and the connection gets suspended, we request a reconnect
in the onConnectionSuspended method:
override fun onConnectionSuspended(p0: Int) {

Log.i(TAG, "Connection Suspended");


mGoogleApiClient.connect();
}

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());
}

In the onConnected method, we first check for the permission to ACCESS


_FINE_LOCATION and that the ACCESS_COARSE_LOCATION is indeed present in the
manifest file.

[ 118 ]
Working with Google's Location Services Chapter 8

We call the startLocationUpdates() method once we ensure the permissions are


granted:
override fun onConnected(p0: Bundle?) {

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();

The fusedLocationProviderClient provides the current location details and assigns


them to the mLocation variable:
var fusedLocationProviderClient :
FusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(this);
fusedLocationProviderClient .getLastLocation()
.addOnSuccessListener(this, OnSuccessListener<Location> {
location ->
if (location != null) {
mLocation = location;
} }) }

The startLocationUpdates creates the LocationRequest instance and provides the


parameters we set for the updates. We also call the FusedLocationAPI and request the
location updates:
protected fun startLocationUpdates() {
// Create the location request
mLocationRequest = LocationRequest.create()
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
.setInterval(UPDATE_INTERVAL)
.setFastestInterval(FASTEST_INTERVAL);
// Request location updates
if (ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION) !=
PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_COARSE_LOCATION) !=
PackageManager.PERMISSION_GRANTED) {
return;

[ 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

val AlarmLat1 = AlarmLat


val AlarmLong1 = AlarmLong
val UserLat1 = UserLat
val UserLong1 = UserLong

if(AlarmLat1 != null && AlarmLong1 != null && UserLat1 != null


&& UserLong1 != null){

checkAlarmLocation(AlarmLat1,AlarmLong1,UserLat1,UserLong1)
}
}

[ 120 ]
Working with Google's Location Services Chapter 8

Matching the location


The startLocationUpdates method provides the current latitude and longitude of the
user continuously as per the interval we have set. We need to use the latitude and longitude
information obtained and need to compare this with the latitude and longitude entered by
the user for the alarm.

[ 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) {

Toast.makeText(this,"Check Alarm Called" + AlarmLat + "," + AlarmLong +


"," + UserLat + "," + UserLong,Toast.LENGTH_LONG ).show()

var LatAlarm: Double


var LongAlarm: Double
var LatUser: Double
var LongUser: Double

LatAlarm = Math.round(AlarmLat * 100.0) / 100.0;


LongAlarm = Math.round(AlarmLong * 100.0) / 100.0;

LatUser = Math.round(UserLat * 100.0) / 100.0;


LongUser = Math.round(UserLong * 100.0) / 100.0;

Toast.makeText(this,"Check Alarm Called" + LatAlarm + "," + LongAlarm + ","


+ LatUser + "," + LongUser,Toast.LENGTH_LONG ).show()

if (LatAlarm == LatUser && LongAlarm == LongUser) {


Toast.makeText(this, "User has reached the area for which
alarm has been set", Toast.LENGTH_LONG).show();
}
}

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

We will be covering the following:

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.

Volley is quite good for lightweight network operations and makes


information exchange easier. For huge downloads and streaming
operations, developers should use Download Manager.

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.

Sync adapter implementation typically contains a stub authenticator, a


stub content provider, and a sync adapter.

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

User interface – XML


The default XML code that gets generated would contain a TextView. We need to tweak
the XML code a bit and replace the TextView with an ImageView. This ImageView will
provide the placeholder for displaying the image that would be fetched from the URL using
Picasso.

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

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {


super.onCreate(savedInstanceState)
setContentView(R.layout.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.*

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {


super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(imageView);
}
}

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.

The modified build.gradle dependencies should look like the following:


dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-
jre7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-
layout:1.1.0'
implementation 'com.squareup.picasso:picasso:2.71828'
implementation 'com.github.bumptech.glide:glide:4.7.1'
kapt "com.github.bumptech.glide:compiler:4.7.1"

[ 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'
}

In the project level build.gradle file, we need to add mavenCentral() in the


repositories section, as shown:

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

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {


super.onCreate(savedInstanceState)
setContentView(R.layout.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.

In the process, we will learn the following:

How to build a user interface in Android Studio


Working with ListViews
How to work with Dialogs
Developing a Simple To-Do List App Chapter 10

Creating the project


Let's start by creating a new project in Android Studio, with the name TodoList. Select Add
No Activity on the Add an Activity to Mobile screen:

[ 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:

Using the Android Studio Layout Editor


Writing the XML code by hand

Let's go ahead and start designing our TodoList app.

[ 142 ]
Developing a Simple To-Do List App Chapter 10

Using the Android Studio layout editor


Android Studio provides a layout editor, which gives you the ability to build your layouts
by dragging widgets into the visual editor. This will auto-generate the XML code for your
UI.

Open the content_main.xml file.

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:

Set the ID as list_view


Change both the layout_width and layout_height attributes to match_parent

[ 144 ]
Developing a Simple To-Do List App Chapter 10

If the Attributes editor is not showing; select View | Tool Windows |


Attributes to display it.

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:

<?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"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="com.packtpub.eunice.todolist.MainActivity"
tools:showIn="@layout/activity_main">

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

A layout always has a root element. In the preceding code,


ConstraintLayout is the root element.

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.

Open the activity_main.xml file:

One of the attributes of


the android.support.design.widget.FloatingActionButton is app:srcCompat.
This is used to specify the icon for the FloatingActionButton. Change its value
from @android:drawable/ic_dialog_email to @android:drawable/ic_input_add.

[ 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:

Adding functionality to the user interface


At the moment, when the user clicks on the Add button, a ticker shows at the bottom of the
screen. This is because of the piece of code in the onCreate() method that defines and sets
an OnClickListener to the FloatingActionButton:
fab.setOnClickListener { view ->
Snackbar.make(view, "Replace with your own action",
Snackbar.LENGTH_LONG)
.setAction("Action", null).show()
}

[ 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() }

Adding a new task


For adding a new task, we'll show the user an AlertDialog with an editable field.

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:

<string name="add_new_task_dialog_title">Add New Task</string>


<string name="save">Save</string>

The add_new_task_dialog_title string will be used as the title of our dialog


The save string will be used as the text of a button on the dialog

The best way to use an AlertDialog is by encapsulating it in a DialogFragment. The


DialogFragment takes away the burden of handling the dialog's life cycle events. It also
makes it easy for you to reuse the dialog in other activities.

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 {

val newTaskDialogFragment = NewTaskDialogFragment()


val args = Bundle()
args.putInt("dialog_title", title)

[ 150 ]
Developing a Simple To-Do List App Chapter 10

newTaskDialogFragment.arguments = args
return newTaskDialogFragment
}
}

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { //


5
val title = arguments.getInt("dialog_title")
val builder = AlertDialog.Builder(activity)
builder.setTitle(title)

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()
}

override fun onAttach(activity: Activity) { // 6


super.onAttach(activity)
try {
newTaskDialogListener = activity as NewTaskDialogListener
} catch (e: ClassCastException) {
throw ClassCastException(activity.toString() + " must
implement NewTaskDialogListener")
}

}
}

[ 151 ]
Developing a Simple To-Do List App Chapter 10

Let's take a closer look at what this class does:

1. The class extends the DialogFragment class.


2. It declares an interface with the name NewTaskDialogListener, which declares
two methods:
onDialogPositiveClick(dialog: DialogFragment, task:
String)
onDialogNegativeClick(dialog: DialogFragment)
3. It declares a variable of type NewTaskDialogListener.
4. It defines a method, newInstance(), in a companion object. By doing this, the
method can be accessed without having to create an instance of the
NewTaskDialogFragment class. The newInstance() method does the
following:
It takes an Int parameter named title
It creates an instance of the NewTaskDialogFragment and passes the
title as part of its arguments
It returns the new instance of the NewTaskDialogFragment
5. It overrides the onCreateDialog() method. This method does the following:
It attempts to retrieve the title argument passed
It instantiates an AlertDialog builder and assigns the retrieved title
as the dialog's title
It uses the LayoutInflater of the DialogFragment instance's parent
activity to inflate the layout we created
Then, it sets the inflated view as the dialog's view
Sets two buttons to the dialog: Save and Cancel
When the Save button is clicked, the text in the EditText will be
retrieved and passed to the newTaskDialogListener variable via the
onDialogPositiveClick() method
6. In the onAttach() method, we attempt to assign the Activity object passed to
the newTaskDialogListener variable created earlier. For this to work, the
Activity object should implement the NewTaskDialogListener interface.

[ 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 {

And, add implementations of the methods declared in the NewTaskDialogListener by


adding the following methods to the MainActivity class:
override fun onDialogPositiveClick(dialog: DialogFragment, task:String)
{
}

override fun onDialogNegativeClick(dialog: DialogFragment) {


}

In the showNewTaskUI() method, add the following lines of code:


val newFragment =
NewTaskDialogFragment.newInstance(R.string.add_new_task_dialog_title)
newFragment.show(fragmentManager, "newtask")

In the preceding lines of code, the newInstance() method in NewTaskDialogFragment is


called to generate an instance of the NewTaskDialogFragment class. The show() method
of the DialogFragment is then called to display the dialog.

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:

private var todoListItems = ArrayList<String>()

In the onDialogPositiveClick() method, place the following lines of code at the


beginning of the method definition:
todoListItems.add(task)
listAdapter?.notifyDataSetChanged()

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

Displaying data in the ListView


To make changes to a UI element in the XML layout, you need to use
the findViewById() method to retrieve the instance of the element in the
corresponding Activity of your layout. This is usually done in the onCreate() method
of the Activity.

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.

In the MainActivity class, add a new variable, of type ArrayAdapter:


private var listAdapter: ArrayAdapter<String>? = null

Add the method shown here to the class:


private fun populateListView() {
listAdapter = ArrayAdapter(this, android.R.layout.simple_list_item_1,
todoListItems)
listView?.adapter = listAdapter
}

In the preceding lines of code, we create a simple ArrayAdapter and assign it to


the listView as its Adapter.

Now, add a call to the previous method in the onCreate() method:


populateListView()

[ 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:

Updating/deleting a Todo item


What if the user made a mistake entering the new task? We need to provide a way for them
to be able to edit a list item, or completely delete that item. We can provide menu items that
are only displayed when the user clicks an item. The menu items will give the user the
opportunity to either edit or delete the item in question.

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>

The next thing we need to do is to add a menu to our UI.

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:

override fun onCreateOptionsMenu(menu: Menu): Boolean {


val inflater = menuInflater
inflater.inflate(R.menu.to_do_list_menu, menu)
val editItem = menu.findItem(R.id.edit_item)
val deleteItem = menu.findItem(R.id.delete_item)

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.

Now, we need to implement an ItemClickListener for the ListView. In


the onCreate() method, add the following lines of code:
listView?.onItemClickListener = AdapterView.OnItemClickListener { parent,
view, position, id -> showUpdateTaskUI(position) }

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)

val task = dialogView.findViewById<EditText>(R.id.task)

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

} else if (R.id.delete_item == item?.itemId) { // 2

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.

1. If the selected menu item is the edit button:


A new instance of the NewTaskDialogFragment is generated and
shown. In the call to generate the new instance, the selected task is
retrieved and passed.
2. If it is the delete button:
The selected item is removed from the todoListItems
The listAdapter is notified of the data change
The selectedItem variable is reset to -1
And, a ticker is shown notifying the user about the successful deletion

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

Replace the onDialogPositiveClick() method with the method shown here:


override fun onDialogPositiveClick(dialog: DialogFragment, task:String) {

if("newtask" == dialog.tag) {
todoListItems.add(task)
listAdapter?.notifyDataSetChanged()

Snackbar.make(fab, "Task Added Successfully",


Snackbar.LENGTH_LONG).setAction("Action", null).show()

} else if ("updatetask" == dialog.tag) {


todoListItems[selectedItem] = task

listAdapter?.notifyDataSetChanged()

selectedItem = -1

Snackbar.make(fab, "Task Updated Successfully",


Snackbar.LENGTH_LONG).setAction("Action", null).show()
}
}

In the preceding lines of code, the following applies:

1. If the dialog's tag is newtask:


The task variable is added to the todoListItems data and
the listAdapter is notified to update the ListView
A ticker is also shown to inform the user that the task has successfully
been added
2. If the dialog's tag is updatetask:
The selected item is replaced with the task variable
in the todoListItems data set and the listAdapter is notified to
update the ListView
The selectedItem variable is reset to -1
And, a ticker is also shown to inform the user that the task has been
successfully changed

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

In this chapter, we will learn the following:

The concept of databases


The different types of databases available for mobile development
How to connect to some of the different databases available

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.

A relational database is maintained using an RDBMS (Relational Database Management


System). The data is accessed and managed using a language known as SQL (Structured
Query Language). Some of the most-used RDBMSs are Oracle, MySQL, Microsoft SQL
Server, PostgreSQL, Microsoft Access, and SQLite. MySQL, PostgreSQL, and SQLite are
open sourced.

[ 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 {

const val DATABASE_VERSION = 1


const val DATABASE_NAME = "todo_list_db"

class TodoListItem: BaseColumns {


companion object {
const val TABLE_NAME = "todo_list_item"
const val COLUMN_NAME_TASK = "task_details"
const val COLUMN_NAME_DEADLINE = "task_deadline"
const val COLUMN_NAME_COMPLETED = "task_completed"
}
}

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

private val SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS " +


TodoListDBContract.TodoListItem.TABLE_NAME // 2

override fun onCreate(db: SQLiteDatabase) { // 3


db.execSQL(SQL_CREATE_ENTRIES)
}

override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion:


Int) {// 4
db.execSQL(SQL_DELETE_ENTRIES)
onCreate(db)
}

override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion:


Int) {
onUpgrade(db, oldVersion, newVersion)
}

[ 171 ]
Persisting with Databases Chapter 11

In the preceding lines of code the following applies:

SQL_CREATE_ENTRIES is a SQL query to create a table. It specifies an_id field,


which is set as the primary key of the database.

In relational databases, a table is required to have a column that uniquely


identifies each row entry. This unique column is known as the Primary
Key. Specifying a column as AUTOINCREMENT tells the RDBMS to
auto-generate a new value for this field whenever a new row is being
inserted.

SQL_DELETE_ENTRIES is a SQL query to drop the table if it exists.


In the onCreate() method, the SQL query is executed to create the table.
In onUpgrade(), the table is deleted and recreated.

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.

var taskId: Long? = null

Next, add the constructor shown as follows:


constructor(taskId:Long, taskDetails: String?, taskDeadline: String?,
completed: Boolean) : this(taskDetails, taskDeadline) {
this.taskId = taskId
this.completed = completed
}

Inserting data into the database


Open TodoListDBHelper, and add the method shown here:
fun addNewTask(task: 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)
values.put(TodoListDBContract.TodoListItem.COLUMN_NAME_COMPLETED,
task.completed)

val taskId = db.insert(TodoListDBContract.TodoListItem.TABLE_NAME,

[ 172 ]
Persisting with Databases Chapter 11

null, values); // 3
task.taskId = taskId

return task
}

Here, we perform the following:

1. We first retrieve the database in write mode.


2. Next, we create an instance of ContentValues and put in a value key mapping
of the fields in the item we want to insert.
3. Then, we invoke the insert() method on the database object, passing the table
name and the ContentValues instance to it. This returns the primary key, _id,
of the inserted item. We update the task object and return it.

Open the MainActivity class.

First, add an instance of the TodoListDBHelper class as a new field at the top of the class:
private var dbHelper: TodoListDBHelper = TodoListDBHelper(this)

And, override the onDestroy() method of AppCompatActivity:


override fun onDestroy() {
dbHelper.close()
super.onDestroy()
}

This closes the database connection when the Activity's onDestroy() method is called.

Then, in the onDialogPositiveClick() method, locate this line of code:


todoListItems.add(Task(taskDetails, ""))

Replace it with the following lines of code:


val addNewTask = dbHelper.addNewTask(Task(taskDetails, ""))
todoListItems.add(addNewTask)

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

Build and run the app:

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.

Retrieving data from the database


Open TodoListDBHelper, and add the method shown as follows:
fun retrieveTaskList(): ArrayList<Task> {
val db = this.readableDatabase // 1

val projection = arrayOf<String>(BaseColumns._ID,


TodoListDBContract.TodoListItem.COLUMN_NAME_TASK,
TodoListDBContract.TodoListItem.COLUMN_NAME_DEADLINE,
TodoListDBContract.TodoListItem.COLUMN_NAME_COMPLETED) // 2

[ 174 ]
Persisting with Databases Chapter 11

val cursor = db.query(TodoListDBContract.TodoListItem.TABLE_NAME,


projection,
null, null, null, null, null) // 3

val taskList = ArrayList<Task>()


// 4
while (cursor.moveToNext()) {
val task =
Task(cursor.getLong(cursor.getColumnIndexOrThrow(BaseColumns._ID)),
cursor.getString(cursor.getColumnIndexOrThrow(TodoListDBContract.TodoListIt
em.COLUMN_NAME_TASK)),
cursor.getString(cursor.getColumnIndexOrThrow(TodoListDBContract.TodoListIt
em.COLUMN_NAME_DEADLINE)),
cursor.getInt(cursor.getColumnIndexOrThrow(TodoListDBContract.TodoListItem.
COLUMN_NAME_COMPLETED)) == 1)
taskList.add(task)
}
cursor.close() // 5

return taskList
}

In the retrieveTaskList method, we perform the following:

1. We first retrieve the database in read-mode.


2. Next, we create an array that lists all the columns of the table we need to retrieve.
Here, if we have no need for the values of a specific column, we don't add that.
3. We then pass the table name and the column list to the query() method on the
database object. This returns a Cursor object.
4. Next, we loop through the items in the Cursor object, and create an instance of
the Task class with the attributes of each item.
5. We close the cursor and return the retrieved data

Now, open MainActivity, and add the following line of code at the beginning of
the populateListView() method:
todoListItems = dbHelper.retrieveTaskList();

Your populateListView() method should now look like this:


private fun populateListView() {
todoListItems = dbHelper.retrieveTaskList();
listAdapter = TaskListAdapter(this, todoListItems)
listView?.adapter = listAdapter
}

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

val selection = BaseColumns._ID + " = ?" // 3


val selectionArgs = arrayOf(task.taskId.toString()) // 4

db.update(TodoListDBContract.TodoListItem.TABLE_NAME, values,
selection, selectionArgs) // 5

In the updateTask() method, we perform the following:

1. We first retrieve the database in write mode.


2. Next, we create an instance of ContentValues and put in a value key mapping
of the fields we want to update. For what we are working on, we will assume an
update of all columns.
3. We specify a query for selecting which database entry to update. Our selection
query uses the _id column.
4. Then, we specify the argument for the select query which, in our case, is the
taskId of the selected Task.
5. Then, we invoke the update() method on the database object, passing the table
name, the ContentValues instance, the select query, and the selection value to
it.

In the onDialogPositiveClick() method in the MainActivity class, locate this line of


code:
dbHelper.updateTask(todoListItems[selectedItem])

And, place it right after this line of code:


todoListItems[selectedItem].taskDetails = taskDetails

[ 177 ]
Persisting with Databases Chapter 11

The onDialogPositiveClick() method should now look like this:


override fun onDialogPositiveClick(dialog: DialogFragment,
taskDetails:String) {
if("newtask" == dialog.tag) {
val addNewTask = dbHelper.addNewTask(Task(taskDetails, ""))
todoListItems.add(addNewTask)
listAdapter?.notifyDataSetChanged()

Snackbar.make(fab, "Task Added Successfully",


Snackbar.LENGTH_LONG).setAction("Action", null).show()

} else if ("updatetask" == dialog.tag) {


todoListItems[selectedItem].taskDetails = taskDetails
dbHelper.updateTask(todoListItems[selectedItem])

listAdapter?.notifyDataSetChanged()

selectedItem = -1

Snackbar.make(fab, "Task Updated Successfully",


Snackbar.LENGTH_LONG).setAction("Action", null).show()
}
}

Next, in onOptionsItemSelected(), locate the following line of code:


dbHelper.updateTask(todoListItems[selectedItem])

And, place it right after this line of code:


todoListItems[selectedItem].completed = true

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
}

The process for deletion is similar to that of updating:

1. First, retrieve the database in write mode


2. Next, specify a query for selecting which database entry to delete. Our
selection query uses the _id column

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

Replace it with this code:


val selectedTask = todoListItems[selectedItem]
todoListItems.removeAt(selectedItem)
dbHelper.deleteTask(selectedTask)

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.

NOTE: You still need some level of SQL query knowledge

There are a number of Android-compatible ORM Libraries:

ORMLite
GreenDAO
DbFlow
Room

But, in this book, we will focus on Room, which is an ORM introduced by Google.

To use Room, we first have to add its dependencies to the project.

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
}

constructor(taskId:Long, taskDetails: String?, taskDeadline: String?,


completed: Boolean) : this(taskDetails, taskDeadline) {
this.taskId = taskId
this.completed = completed
}

Here, the following applies:

@Entity specifies that Task represents a table in the database


@ColumnInfo maps the field to a database column
@PrimaryKey specifies that the field is the primary key of the table

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 {

@Query("SELECT * FROM " + TodoListDBContract.TodoListItem.TABLE_NAME)


fun retrieveTaskList(): List<Task>

@Insert
fun addNewTask(task: Task): Long

@Update
fun updateTask(task: Task)

@Delete
fun deleteTask(task: Task)

[ 182 ]
Persisting with Databases Chapter 11

As shown in the preceding code, the following applies:

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
}

That's all the setup needed to connect to the database.

To use the database, open MainActivity. First, create a field of AppDatabase type:
private var database: AppDatabase? = null

Next, instantiate the field in the onCreate() method:


database = Room.databaseBuilder(applicationContext,
AppDatabase::class.java, DATABASE_NAME).build()

Here, you specify your database class and the name of the database.

Retrieving data from the database


Room does not allow you to run database operations on the main thread, so we will use
an AsyncTask to perform the calls. Add the private class shown here to
the MainActivity class:
private class RetrieveTasksAsyncTask(private val database: AppDatabase?) :
AsyncTask<Void, Void, List<Task>>() {

override fun doInBackground(vararg params: Void): List<Task>? {


return database?.taskDao()?.retrieveTaskList()
}
}

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

Next, in the populateListView() method, locate the following line of code:


todoListItems = dbHelper.retrieveTaskList();

And, replace it with this:


todoListItems = RetrieveTasksAsyncTask(database).execute().get() as
ArrayList<Task>

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.

Open the TodoListDBContract class and increase the DATABASE_VERSION constant to 2.

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

Inserting data into the database


To add a new task, create a new AsyncTask in the MainActivity:
private class AddTaskAsyncTask(private val database: AppDatabase?, private
val newTask: Task) : AsyncTask<Void, Void, Long>() {

override fun doInBackground(vararg params: Void): Long? {


return database?.taskDao()?.addNewTask(newTask)
}
}

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, ""))

And, replace it with the following code:


var addNewTask = Task(taskDetails, "")

addNewTask.taskId = AddTaskAsyncTask(database, addNewTask).execute().get()

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>() {

override fun doInBackground(vararg params: Void): Unit? {


return database?.taskDao()?.updateTask(selectedTask)
}
}

Here, we make a call to the taskDao to insert the new task in the database in
the doInBackground() method.

Next, in the onDialogPositiveClick() method, locate the following line of code:


dbHelper.updateTask(todoListItems[selectedItem])

Replace it with this line of code:


UpdateTaskAsyncTask(database, todoListItems[selectedItem]).execute()

Further, in onOptionsItemSelected(), locate the following line of code:


dbHelper.updateTask(todoListItems[selectedItem])

And, replace it with this line of code:


UpdateTaskAsyncTask(database, todoListItems[selectedItem]).execute()

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>() {

override fun doInBackground(vararg params: Void): Unit? {


return database?.taskDao()?.deleteTask(selectedTask)
}
}

[ 188 ]
Persisting with Databases Chapter 11

Next, in onOptionsItemSelected(), locate the following line of code:


dbHelper.deleteTask(selectedTask)

Replace it with this line of code:


DeleteTaskAsyncTask(database, selectedTask).execute()

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

CouchBase is an example of a document database and Realm is an example of an object


database.

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.

Object databases, on the other hand, store their data as objects.

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:

To create and display notifications for the reminders set


An introduction to push notifications
How to use cloud services, such as Firebase and Amazon SNS, to send push
notifications, and
How to set up your app to receive and display push notifications to the user

All in all, the topics covered in this chapter include:

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.

Creating the alarm


Basically, there are four types of alarms:

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

class TimePickerFragment : DialogFragment(),


TimePickerDialog.OnTimeSetListener {

override fun onCreateDialog(savedInstanceState: Bundle): Dialog {


val c = Calendar.getInstance()
val hour = c.get(Calendar.HOUR_OF_DAY)
val minute = c.get(Calendar.MINUTE)

return TimePickerDialog(activity, this, hour, minute,


DateFormat.is24HourFormat(activity))
}

override fun onTimeSet(view: TimePicker, hourOfDay: Int, minute: Int) {


Log.d("onTimeSet", "hourOfDay: $hourOfDay minute:$minute")

Toast.makeText(activity, "Reminder set successfully",


Toast.LENGTH_LONG).show()

val intent = Intent(activity, AlarmReceiver::class.java)


intent.putExtra(ARG_TASK_DESCRIPTION, taskDescription)

val alarmIntent = PendingIntent.getBroadcast(activity, 0, intent,


0)
val alarmMgr = activity.getSystemService(Context.ALARM_SERVICE) as
AlarmManager

val calendar = Calendar.getInstance()


calendar.set(Calendar.HOUR_OF_DAY, hourOfDay)
calendar.set(Calendar.MINUTE, minute)

[ 193 ]
Setting Reminders for Tasks Chapter 12

alarmMgr.set(AlarmManager.RTC_WAKEUP, calendar.timeInMillis,
alarmIntent)
}
}

companion object {
val ARG_TASK_DESCRIPTION = "task-description"

fun newInstance(taskDescription: String): TimePickerFragment {


val fragment = TimePickerFragment()
val args = Bundle()
args.putString(ARG_TASK_DESCRIPTION, taskDescription)
fragment.arguments = args
return fragment
}
}

In the onCreateDialog method, you created an instance of the TimePickerDialog and


set the default time to the current time. So, when the time picker starts, it will display the
current time.

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.

Starting the reminder dialog


Open up the MainActivity file, to make some quick updates that will enable you to show
the dialog.

In the onCreateOptionsMenu, make the following changes:


override fun onCreateOptionsMenu(menu: Menu): Boolean {
...
val reminderItem = menu.findItem(R.id.reminder_item)

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:

Dynamically, using an instance of this class with


Context.registerReceiver(), or
Statically, using the <receiver> tag in your AndroidManifest.xml

An important note from the documentation:


Beginning with Android 8.0 (API level 26), the system imposes additional restrictions on manifest-
declared receivers. If your app targets API level 26 or higher, you cannot use the manifest to declare
a receiver for most implicit broadcasts (broadcasts that do not target your app specifically).

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

And, this tells us why it is efficient:


Local Broadcasts can be used as a general purpose pub/sub event bus in your app without any
overheads of system wide broadcasts.

Creating a broadcast receiver


Create a new file and name it AlarmReceiver, and have it extend BroadcastReceiver.
Then, update it with the following code:
class AlarmReceiver: BroadcastReceiver() {

override fun onReceive(context: Context?, p1: Intent?) {


Log.d("onReceive", "p1$p1")
val i = Intent(context, AlarmService::class.java)
context?.startService(i)
}
}

[ 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>

Now, proceed to create the AlarmService started by the AlarmReceiver.

Creating the AlarmService


IntentService
Let's first hear what the official documentation has to say:

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

IntentService is a Service component that handles requests via Intents. After


receiving the Intent, it starts a worker thread to run the task, and stops when the work is
done, or whenever it is appropriate as per the given 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

class AlarmService : IntentService("ToDoListAppAlarmReceiver") {


private var context: Context? = null

override fun onCreate() {


super.onCreate()
context = applicationContext
}

override fun onHandleIntent(intent: Intent?) {


intent?showNotification(it)

if(null == intent){
Log.d("AlarmService", "onHandleIntent( OH How? )")
}
}

private fun showNotification(taskDescription: String) {


Log.d("AlarmService", "showNotification($taskDescription)")
val CHANNEL_ID = "todolist_alarm_channel_01"
val mBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notifications_active_black_48dp)
.setContentTitle("Time Up!")
.setContentText(taskDescription)

val mNotificationManager = getSystemService(Context.NOTIFICATION_SERVICE)


as NotificationManager
mNotificationManager.notify(23, mBuilder.build())
}
}

[ 198 ]
Setting Reminders for Tasks Chapter 12

Stepping through the code, this is what you just did:

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.

In showNotification(), you used the NotificationCompat builder to create a


Notification instance. You set the title and content for the notification as well. Then, using a
NotificationManager, you triggered the notification. The ID parameter in the notify()
method is an identification for this notification unique to your application.

You need to register the service too. Here is how:


<service android:name=".AlarmService"
android:exported="false"/>

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.

Firebase Cloud Messaging


"Firebase Cloud Messaging (FCM) is a cross-platform messaging solution that lets you
reliably deliver messages at no cost." That, I believe, is the best brief description of this
service. It is actually part of a suite of many other services on the Firebase platform created
and run by Google.

[ 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:

1. Click Tools | Android | SDK Manager


2. Click the SDK Tools tab
3. Check the Google Repository checkbox, and click OK
4. Click OK to install
5. Click Background to complete the installation in the background, or wait for the
installation to complete and click Finish

You can now open and use the Assistant window in Android Studio by following these
steps:

1. Click Tools | Firebase to open the Assistant window:

[ 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

This is how the assistant looks:

[ 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:

Registered your app on Firebase


Added the SDK to your app by making the following updates to your root-
level build.gradle file
buildscript {
// ...
dependencies {
// ...
classpath 'com.google.gms:google-services:3.1.1' // google-services
plugin
}
}

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'

Update your manifest with the following:


<service
android:name=".MyFirebaseMessagingService">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT"/>

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

For that functionality, you will instead need the following:


<service
android:name=".MyFirebaseInstanceIDService">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
</intent-filter>
</service>

You will now create the MyFirebaseInstanceIDService class to extend


FirebaseInstanceIdService.

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

Now you will be taken through a three-step process:

1. The first step is to register your app by supplying your package name:

[ 206 ]
Setting Reminders for Tasks Chapter 12

2. In this step, you will only download the google-services.json file:

[ 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:

The app deletes the instance ID


The app is restored on a new device
The user uninstalls/reinstalls the app
The user clears app data

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

// If you want to send messages to this application instance or


// manage this apps subscriptions on the server side, send the
// Instance ID token to your app server.
sendRegistrationToServer(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?

In this chapter, we will:

Learn about writing tests


Learn about the Android Testing Support Library
Learn how to use Crashlytics to track crash reports
Learn about beta testing
Be introduced to the concept of CI
Learn about tools such as Jenkins, Bamboo, and Fastlane and how to use them for
build automation and deployment

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:

It allows the business to appreciate and understand the risks of software


implementation
It ensures that quality programs are written
It helps produce bug-free products
It reduces maintenance costs
It is a sure way of validating and verifying software
It improves performance
It confirms that all the declared functional requirements have been implemented
It instils confidence in clients
It exposes bugs quicker
It's required to stay in business
It ensures that the product can be installed and run in its intended environment

Android Testing Support Library


The Android Testing Support Library (ATSL) is a set of libraries purposely built for
testing Android apps. It's just like the usual support libraries you use in Android app
development, only this one is specifically for 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:

Very briefly, this is what the various parts mean:

Model: It provides and stores the app's data


View: It handles the display of the model's data
Presenter: It coordinates the UI with the data

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.

Functional versus non-functional testing


With functional tests, you test the application against the given business requirements.
They don't require the application to be in full operation. These include:

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.

Your first test


You will first work on displaying the notes to the user. The notes presenter will provide the
logic showing a progress indicator, which displays the notes and other note-related views.

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)

// The class under test


notesPresenter = NotesPresenter()
}

@Test
fun `should display note view when button is clicked`() {
// When adding a new note
notesPresenter.addNewNote()

// Then show add note UI


verify(notesView)?.showAddNote()
}

[ 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 loadNotes(forceUpdate: Boolean)

fun addNewNote()

fun openNoteDetails(requestedNote: Note)


}
}

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

data class Note(val title: String?,


val description: String?,
val imageUrl: String? = null) {

val id: String = UUID.randomUUID().toString()


}

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) {
}

override fun addNewNote() {


}

override fun openNoteDetails(requestedNote: Note) {


}
}

[ 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:

Your test should fail:

It failed because we expected the showAddNote() method of the NotesView class to be


called, but it wasn't. This happened because you only implemented the interface in the
Presenter class, but you never called the expected method in the NotesView class.

Let's go ahead and fix that now.

First, update NotesPresenter to accept a NotesContract.View object in its primary


constructor. Then, call the expected method, showAddNote(), within the addNewNote()
method.

You should always prefer constructor injection to field injection. It is


much easier to handle, and easier to read and maintain too.

[ 221 ]
Testing and Continuous Integration Chapter 13

Your NotesPresenter class should now look like this:


class NotesPresenter(notesView: NotesContract.View):
NotesContract.UserActionsListener {
private var notesView: NotesContract.View = checkNotNull(notesView) {
"notesView cannot be null"
}
override fun loadNotes(forceUpdate: Boolean) {
}

override fun addNewNote() = notesView.showAddNote()

override fun openNoteDetails(requestedNote: Note) {


}
}

checkNotNull is a built-in Kotlin utility function for verifying whether


an object is null or not. Its second parameter takes a lambda function
which should return a default message if the object is null.

Since the NotesPresenter now requires a NotesContract.View in its primary


constructor, you'll have to update the test to cater for that:
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)

// Get a reference to the class under test


notesPresenter = NotesPresenter(notesView)
}

The code has been refactored. Now, rerun the test:

[ 222 ]
Testing and Continuous Integration Chapter 13

Hooray! The test passes now; that's awesome. Excellent work.

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

private val NOTES = arrayListOf(Note("Title A", "Description A"),


Note("Title A", "Description B"))
...

@Test
fun `should load notes from repository into view`() {
// When loading of Notes is requested
notesPresenter.loadNotes(true)

// Then capture callback and invoked with stubbed notes


verify(notesRepository)?.getNotes(loadNotesCallbackCaptor?.capture())
loadNotesCallbackCaptor!!.value.onNotesLoaded(NOTES)

// Then hide progress indicator and display notes


verify(notesView).setProgressIndicator(false)
verify(notesView).showNotes(NOTES)
}

Let's go over this again very briefly.

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

Leverage the Null Safety feature in Kotlin as much as possible. Instead of


having nullable types for the mocks, use Kotlin's lateinit modifier
instead.

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.

Now, create the NotesRepository as follows:


interface NotesRepository {

interface LoadNotesCallback {

fun onNotesLoaded(notes: List<Note>)


}

fun getNotes(callback: LoadNotesCallback?)


fun refreshData()
}

Next, update the NotesContract:


interface NotesContract {
interface View {
fun setProgressIndicator(active: Boolean)

fun showNotes(notes: List<Note>)

...
}

...
}

[ 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

class NotesPresenter(notesView: NotesContract.View, notesRepository:


NotesRepository) :
NotesContract.UserActionsListener {

private var notesRepository: NotesRepository =


checkNotNull(notesRepository) {
"notesRepository cannot be null"
}

override fun loadNotes(forceUpdate: Boolean) {

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

You used constructor injection to inject a NotesRepository instance into


NotesPresenter. You checked it for nullability just like you did
for NotesContract.View.

In the loadNotes() method, you displayed the progress indicator and refreshed the data
depending on the forceUpdate field.

You then used a utility class, EspressoIdlingResource, basically to alert Espresso of a


possible asynchronous request. On getting the notes, you hide the progress indicator and
showed the notes.

Create a util package to contain EspressoIdlingResource and


SimpleCountingIdlingResource:

import android.support.test.espresso.IdlingResource

object EspressoIdlingResource {

private const val RESOURCE = "GLOBAL"

private val countingIdlingResource =


SimpleCountingIdlingResource(RESOURCE)

val idlingResource = countingIdlingResource

[ 226 ]
Testing and Continuous Integration Chapter 13

fun increment() = countingIdlingResource.increment()

fun decrement() = countingIdlingResource.decrement()


}

And for SimpleCountingIdlingResource :


package com.packtpub.eunice.notesapp.util

import android.support.test.espresso.IdlingResource
import java.util.concurrent.atomic.AtomicInteger

class SimpleCountingIdlingResource

(resourceName: String) : IdlingResource {

private val mResourceName: String = checkNotNull(resourceName)

private val counter = AtomicInteger(0)

@Volatile
private var resourceCallback: IdlingResource.ResourceCallback? =
null

override fun getName() = mResourceName

override fun isIdleNow() = counter.get() == 0

override fun registerIdleTransitionCallback(resourceCallback:


IdlingResource.ResourceCallback) {
this.resourceCallback = resourceCallback
}

fun increment() = counter.getAndIncrement()

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)

// Get a reference to the class under test


notesPresenter = NotesPresenter(notesView)
}

Now that everything's set, run the test:

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.

The steps for implementation are:

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.

The minimum requirements are:

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)

Make sure your Gradle Wrapper version is at least 4.4:


distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-
all.zip

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

<uses-permission android:name="android.permission.INTERNET" />

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

...

override fun onCreate(savedInstanceState: Bundle?) {


crash_btn.setOnClickListener {
Crashlytics.getInstance().crash()
}
}

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.

Setting up for beta testing


You can set up and manage beta testing from the Google Pay Console. You can choose to
make your app available to a specific Google group or you can send them invitations via
email.

Users must have a Google (@gmail.com) or a G Suite account to join.


After publishing, it may take a while for your link to become available to
testers.

Creating the beta test track


Now, you will have to create what is called a track inside your Google Play console. This is
basically a setup to manage your testing processes.

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

Follow the following steps to set up a beta track:

1. Login to your Play Console and select your application.


2. Locate App releases under Release management and select Manage under
the Beta track.
3. Upload your APK in the Artifacts section, then expand the Manage
testers section.
4. Under Choose a testing method, select Open Beta Testing.
5. Copy the Opt-in URL and share it with your testers.
6. Provide an email address or URL next to the Feedback channel in order to collect
feedback from testers. Then, click Save to, well, save it.

The opt-in URL


When you are done creating the test, publish it. Then, you will be given the test link.
Its format is as follows: https:/​/​play.​google.​com/​apps/​testing/​com.​yourpackage.​name.
Now, you have to share this link with your testers. With that, they can opt-in to test your
app.

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

3. While you're at it, add the following locales as well:


export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8

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:

[sudo] gem install bundler

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.

In this chapter, the focus will be on learning the following:

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

Key store generation


One of the most important security features of Android is allowing the installation of APKs,
but only from a trusted source such as Google Play Store or Amazon App Store. These
distribution channels mandate that the developer authenticates the app, stating that it is
indeed the app that he or she intends for distribution.

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.

The KeyStore can be generated in a couple of ways:

Android Studio
The command line

Let's discuss the steps involved in the generation of the key store in detail.

Key store generation through Android Studio


These are the steps we need to follow for the generation of the key store through Android:

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.

Auto signing the APK through Android Studio


We have the option to have the signed APK generate automatically whenever changes are
made to the app. This can be achieved by doing the following in Android Studio:

1. Right click on App | Project Structure.


2. Select the Signing tab. In this tab, we need to provide the details of the app
signing configuration. Here, we have named it config and we are storing the
Key Alias, Password, and the path to the store file:

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.

In this code, we are creating a variable called keystorePropertiesFile and initializing it


with the keystore.properties file which we created. Furthermore, we initialize a new
Properties() object called keyStoreProperties. The details of the
keystorePropertiesFile are loaded into the keystoreProperties object:

def keystorePropertiesFile = rootProject.file("keystore.properties")


def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))

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.

Build types and flavors


Developers can maintain variants in build types, and these can be configured through
the build.gradle file. The configuration makes it easy for developers to maintain the
debugging code and the release version of the code in the same app:

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

We can define the type of build—debug or release, in Android Studio:

1. Right click on app | Project structure.


2. In the Build Types tab, we need to add a new type of build variant. We have two
build types, debug and release, which are shown in the following screenshot.
While creating the Build Types, we will have the option to select the Signing
Config for the build variant:

[ 253 ]
Making Your App Available to the World Chapter 14

This will add the following code in the build.gradle file:


buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-
android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.config
}
}

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 build.gradle file will contain the following information:


android {
........
productFlavors {
paid {
signingConfig signingConfigs.paidconfig
}
free {
signingConfig signingConfigs.freeconfig
}
}
}

Key store generation through the command line


The Key store can also be generated via the command line using the keytool. The keytool is
available in the bin directory of the jdk:

Launch the command prompt and run the following command:


keytool -genkey -v -keystore dreamindiacmd.jks -keyalg RSA -keysize 2048 -
validity 10000 -alias packtcmdkey

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:

Publishing the app in Google Play Store


Now that we have the signed release version of the APKs available with us, it is time to
publish them via the Google Play Store for worldwide distribution.

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

App release section


The app release section enables the developer to manage the entire life cycle of APK release.
Developers can distribute their app for internal testing, alpha, and beta release before
moving the APK into production for public distribution. The various stages of release help
developers to collect feedback on the app by restricting the app so that it is only available to
specific users:

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

Store listing section


The Store listing section is the next section to focus on. It is an important section, as it is
where users will get to see the various screenshots, short, and detailed descriptions of the
app. Developers will have the option to save a draft and come back any time to continue
filling in the details:

In the Google Play Store, the store listing section mandates the following:

Two screenshots of the app


A hi-res icon - 512 * 512
A feature graphic - 1,024 W x 500 H:

[ 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

Content rating section


Developers should do a self declaration of the content provided in the app. The content
rating section has a questionnaire which requests developers to provide specific answers.
Answering the questionnaire is a straightforward task:

It is important that developers provide the right information about the


content of the app, as providing incorrect information can affect the store
listing.

Pricing and distribution section


The last and final mandatory section, pricing and distribution, requires developers to
provide information related to the pricing of their app—free or paid, the list of countries the
app is to be distributed to, if the app if primarily directed at children, if the app contains
ads, content guidelines, and an affirmation from the developer to comply with US export
laws:

[ 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

Publishing the app in Amazon Appstore


The Amazon Appstore provides a free market place for developers to distribute their
Android app. Developers can log on and create their free account at the following
URL: https:/​/​developer.​amazon.​com/​apps-​and-​games/​app-​submission/​android.​

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

Let's look at each of these sections in detail.

[ 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

Availability & Pricing section


In this section, the developers are expected to provide information about:

The app's pricing—free or paid


The list of countries
The date and time for the app to be published:

[ 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

Images & Multimedia section


In the Images & Multimedia section, developers are expected to enter the graphical assets
related to the app. The users are expected to provide:

Icons: 512 * 512 PNG and 114 * 114 PNG


Screenshots: Between 3 to 10 PNGs or JPGs

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

Content Rating section


In the Content Rating section, developers are expected to answer a set of questions related
to the nature of the content displayed in the app. These questions come under the subject
matter:

[ 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

Binary File(s) section


In this section, developers are expected to upload the signed APK generated from Android
Studio or the command line:

[ 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:

Device support for non-Amazon Android devices is NOT enabled by


default. Developers need to enable this explicitly by clicking on Edit
device support and making the changes required.

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

In this chapter, the following topics are going to be covered:

Identifying human faces in an image


Tracking human faces from a camera feed
Identifying specific parts of the face (for example, eyes, ears, nose, and mouth)
Drawing graphics on specific parts of a detected face in an image (for example,
rabbit ears over the user's ears)

Introduction to Mobile Vision


The Mobile Vision API provides a framework for finding objects in photos and videos. The
framework can locate and describe visual objects in images or video frames, and it has an
event-driven API that tracks the position of those objects.

Currently, the Mobile Vision API includes face, barcode, and text detectors.
Building an App Using the Google Faces API Chapter 15

Faces API concepts


Before diving into coding the features, it is necessary that you understand the underlying
concepts of the face detection capabilities of the face detection API.

From the official documentation:

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.

Classification is determining whether a certain facial characteristic is present. For example,


a face can be classified with regards to whether its eyes are open or closed or smiling or not.

Getting started – detecting faces


You will first learn how to detect a face in a photo and its associated landmarks.

We will need some requirements in order to pursue this.

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

Creating the FunyFace project


Create a new project called FunyFace. Open up the app module's build.gradle file and
update the dependencies to include the Mobile Vision APIs:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
implementation 'com.google.android.gms:play-services-vision:11.0.4'
...
}

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" />

Now, your app is ready to use the face detection APIs.

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

Now, this is how you will go about performing face detection.

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

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {


super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

button.setOnClickListener {
detectFace()
}
}
}

Loading the image


In your detectFace() method, you will first load your image from the drawable folder
into memory and create a bitmap image from it. Since you will be updating this bitmap to
paint over it when the face is detected, you need to make it mutable. This is what makes
your bitmap mutable:
options.inMutable=true

See the following implementation:


private fun detectFace() {
// Load the image
val bitmapOptions = BitmapFactory.Options()
bitmapOptions.inMutable = true
val myBitmap = BitmapFactory.decodeResource(
applicationContext.resources,
R.drawable.children_group_picture,
bitmapOptions)
}

[ 280 ]
Building an App Using the Google Faces API Chapter 15

Creating a Paint instance


Use the Paint API to get an instance of the Paint class. You will only draw around the
face, and not paint the whole face. To do this, set a thin stroke, give it a color, which in our
case is red, and set the style of paint to STROKE using Paint.Style.STROKE:
// Get a Paint instance
val myRectPaint = Paint()
myRectPaint.strokeWidth = 5F
myRectPaint.color = Color.RED
myRectPaint.style = Paint.Style.STROKE

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.

Creating the face detector


All you have done thus far is basically housekeeping. You will now access the FaceDetector
API by which you will, well, detect the face in the image. You will disable tracking for now,
as you only want to detect the face at this stage.

[ 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
}

Detecting the faces


Now, you will use the detect() method from the faceDetector instance to get the faces
and their metadata. The result will be SparseArray of Face objects:
// Detect the faces
val frame = Frame.Builder().setBitmap(myBitmap).build()
val faces = faceDetector.detect(frame)

Drawing rectangles on the faces


Now that you have the faces, you will iterate through this array to get the coordinates of
the bounding rectangle for the face. Rectangles require x, y of the top left and bottom right
corners, but the information available only gives the left and top positions, so you have to
calculate the bottom right using the top left, width, and height. Then, you need to
release the faceDetector to free up resources. Here's the code:
// Mark out the identified face
for (i in 0 until faces.size()) {
val thisFace = faces.valueAt(i)
val left = thisFace.position.x

[ 282 ]
Building an App Using the Google Faces API Chapter 15

val top = thisFace.position.y


val right = left + thisFace.width
val bottom = top + thisFace.height
tempCanvas.drawRoundRect(RectF(left, top, right, bottom), 2F, 2F,
myRectPaint)
}

imageView.setImageDrawable(BitmapDrawable(resources, tempBitmap))

// Release the FaceDetector


faceDetector.release()

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

for (landmark in thisFace.landmarks) {


val x = landmark.position.x
val y = landmark.position.y

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:

There you have it! That's funny, right?

[ 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:

Hands-On Microservices with Kotlin


Juan Antonio Medina Iglesias
ISBN: 9781788471459
Understand microservice architectures and principles
Build microservices in Kotlin using Spring Boot 2.0 and Spring Framework 5.0
Create reactive microservices that perform non-blocking operations with Spring
WebFlux
Use Spring Data to get data reactively from MongoDB
Test effectively with JUnit and Kotlin
Create cloud-native microservices with Spring Cloud
Build and publish Docker images of your microservices
Scaling microservices with Docker Swarm
Monitor microservices with JMX
Deploy microservices in OpenShift Online
Other Books You May Enjoy

Building Applications with Spring 5 and Kotlin


Miloš Vasić

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

Leave a review - let other readers know what


you think
Please share your thoughts on this book with others by leaving a review on the site that you
bought it from. If you purchased the book from Amazon, please leave us an honest review
on this book's Amazon page. This is vital so that other potential readers can see and use
your unbiased opinion to make purchasing decisions, we can understand what our
customers think about our products, and our authors can see your feedback on the title that
they have worked with Packt to create. It will only take a few minutes of your time, but is
valuable to other potential customers, our authors, and Packt. Thank you!

[ 289 ]
Index

Android Studio project


! building 23
!! operator 75 creating 18
executing 26
A executing, with Android emulator 26
alarm, AlarmManager Gradle, using 23
AlarmService, creating 197 parts 26
BroadcastReceiver class, implementing 195 SDK, selecting 19, 22
creating 192, 194 Android Studio
Elapsed Realtime 192 initiating 14, 17
Elapsed Realtime Wakeup 192 installation link 12
reminder dialog, starting 194, 195 installing 12
RTC 192 layout editor, using 143, 144, 145, 147
RTC Wakeup 192 Android Testing Support Library (ATSL)
AlarmManager about 213
alarm, creating 192, 194 Model-View-Presenter architecture 213
using 192 Test-Driven Development (TDD) 215
AlarmService, alarm Android Virtual Device (AVD) 27
creating 197 Android
IntentService 197 developing, with Kotlin 7
IntentService, creating 198, 199 AndroidManifest file
alpha testing 238 about 105
Amazon Appstore build.gradle file 107
app, publishing 265 Anonymous Inner Class 71
Availability & Pricing section 267 APK
Binary File(s) section 273 auto signing, through Android Studio 251
Content Rating section 271 build types and flavors 253
Description section 268 app
general information 266 publishing, in Amazon Appstore 265
Images & Multimedia section 270 publishing, in Google Play Store 256
reference 265
Android emulator B
creating 27, 29, 34 beta testing
executing, on actual device 37 about 238
reference 26 opt-in URL 239
used, for executing Android Studio project 26, 34 setting up 238
Android Package Kit (APK) 23
track, creating 238
BroadcastReceiver, alarm E
about 195 Elvis operator 75
broadcast receiver, creating 196 emulator toolbar 33
broadcasts, sending 196
F
C face detection
capabilities, Crashlytics about 277, 282
analytics 229 canvas, creating 281
crash reporting 229 face detector, creating 281
real-time alerts 229 FunyFace project, creating 278, 279
class structure image, loading 280
about 64 Paint instance, creating 281
constructors 65 face tracking 277
data class 67, 68 faces API concepts 277
classification 277 fastlane
code installing 241
about 97 features, Kotlin
customizing 98 concise 8
constants 62 Java interoperability 9
constructors 65 NullPointerException, avoiding 9
Continuous Integration Firebase Cloud Messaging (FCM)
about 239 about 200
defining 240 integrating, with TodoList app 200, 202, 203,
fastlane, installing 241 204, 206, 208, 210, 211
tools 241 FloatingActionButton 85
CouchBase 190 function
Crashlytics about 78
about 229 parameters 79
Connect 229, 237 return type 79
functional, versus non-functional testing
D about 216
Notes app, building 217
Data Access Object (DAO) 182
test dependencies 218, 219, 220, 223
data classes 67
database
about 167 G
non-relational database 190 game status check
relational database 168, 169 implementing 82, 85
datatypes game UI
about 62 building 56, 61
constants 62 Glide
properties 62 about 134
variables 62 build.gradle file 134
document databases 190 Kotlin code 135
Google API client 117, 119

[ 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

You might also like