© Peter Späth 2019
Peter SpäthLearn Kotlin for Android Developmenthttps://doi.org/10.1007/978-1-4842-4467-8_2

2. Classes and Objects: Object Orientation Philosophy

Peter Späth1 
(1)
Leipzig, Germany
 

At the beginning of the book we said that computer programs are about processing some input and generating some output from it, possibly also altering the state of some data-holding instance like a file or a database. Although this is certainly true, it does not tell the whole story. In real-world scenarios, computer programs exhibit another characteristic: They are supposed to be of some practical use, and as a result of that, model real-world events and things.

Say, for example, you write a simple program for registering paper invoices and summing up the money amounts for each day. The input is clear: It is the paper invoices in electronic form. The output is the daily sum, and along the way a database keeps records for all registered invoices. The modeling aspect tells us that we deal with the following objects: an invoice in electronic form, a database for keeping the records, and some calculation engine for accessing the database and performing the summation operation. To be of practical use these objects need to exhibit the following features. First, depending on their nature, they might have a state. For the invoice object, for example, we have the name of the seller, the name of the buyer, the date, the name of the goods, and of course the monetary amount. Such state elements are commonly referred to as properties . The database obviously has the database contents as its state. The calculation engine, in contrast, doesn’t need its own state, and it uses the state from the other objects to do its work. The second feature of objects is operations you can perform on them, commonly referred to as methods . The invoice object, for example, might have methods to set and tell us about its state, the database object needs methods for saving and retrieving data from its store, and the calculation engine obviously must be able to perform the summation calculation for each day.

Before we deepen our insights in that kind of real world to computer world mapping methodology, let us first sum up what we have seen so far.
  • Objects: We use objects to identify things in the real world we want to model in a computer program.

  • State: Objects often have a state, which describes the characteristics each object has to be able to handle the tasks it is supposed to perform.

  • Property: The state of an object consists of a set of properties. Properties thus are elements of a state.

  • Method: A method is a way to access an object. This describes the functional aspects an object needs to exhibit to handle the tasks it is supposed to perform, possibly including changing and querying its state.

Note

Depending on the terminology used, methods are also sometimes called operations or functions, and properties are sometimes referred to as attributes . Although we continue to use property we’ll switch to using function for methods because the Kotlin documentation uses the term function and we want to keep things easy for you, including reference research in the Kotlin documentation.

One important concept to understand is that Invoice is not an object, nor is Person, nor is Triangle. How can that be? We just were talking of invoices as objects, and why are Person and Triangle not objects? This contradiction comes from some kind of linguistic fluffiness. Did you realize that we’re talking of Invoice, but not of an invoice? There is a major difference between those: An invoice, or more precisely a particular invoice, is an object, but Invoice is a classification or class. All possible invoices share membership in the Invoice class, and all concrete persons share membership in the Person class, just as all possible triangles belong to the Triangle class. Does this seem theoretical or even nitpicking? Perhaps, but it has important practical implications and we really need to understand that class notion. Consider a thousand invoices arriving on a particular day. In some computer program, do we really want to write something like this:
object1 = Invoice(Buyer=Smith,Date=20180923,Good=Peas,Cost=$23.99), object2 = Invoice(...), ..., object1000 = Invoice(...)
That just doesn’t make sense, because we don’t want to write a huge new computer program every day. What instead makes sense is having an Invoice class that describes all possible invoices. From this class we must be able to create concrete invoices given some invoice style input. In pseudo-code:
data = [Some incoming invoice data]
This provides incoming invoice data for a particular paper invoice. Make sure the data can be represented by the abstract characteristics of the Invoice class, so it has a buyer, a date, goods or services, and so on. This is the same as saying that Invoice is a valid classification of all possible input data.
object = concrete object using that data

You can build a concrete invoice object, given the classification and the data. Another way of saying building a concrete invoice object from the Invoice class is constructing an object from the class or creating an Invoice instance. We will be using that instance and constructor notion in the rest of the book.

Our main subject of this chapter, object orientation, exactly is about classes, instantiation, and objects. Some details are still missing, but let us first summarize what we just learned and continue with our definition list:
  • Class: A class characterizes all possible objects of some kind. It is thus an abstraction, and any such object characterized by the class is said to belong to that particular class.

  • Instance: An instance of a class represents exactly one object belonging to that class. The process of creating an object from a class and concrete data is called instantiation.

  • Construction: The process of creating an instance from a class is also called construction.

Equipped with these object orientation concepts we can now start looking at how Kotlin deals with objects, classes, and instantiation. In the following sections we also talk about some aspects of object orientation we haven’t introduced yet. We could have done that here in a theoretical manner, but describing it with the Kotlin language at hand is a little easier to grasp.

Kotlin and Object-Oriented Programming

In this section we talk about the main characteristics of classes and objects in Kotlin. Some aspects are covered in greater detail in later sections, but here we want to give you the basics you need for Kotlin programming.

Class Declaration

Note

The term declaration here is used for the description of the structure and the constituent parts of a class.

In Kotlin, to declare a class you basically write
class ClassName(Parameter-Declaration1, Parameter-Declaration2, ...) {
        [Class-Body]
    }
Let us examine its parts:
  • ClassName: This is the name of the class. It must not contain spaces, and in Kotlin by convention you should use CamelCase notation; that is, start with a capital letter, and instead of using spaces between words, capitalize the first letter of the second word, as in EmployeeRecord.

  • Parameter-Declaration: These declare a primary constructor and describe data that are needed to instantiate classes. We talk more about parameters and parameter types later, but for now we mention that such parameter declarations basically come in three varieties:
    • Variable-Name:Variable-Type: An example would be userName: String. Use this to pass a parameter usable for instantiating a class. This happens inside a special construct called an init{} block. We’ll talk about that initialization later.

    • val Variable-Name:Variable-Type (e.g., val userName: String): Use this to pass a usable parameter from inside the init{} block, but also define a nonmutable (unchangeable) property. This parameter is thus used to directly set parts of the object’s state.

    • var Variable-Name:Variable-Type (e.g., var userName: String): Use this to pass a usable parameter from inside the init() function, but also to define a mutable (changeable) property for setting parts of the object’s state.

    For the names use CamelCase notation, this time starting with a lowercase letter, as in nameOfBuyer. There are lots of possibilities for a variable type. For example, you can use Int for an integer and the declaration then could look like val a:Int. In Chapter 3 we talk more about types.

  • [Class-Body]: This is a placeholder for any number of functions and additional properties, and also init { ... } blocks worked through while instantiating a class. In addition, you can also have secondary constructors and companion objects, which we describe later, and inner classes.

Exercise 1

Which of the following appears to be a valid class declaration?
1.    class Triangle(color:Int) (
         val coordinates:Array<Pair<Double,Double>>
             = arrayOf()
     )
2.    class Triangle(color:Int) {
         val coordinates:Array<Pair<Double,Double>>
             = arrayOf()
     }
3.    class simple_rectangle() {
         val coordinates:Array<Pair<Double,Double>>
             = arrayOf()
     }
4.    class Colored Rectangle(color:Int) {
         val coordinates:Array<Pair<Double,Double>>
             = arrayOf()
     }

Property Declaration

We’ll be talking about detailed characteristics of properties in Chapter 3. Here I provide just a brief summary for simple property declarations: They basically look like
val Variable-Name:Variable-Type = value
for immutable properties, and
var Variable-Name:Variable-Type = value
for mutable properties. The = value is not needed, however, if the variable’s value gets set inside an init { } block.
class ClassName(Parameter-Declaration1,
        Parameter-Declaration2, ...) {
    ...
    val propertyName:PropertyType = [init-value]
    var propertyName:PropertyType = [init-value]
    ...
}

One word about mutability is in order: Immutable means the val variable gets its value at some place and cannot be changed afterward, whereas mutable means the var variable is freely changeable anywhere. Immutable variables have some advantages concerning program stability, so as a rule of thumb you should always prefer immutable over mutable variables.

Exercise 2

Which one of the following is a valid class?
1.    class Invoice() {
         variable total:Double = 0.0
     }
2.    class Invoice() {
         property total:Double = 0.0
     }
3.    class Invoice() {
         Double total =
         0.0
     }
4.    class Invoice() {
         var total:Double = 0.0
     }
5.    class Invoice() {
         total:Double = 0.0
     }

Exercise 3

What is wrong with the following class (not technically, but from a functional perspective)?
class Invoice() {
    val total:Double = 0.0
}

How can it be fixed?

Class Initialization

An init { } block inside the class body may contain statements that get worked through when the class gets instantiated. As the name says, it should be used to initialize instances before they actually get used. This includes preparing the state of an instance so it is set up properly to do its work. You can in fact have several init{ } blocks inside a class. In this case the init{ } blocks get worked through sequentially in the order in which they appear in the class. Such init{ } blocks are optional, however, so in simple cases it is totally acceptable to not provide one.
class ClassName(Parameter-Declaration1,
        Parameter-Declaration2, ...) {
    ...
    init {
        // initialization actions...
    }
}

Note

A // starts a so-called end-of-line comment ; anything starting from that until the end of the current line is ignored by the Kotlin language. You can use it for comments and documentation.

If you set properties inside an init { } block, it is no longer necessary to write = [value] inside the property declaration.
class ClassName(Parameter-Declaration1,
        Parameter-Declaration2, ...) {
    val someProperty:PropertyType
    ...
    init {
        someProperty = [some value]
        // more initialization actions...
    }
}
If you specify a property value inside the property declaration and later change the property’s value inside init { }, the value from the property declaration gets taken to initialize the property before init{ } starts. Later, inside init { } the property’s value then gets changed by suitable statements:
class ClassName {
        var someProperty:PropertyType = [init-value]
        ...
        init {
            ...
            someProperty = [some new value]
            ...
        }
}

Exercise 4

What is wrong with the following class?
class Color(val red:Int,
            val green:Int,
            val blue:Int)
{
    init {
        red = 0
        green = 0
        blue = 0
    }
}

Exercise 5

What is wrong with the following class?
class Color() {
    var red:Int
    var green:Int
    var blue:Int
    init {
      red = 0
      green = 0
    }
}

An Invoice in Kotlin

That is enough theory; let us work out the Invoice class we already talked about. For simplicity, our invoice will have the following properties: the buyer’s first and last name, the date, the name and amount of a single product, and the price per item. I know in real life we need more properties, but this subset will do here because it describes enough cases and you can easily extend it. A first draft of the actual Invoice class then reads:
class Invoice(val buyerFirstName:String,
      val buyerLastName:String,
      val date:String,
      val goodName:String,
      val amount:Int,
      val pricePerItem:Double) {
}

We talk about data types later in this chapter, but for now we need to know that String is any character string, Int is an integer number, and Double is a floating-point number. You can see that for all the parameters passed to the class I used the val ... form, so after instantiation all those parameters will be available as immutable (unchangeable) properties. This makes a lot of sense here, because the parameters are exactly what is needed to describe the characteristics, or state, of an invoice instance.

Note

In Kotlin it is permitted to omit empty blocks altogether. You could therefore remove the { } from the Invoice class declaration. Nevertheless, we leave it here, because we will add elements to the body soon.

More Invoice Properties

The class body is still empty, but we can easily think of properties we might want to add. For example, it could be interesting to have the full name of the buyer at hand, and the total price of all items. We can add the corresponding properties:
class Invoice(val buyerFirstName:String,
      val buyerLastName:String,
      val date:String,
      val goodName:String,
      val amount:Int,
      val pricePerItem:Double)
{
    val buyerFullName:String
    val totalPrice:Double
}

Did we forget to initialize the properties by adding values via = something? Well, yes and no. Writing it that way is actually forbidden, but because we will initialize those properties inside an init{ } block soon, it is allowable to not initialize the properties.

Invoice Initialization

No sooner said than done, we add a corresponding init{ } block:
class Invoice(val buyerFirstName:String,
      val buyerLastName:String,
      val date:String,
      val goodName:String,
      val amount:Int,
      val pricePerItem:Double)
{
     val buyerFullName:String
     val totalPrice:Double
     init {
         buyerFullName = buyerFirstName + " " +
             buyerLastName
         totalPrice = amount * pricePerItem
     }
}
By the way, there is a shorter way of writing such one-line initializers for properties:
...
val buyerFullName:String = buyerFirstName + " " + buyerLastName
val totalPrice:Double = amount * pricePerItem
...

This makes an init{ } block unnecessary. There is, however, no functional difference to using an init{ } block, and the latter allows for more complex calculations that do not fit into one statement.

Exercise 6

Write the Invoice class without the init{ } block, keeping its full functionality.

Instantiation in Kotlin

With the class declaration ready now, to instantiate an Invoice object from it, all you have to do is write this:
val firstInvoice = Invoice("Richard", "Smith", "2018-10-23", "Peas", 5, 2.99)
If you don’t know how to put all that into a program, in Kotlin it is totally acceptable to write everything in one file, which then reads:
class Invoice(val buyerFirstName:String,
      val buyerLastName:String,
      val date:String,
      val goodName:String,
      val amount:Int,
      val pricePerItem:Double)
{
    val buyerFullName:String
    val totalPrice:Double
    init {
        buyerFullName = buyerFirstName + " " +
            buyerLastName
        totalPrice = amount * pricePerItem
    }
}
fun main(args:Array<String>) {
    val firstInvoice = Invoice("Richard", "Smith",
      "2018-10-23", "Peas", 5, 2.99)
    // do something with it...
}

The main() function is the entry point for Kotlin applications. Unfortunately, this won’t work like that for Android, because Android has a different idea for how to start apps. Please be patient, as we will come back to that soon.

Note

Having said that, please do not write files containing a lot of different classes or long functions. We’ll talk about program structure soon in the section “Structuring and Packages” later in this chapter. For now, just remember that having short identifiable pieces of code helps a great deal in writing good software!

Adding Functions to Invoices

Our Invoice class does not yet have explicit functions. I deliberately said explicit, because by virtue of both the constructor properties and the properties we added in the class body, Kotlin provides us implicit accessor functions in the form of objectName.propertyName. We can, for example, add inside any function:
...
val firstInvoice = Invoice("Richard", "Smith",
    "2018-10-23", "Peas", 5, 2.99)
val fullName = firstInvoice.buyerFullName
where firstInvoice.buyerFullName reads the full name of the buyer from the object. Under different circumstances we could also use accessors to write properties as in
...
val firstInvoice = Invoice("Richard", "Smith",
    "2018-10-23", "Peas", 5, 2.99)
firstInvoice.buyerLastName = "Doubtfire"

Do you see why we can’t do that here? Remember, we declared buyer- LastName as an immutable val, so it cannot be changed. If we substituted for the val with var, the variable became mutable and the setting became an allowed operation.

As an example for an explicit function, we could create a means to let the object tell about its state. Let us call this function getState() . An implementation would be:
class Invoice( [constructor parameters] ) {
    val buyerFullName:String
    val totalPrice:Double
    init { [initializer code] }
    fun getState(): String {
        return "First name: ${firstName} " +
                "Last name: ${lastName} " +
                "Full name: ${buyerFullName} " +
                "Date: ${date} " +
                "Good: ${goodName} " +
                "Amount: ${amount} " +
                "Price per item: ${pricePerItem} " +
                "Total price: ${totalPrice}"
    }
}

where the :String in fun getState(): String indicates that the function returns a string, and the return ... actually performs the return action. The ${some- Name} inside a string gets replaced by the value of someName, and the represents a line break.

Note

Developers use the term implementation quite often to describe the transition from an idea to the code performing the idea.

To invoke a function from outside the class, just use both the object name and the function name and write
objectName.functionName(parameter1, parameter2, ...)
Because we don’t have any parameters for getState() this would be:
...
val firstInvoice = Invoice("Richard", "Smith",
    "2018-10-23", "Peas", 5, 2.99)
val state:String = firstInvoice.getState()
If, however, we find ourselves inside the class, say inside an init{ } block or inside any other function of the class, to call a function just use its name, as in
...
// we are inside the Invoice class
val state:String = getState()
Functions are going to be described in detail later in this chapter. For now, I only want to mention that functions might have a parameter list. For example, a method for the Invoice class calculating the tax with the tax rate as a parameter would read:
fun tax(taxRate:Double):Double {
    return taxRate * amount * pricePerItem
}

The :Double after the parameter list declares that the method returns a floating-point number, which the return statement actually does. For parameter lists with more than one element use a comma (,) as a separator. In case you didn’t already realize it, the asterisk (*) is used to describe a multiplication operation.

To invoke that tax method, you write
...
val firstInvoice = Invoice("Richard", "Smith", "2018-10-23", "Peas", 5, 2.99)
val tax:Double = firstInvoice.tax(0.11)

Exercise 7

Add a method goodInfo() that returns something like “5 pieces of Apple.” Hint: Use amount.toString() to convert the amount to a string.

The Complete Invoice Class

The Invoice class with all properties and methods we have talked about so far, and some code to invoke it, reads like this:
class Invoice(val buyerFirstName:String,
      val buyerLastName:String,
      val date:String,
      val goodName:String,
      val amount:Int,
      val pricePerItem:Double)
{
    val buyerFullName:String
    val totalPrice:Double
    init {
        buyerFullName = buyerFirstName + " " +
            buyerLastName
        totalPrice = amount * pricePerItem
    }
    fun getState():String {
        return "First name: ${buyerFirstName} " +
                "Last name: ${buyerLastName} " +
                "Full name: ${buyerFullName} " +
                "Date: ${date} " +
                "Good: ${goodName} " +
                "Amount: ${amount} " +
                "Price per item: ${pricePerItem} " +
                "Total price: ${totalPrice}"
    }
    fun tax(taxRate:Double):Double {
        return taxRate * amount * pricePerItem
    }
}
fun main(args:Array<String>) {
    val firstInvoice = Invoice("Richard", "Smith", "2018-10-23", "Peas", 5, 2.99)
    val state:String = firstInvoice.getState()
    val tax:Double = firstInvoice.tax(0.11)
    // do more things with it...
}

This works for an application style invocation you’d find if you built that class for a desktop or server application. It wouldn’t run on Android, because there the procedures for starting up apps and communicating with the hardware differ substantially compared to such a simple main() method. Therefore, to get back to the subject, in the rest of this chapter we will develop a more Android-style app.

A Simple Number Guessing Game

In Android, applications circle around Activities, which are identifiable pieces of code corresponding to particular responsibilities from a user’s workflow perspective. Each of these responsibilities can be handled by a distinct screen built up by graphical objects positioned in a screen layout. An app can have one or more activities that are represented by distinct classes, together with resource and configuration files. As we have already seen in Chapter 1, Android Studio helps in preparing and tailoring all the necessary files.

For the rest of this chapter and most of the following chapters we will be working on a simple game called the Number Guessing Game. Although extremely simple to understand, it is complex enough to show basic Kotlin language constructs and allows for extensions that help to illustrate most of the language features introduced during the course of the book. We thus neither start with the most elegant solution, nor do we show the most high-performance code from the beginning. The aim is to start with a working app and introduce new features step by step so we can improve our Kotlin language proficiency.

The game description goes as follows: At the beginning the user is presented some informational text and a Start button. Once started, the app internally chooses a random number between one and seven. The user is asked to guess that number and after each guess, the user is informed whether the guess matches, is too high, or is too low. Once the random number gets selected, the game is over and the user can start a new game.

To start app development, open Android Studio. If the last project you worked on is the HelloKotlin app from Chapter 1, the files from that app appear. To start a new project, from the menu select File ➤ New ➤ New Project. Enter NumberGuess as the application name, and book.kotlinforandroid as the company domain. Accept the suggested project location or choose your own. Make sure Include Kotlin support is selected. Click Next. Select Phone and Tablet as a form-factor, and API 19 as a minimum software development kit (SDK) version. Click Next again. Select Empty Activity and click Next. Accept the suggested activity name MainActivity and the suggested layout name activity_main. Make sure Generate Layout File and Backwards Compatibility are both selected. Click Finish.

Android Studio will now generate all build files and basic template files for the game app. Inside the res folder you will find a couple of resource files, including images and text that are used for the user interface. We don’t need images for now, but we define a couple of text elements that are used for both the layout file and the coding. Open the file res/values/strings.xml by double-clicking it. Let the file read:
<resources xmlns:tools="http://schemas.android.com/tools"
      tools:ignore="ExtraTranslation">
  <string name="app_name">
      NumberGuess</string>
  <string name="title.numberguess">
      NumberGuess</string>
  <string name="btn.start">
      Start</string>
  <string name="label.guess">
      Guess a number:</string>
  <string name="btn.do.guess">
      Do guess!</string>
  <string name="edit.number">
      Number</string>
  <string name="status.start.info">
      Press START to start a game</string>
  <string name="label.log">
      Log:</string>
  <string name="guess.hint">
      Guess a number between %1$d and %2$d</string>
  <string name="status.too.low">
      Sorry, too low.</string>
  <string name="status.too.high">
      Sorry, too high.</string>
  <string name="status.hit">
      You got it after %1$d tries!
      Press START for a new game.</string>
</resources>
The layout file is situated in res/layout/activity_main.xml. Open that file, switch to the text view by clicking the Text tab at the bottom of the center pane, and then as its contents write this:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
      xmlns:android=
        "http://schemas.android.com/apk/res/android"
      xmlns:tools=
        "http://schemas.android.com/tools"
      xmlns:app=
        "http://schemas.android.com/apk/res-auto"
      android:orientation="vertical"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:padding="30dp"
      tools:context=
        "kotlinforandroid.book.numberguess.MainActivity">
  <TextView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@string/title.numberguess"
          android:textSize="30sp" />
  <Button
          android:id="@+id/startBtn"
          android:onClick="start"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:text="@string/btn.start"/>
  <Space android:layout_width="match_parent"
      android:layout_height="5dp"/>
  <LinearLayout
          android:orientation="horizontal"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content">
      <TextView android:text="@string/label.guess"
              android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
      <EditText
              android:id="@+id/num"
              android:hint="@string/edit.number"
              android:layout_width="80sp"
              android:layout_height="wrap_content"
              android:inputType="number"
              tools:ignore="Autofill"/>
      <Button
              android:id="@+id/doGuess"
              android:onClick="guess"
              android:text="@string/btn.do.guess"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"/>
  </LinearLayout>
  <Space android:layout_width="match_parent"
      android:layout_height="5dp"/>
  <TextView
          android:id="@+id/status"
          android:text="@string/status.start.info"
          android:textColor="#FF000000" android:textSize="20sp"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"/>
  <Space android:layout_width="match_parent"
      android:layout_height="5dp"/>
  <TextView android:text="@string/label.log"
            android:textStyle="bold"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
  <kotlinforandroid.book.numberguess.Console
          android:id="@+id/console"
          android:layout_height="100sp"
          android:layout_width="match_parent" />
</LinearLayout>
You will get an error because the file refers to class kotlinforandroid.book.numberguess.Console, which does not exist yet. Ignore this for now; we are going to fix that soon. All the other elements of that layout file are described in depth in the Android developer documentation or an appropriate Android book. A few hints seem appropriate here, though.
  • If you don’t switch to the Text tab in the editor view for that file, the Design view type gets shown instead. The latter allows for graphically arranging user interface elements. We won’t use the graphical design editor in this book, but you are free to try that one as well. Just expect some minor differences in the resulting XML.

  • I don’t use fancy layout containers; instead I prefer ones that are easy to write and easy to understand when looking at the XML code. You don’t have to do the same for your projects, and in fact some other solutions might be better according to the circumstances, so you are free to try other layout approaches.

  • Wherever you see @string/... in the XML code, it refers to one of the entries from the strings.xml file.

  • The kotlinforandroid.book.numberguess.Console element refers to a custom view. You won’t see that too often in tutorials, but custom views allow for more concise coding and improved reusability, which means you could easily use them in other projects. The Console refers to a custom class we will write soon.

The Kotlin code goes into the file java/kotlinforandroid/book/numberguess/MainActivity.kt. Open this, and as its contents write:
package kotlinforandroid.book.numberguess
import android.content.Context
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.util.AttributeSet
import android.util.Log
import android.view.View
import android.widget.ScrollView
import android.widget.TextView
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
    var started = false
    var number = 0
    var tries = 0
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        fetchSavedInstanceData(savedInstanceState)
        doGuess.setEnabled(started)
    }
    override fun onSaveInstanceState(outState: Bundle?) {
        super.onSaveInstanceState(outState)
        putInstanceData(outState)
    }
    fun start(v: View) {
        log("Game started")
        num.setText("")
        started = true
        doGuess.setEnabled(true)
        status.text = getString(R.string.guess_hint, 1, 7)
        number = 1 + Math.floor(Math.random()*7).toInt()
        tries = 0
    }
    fun guess(v:View) {
        if(num.text.toString() == "") return
        tries++
        log("Guessed ${num.text} (tries:${tries})")
        val g = num.text.toString().toInt()
        if(g < number) {
            status.setText(R.string.status_too_low)
            num.setText("")
        } else if(g > number){
            status.setText(R.string.status_too_high)
            num.setText("")
        } else {
            status.text = getString(R.string.status_hit,
                tries)
            started = false
            doGuess.setEnabled(false)
        }
    }
    ///////////////////////////////////////////////////
    ///////////////////////////////////////////////////
    private fun putInstanceData(outState: Bundle?) {
        if (outState != null) with(outState) {
            putBoolean("started",  started)
            putInt("number", number)
            putInt("tries", tries)
            putString("statusMsg", status.text.toString())
            putStringArrayList("logs",
                ArrayList(console.text.split(" ")))
        }
    }
    private fun fetchSavedInstanceData(
          savedInstanceState: Bundle?) {
        if (savedInstanceState != null)
        with(savedInstanceState) {
            started = getBoolean("started")
            number = getInt("number")
            tries = getInt("tries")
            status.text = getString("statusMsg")
            console.text = getStringArrayList("logs")!!.
                  joinToString(" ")
        }
    }
    private fun log(msg:String) {
        Log.d("LOG", msg)
        console.log(msg)
    }
}
class Console(ctx:Context, aset:AttributeSet? = null)
      : ScrollView(ctx, aset) {
    val tv = TextView(ctx)
    var text:String
        get() = tv.text.toString()
        set(value) { tv.setText(value) }
    init {
        setBackgroundColor(0x40FFFF00)
        addView(tv)
    }
    fun log(msg:String) {
        val l = tv.text.let {
            if(it == "") listOf() else it.split(" ")
        }.takeLast(100) + msg
        tv.text = l.joinToString(" ")
        post(object : Runnable {
            override fun run() {
                fullScroll(ScrollView.FOCUS_DOWN)
            }
        })
    }
}
Don’t worry if by now you don’t understand everything in that file. In the rest of this chapter and in subsequent chapters we refer to this project a lot, and in the end you will understand all of it. For now here is what you need to know.
  • The package ... at the top of the file both defines a namespace for elements declared in that file and indicates its position in the file hierarchy. We will be talking about project structure later; for now it is enough to know that the argument should reflect the file position inside the java folder, with the dot . as a separator.

  • The file contains two classes. In other languages each class is supposed to go in its own file, and in fact you could move the declaration of the Console class to the file Console.kt. In Kotlin you can write as many declarations as you wish into one file. You should not overuse this feature, though, as writing too many things in one big file inevitably leads to messy code. For small projects and for simplicity’s sake, however, it is acceptable to put several declarations in a file.

  • The import ... statements refer to classes from other projects or classes built into Kotlin. Listing them in import statements allows us to address the imported elements using just their simple name. Otherwise you’d have to prepend their package name to use them. It is common practice to import as much as possible to keep the code readable.

  • The import statement kotlinx.android.synthetic.main.activity_main.* is special insofar it imports user interface-related classes the studio derived from the layout file. This has nothing to do with Kotlin; it is some automation controlled by Android Studio.

  • The properties var started = false, var number = 0, and var tries = 0 seem to miss the property types. However, Kotlin can automatically infer the type from the right-hand side of the assignments: false belongs to a boolean and both of the others belong to an integer. The :PropertyType can thus be left out here.

  • The class MainActivity : AppCompatActivity() { ... } declaration indicates that class MainActivity is derived from class AppCompatActivity, or inherits from it. We will be talking about inheritance in detail later; for now it is enough to know that MainActivity is kind of a copy of AppCompatActivity with some parts redefined.

  • The function onCreate() gets called by Android when the user interface gets created. Its parameter of type Bundle might or might not contain saved data from a restart of the user interface. This is something that happens often in Android apps, so we use that parameter to rebuild the state of the activity whenever the activity is restarted.

  • The onSaveInstanceState()gets called when the activity is suspended temporarily. We use it to save the state of the activity.

  • Both functions start() and guess() get invoked when the user clicks a button in the user interface. You can see that in the layout file. We use them as game actions and accordingly update the user interface and the activity object state.

  • Functions marked with private are only going to be used from inside the same class; they are not visible to the outside. We will be talking about visibility later. To stress that fact, I usually put all private functions at the end of a class and separate normal from private functions by a two-line comment //////....

  • The Console is a custom view object. It can be placed in any layout just like all the other built-in views Android provides.

  • For reasons of brevity, no in-line documentation was added. We return to documentation issues in later chapters.

You can now start the game. Click the green arrow in the top toolbar of Android Studio and choose an emulator or a connected hardware device to specify where to run the app.

Constructors

We already learned that parameters passed to a class when an instantiation happens get declared in parentheses after the class name:
class ClassName(Parameter-Declaration1,
        Parameter-Declaration2, ...) {
    [Class-Body]
}
We also know that parameters are accessible from inside any init{ } block and furthermore lead to creating properties if we prepend val or var to the parameter declaration:
Variable-Name:Variable-Type
for parameters that just are needed for the init{ } blocks,
val Variable-Name:Variable-Type
if you additionally want the parameter to be converted to an immutable property, and
var Variable-Name:Variable-Type

if you additionally want the parameter to be converted to a mutable property instead.

Such a parameter declaration list in Kotlin is called a primary constructor. As you might guess, there are secondary constructors as well. Let’s talk about primary constructors first, though, because they exhibit features we haven’t seen yet.

The full primary constructor declaration actually reads:
class ClassName [modifiers] constructor(
        Parameter-Declaration1,
        Parameter-Declaration2, ...)
{
    [Class-Body]
}
The constructor in front of the parameter list can be omitted (together with the space character) if there are no modifiers. As modifiers, you can add one of these visibility modifiers:
  • public: The instantiation can be done from anywhere inside and outside your program. This is the default.

  • private: The instantiation can be done only from inside the very same class or object. This makes sense if you use secondary constructors.

  • protected: The setting is the same as private, but the instantiation can be done from subclasses as well. Subclasses belong to inheritance, which is discussed in Chapter 3.

  • internal: The instantiation can be done from anywhere inside the module. In Kotlin, a module is a set of files compiled together. You use this modifier if you don’t want other programs (from other projects) to access a constructor, but you otherwise want the constructor to be freely accessible from other classes or objects inside your program.

Note

In other languages, constructors contain statements, or code to be executed on instantiation. The Kotlin designers decided to only name the parameters in the (primary) constructor and move any class initialization code to the init{ } block.

In our NumberGuess game the activity class MainActivity does not have a constructor. Actually, it implicitly has the default no-operation constructor, which doesn’t need to be declared. In fact, a specialty of Android is that activities should not have an explicit constructor. This has nothing to do with Kotlin, though; it is just the way Android handles the life cycles of its objects. The Console class instead does have a constructor. This is again a requirement of Android for its view elements.

Exercise 8

Create a class Person with constructor parameters: firstName (a String), lastName (a String), ssn (a String), dateOfBirth (a String) and gender (a Char). Make sure the parameters are later available as instance properties and are changeable afterward.

Constructor Invocation

In the previous section we already applied the main usage pattern: Given, for example, a class
class GameUser(val firstName:String,
      val lastName:String,
      val birthday:String,
      val userName:String,
      val registrationNumber:Int,
      val userRank:Double) {
}
you can instantiate the class via
...
val firstUser = GameUser("Richard", "Smith",
    "2008-10-23", "rsmith", 123, 0.0)

You can see that for this kind of instantiation you have to specify the parameters in exactly the same order as in the class definition.

Exercise 9

Instantiate the Person class from the previous exercise, using name John Smith, date of birth 1997-10-23, SSN 0123456789, and gender M. Assign it to variable val person1. Hint: Use single quotation marks for Char literals, like 'A' or 'B'.

Exercise 10

Add the GameUser class we talked about in this section to the NumberGuess game. Just add the class for now; do not write code to include the user in the game logic.

Named Constructor Parameters

There is actually a way to construct objects in a more readable and less error-prone fashion, compared to just listing parameters in the same order as given in the declaration. For instantiation you can also explicitly specify the parameter names and then apply any order at will:
val instance = TheClass(
    parameterName1 = [some value],
    parameterName2 = [some value],
    ...)
For our GameUser from the last exercise you can write
...
val user = GameUser(
    lastName = "Smith",
    firstName = "Richard",
    birthday = "2098-10-23",
    userName = "rsmith",
    registrationNumber = 765,
    userRank = 0.5)

With the names given, the sort order of the call parameters no longer plays a role. Kotlin knows how to properly distribute the passed-in parameters.

Exercise 11

Rewrite the Person instantiation from Exercise 9 using named parameters.

Exercise 12

Add a var gameUser property to the MainActivity and initialize it with the name John Doe, username jdoe, birthday 1900-01-01, registration number = 0 and user rank = 0.0. Use named parameters. Hint: To initialize the property right in the declaration use var gameUser = GameUser(...).

Constructor Default Values

Constructor parameters can also have default values. We could, for example, use ”” as a default birthday, and 0.0 as a rank in case we wouldn’t care. This simplifies the construction of game users who don’t specify a birthday, and new users, for example, with an initial ranking of 0.0. To declare such defaults you write:
class GameUser(val firstName:String,
      val lastName:String,
      val userName:String,
      val registrationNumber:Int,
      val birthday:String = "1900-01-01",
      val userRank:Double = 0.0) {
}
If you use parameters with and without defaults, such default values go frequently to the end of the parameter list. Only then is the distribution of passed-in parameters during invocation unique. You can now perform the very same construction as before, but watch out for the changed order:
...
val firstUser = GameUser("Richard", "Smith", "rsmith", 123, "2008-10-23", 0.4)
Now, by virtue of the default parameters, it is possible to omit parameters. In
...
val firstUser = GameUser("Richard", "Smith", "rsmith", 123, "2008-10-23")
the value 0.0 would apply for the ranking, and in
...
val firstUser = GameUser("Richard", "Smith", "rsmith", 123)

additionally the default birthday of 1900-01-01 would be used.

To make things even easier and extend readability further you can also mix default and named parameters, as in
...
val firstUser = GameUser(firstName = "Richard",
    lastName = "Smith",
    userName = "rsmith",
    registrationNumber = 123)

this time with any parameter sort order you like.

Exercise 13

Update the Person class from the previous exercises: add the default value ”” (the empty string) to the ssn parameter. Perform an instantiation using named parameters, letting the SSN’s default value apply.

Exercise 14

Update the GameUser class from the NumberGuess game: Add the default value ”” (the empty string) to birthday, and add 0.0 to the userRank parameter.

Secondary Constructors

With named parameters and default parameter values, we already have quite versatile means for various construction needs. If this is not enough for you, there is another way of describing different methods of construction: secondary constructors. You can have several of them, but their parameter list must differ from that of the primary constructor and they must also be different from each other.

Note

More precisely, primary and secondary constructors all must have different parameter signatures. A signature is the set of parameter types, with the order taken into account.

To declare a secondary constructor, inside the class body write
constructor(param1:ParamType1,
            param2:ParamType2, ...)
{
    // do some things...
}
If the class has an explicit primary constructor as well, you must delegate to a primary constructor call as follows:
constructor(param1:ParamType1,
            param2:ParamType2, ...) : this(...) {
    // do some things...
}

where inside this(...) the parameters for the primary constructor have to be specified. It is also possible here to specify the parameters for another secondary constructor, which in turn delegates to the primary constructor.

For our GameUser example, removing the default parameter values from the primary constructor, a secondary constructor could read like this:
constructor(firstName:String,
            lastName:String,
            userName:String,
            registrationNumber:Int) :
      this(firstName = firstName,
           lastName = lastName,
           userName = userName,
           registrationNumber = registrationNumber,
           birthday = "",
           userRank = 0.0
      )
{
    // constructor body
    // do some things...
}
and you can instantiate the class via
...
val firstUser = GameUser(firstName = "Richard",
     lastName = "Smith",
     userName = "rsmith",
     registrationNumber = 123)

Inside the secondary constructor’s body you can perform arbitrary calculations and other actions, which is what secondary constructors can be used for except for different, maybe shorter parameter lists.

The construct firstName = firstName, lastName = lastName, userName = userName, registrationNumber = registrationNumber might seem a bit confusing. It is easy to understand, however, if you remember that the part to the left of the equals sign points to the name in the primary constructor’s parameter list, whereas the right side is the value taken from inside the constructor(...) parameter list.

Note

If you can achieve the same thing using default values and secondary constructors, you should favor default values because the notation is more expressive and concise.

Exercise 15

In the Person class of the previous exercises, add a secondary constructor with parameters firstName (a String), lastName (a String), ssn (a String), and gender (a Char). Let it call the primary constructor, setting the missing dateOfBirth to 0000-00-00. Create an instance using the secondary constructor.

If Classes Are Not Needed: Singleton Objects

Once in a while objects don’t need a classification because you know there will never be different states associated with them. Here is another way of saying this: If we have a class, there will never be more than one instance needed, because all instances would somehow be forced to carry the same state during the lifetime of the application and thus would be indistinguishable.

To make things clear, Kotlin allows for creating such an object using the following syntax:
object ObjectName { [Object-Body]
}

where the object body could contain property declarations, init{ } blocks, and functions. Neither primary nor secondary constructors are allowed. To distinguish this kind of object from objects that are the result of class instantiations for the rest of the section, I use the term singleton object.

To access a singleton object’s properties and functions you use a similar notation as for objects that are the result of a class’s instantiation:
ObjectName.propertyName
ObjectName.function([function-parameters])
You won’t use singleton objects too often, because object orientation without classes wouldn’t make too much sense, and using too many singleton objects quite often is an indication of poor application design. There are, however, some prominent examples where object declarations make sense:
  • Constants: For your application you might want to have a single object containing all constants the application needs.

  • Preferences: If you have a file with preferences you might want to use an object to read in the preferences once the application has started.

  • Database: If your application needs a database and you think your application will never access a different database, you might want to move database access functions into an object.

  • Utilities: Utility functions are functional in a sense that their output only depends on their input and no state is associated; for example, fun degreeToRad(deg: Double) = deg * Math.PI / 180. They also serve a common purpose and adding them to certain classes doesn’t make sense from a conceptual point of view. Providing such utility functions in a singleton object, for example named Utility, thus is reasonable.

Other use cases are possible; just make sure your decision to use classes or singleton objects is based on sound reasoning. If in doubt, experience tells us that using classes makes more sense.

For our NumberGuess game, looking into the file MainActivity.kt we can see that we use numbers 1 and 7 for the lower and upper bounds of the game logic. The numbers get used in the function fun start(...) for the text shown in the user interface, and for the random number determination:
status.text = getString(R.string.guess_hint, 1, 7)
number = 1 + Math.floor(Math.random()*7).toInt()
It is better to extract such constants to their own file, so it can more easily be changed later or used from within other classes if necessary. A Constants singleton object seems to be a very appropriate place for it. To improve the code, we create a new file via right-click in the project view at package kotlinforandroid.book.numberguess ➤ New ➤ Kotlin File/Class. Enter Constants as a name and make sure File is selected on the drop-down list. Inside the file that is created, underneath the package declaration, write
object Constants {
    val LOWER_BOUND = 1
    val UPPER_BOUND = 7
}

We again omitted the property types because Kotlin can infer that 1 and 7 are Int types.

Note

This autoinferring works for other types as well, so a common practice is to leave out the type specification and add it only if it is needed or helps to improve readability.

There is one other thing you might have noticed: We deviated from the naming schema for the val inside the companion object. Using this all-capitals with underscore notation expresses that we have a real immutable instance-independent constant. Such constants are thus easier to identify from inside your coding.

Back in MainActivity.kt, inside the start() function , we can now write
status.text = getString(R.string.guess_hint,
      Constants.LOWER_BOUND,
      Constants.UPPER_BOUND)
val span = Constants.UPPER_BOUND -
      Constants.LOWER_BOUND + 1
number = Constants.LOWER_BOUND +
      Math.floor(Math.random()*span).toInt()
for the user interface text and the secret number. The function then reads in total:
fun start(v: View) {
    log("Game started")
    num.setText("")
    started = true
    doGuess.setEnabled(true)
    status.text = getString(R.string.guess_hint,
          Constants.LOWER_BOUND,
          Constants.UPPER_BOUND)
    val span = Constants.UPPER_BOUND -
          Constants.LOWER_BOUND + 1
    number = Constants.LOWER_BOUND +
          Math.floor(Math.random()*span).toInt()
    tries = 0
}

Exercise 16

Which of the following is true?
  1. 1.

    Using a lot of singleton objects helps to improve code quality.

     
  2. 2.

    It is possible to instantiate singleton objects.

     
  3. 3.

    To declare singleton objects, use any of object, singleton, or singleton object.

     
  4. 4.

    Singleton objects don’t have a state.

     
  5. 5.

    Singleton objects may have a constructor.

     

Exercise 17

Create a Constants singleton object with the following properties: numberOf- Tabs = 5, windowTitle = "Astaria", prefsFile = "prefs.properties". Write some code to print out all constants for diagnostic purposes. Hint: For formatting you could use inside strings for a line break.

If State Doesn’t Matter: Companion Objects

Quite often, perhaps without even noticing it, your classes will have two categories of properties and functions: state related and not state related. Not state related means for properties that their value will be the same for all possible instances. For functions it means they will do exactly the same thing for all possible instances. This is somehow related to singleton objects, which do not care about a distinguishable state at all, and for that reason Kotlin allows for a construct named the companion object. Such companion objects have an indistinguishable state for all instances of a particular class they accompany, and this is where the “companion” in the name comes from.

To declare a companion object inside the class body, write this:
companion object ObjectName {
    ...
}

where the ObjectName is optional; in most cases you can omit it. Inside the companion object’s body you can add the same elements as for singleton objects (see the previous section).

Note

You need the companion object to have a name only if you want to address it from outside the class, using a dedicated name: ClassName.ObjectName. However, even with the name missing you can access it via ClassName.Companion.

A companion object is a really good place to declare constants used by the class. You can then use the constants from anywhere inside the class as if they were declared in the class itself:
class TheClass {
    companion object ObjectName {
        val SOME_CONSTANT: Int = 42
    }
    ...
    fun someFunction() {
        val x = 7 * SOME_CONSTANT
        ...
    }
}

In our NumberGuess game there are two constants in the Console class: Look at the init{ } function where we specify a color value 0x40FFFF00 for the background color (this is a pale yellow). Also, in the function fun log(...) you can see a 100, which happens to specify a memorized line number limit. I intentionally left these out for the Constants companion object, as those two new constants can be considered to more closely belong to the Console class and maybe are misplaced in a common constants file.

It is, however, a good idea to move them to a companion object, because both the color and the line number limit values are shared by all instances of the Console class and are not subject to being changed from inside an instance. An accordingly rewritten Console class reads:
class Console(ctx:Context, aset:AttributeSet? = null)
      : ScrollView(ctx, aset) {
  companion object {
      val BACKGROUND_COLOR = 0x40FFFF00
      val MAX_LINES = 100
  }
  val tv = TextView(ctx)
  var text:String
      get() = tv.text.toString()
      set(value) { tv.setText(value) }
  init {
      setBackgroundColor(BACKGROUND_COLOR)
      ddView(tv)
  }
  fun log(msg:String) {
      val l = tv.text.let {
        if(it == "") listOf() else it.split(" ") }.
              takeLast(MAX_LINES) + msg
      tv.text = l.joinToString(" ")
      post(object : Runnable {
          override fun run() {
                fullScroll(ScrollView.FOCUS_DOWN)
          }
      })
  }
}
Companion object properties and functions can also be accessed from outside the class. Just write this:
TheClass.THE_PROPERTY
TheClass.someFunction()

to directly address a property or a function from the associated companion object. The function can, of course, also have parameters.

Exercise 18

Create a Triangle class. Add constructor parameters and properties at will, but also create a companion object with a constant NUMBER_OF_CORNERS = 3. Inside the class, create an info() function indicating the number of corners.

Exercise 19

Inside a main() function, instantiate the Triangle class from Exercise 18, then assign the number of corners to some val numberOfCorners.

Describing a Contract: Interfaces

Software development is about things that need to be done, and in object-oriented development, this means things that need to be done on objects that are described inside classes. Object orientation, however, unveils a feature we haven’t talked about until now: the separation of intent and implementation.

Consider, for example, a class or a couple of classes gathering information on two-dimensional graphical objects, and another class or couple of classes providing such graphical objects. This introduces a natural separation of classes. We call the information collecting part of the classes the info collector module, and the part that provides the graphical objects the client module. We want to extend that idea by allowing several client modules, and in the end we want to make sure the info collector module wouldn’t care how many clients there are (see Figure 2-1).
../images/476388_1_En_2_Chapter/476388_1_En_2_Fig1_HTML.jpg
Figure 2-1.

Collector module and clients

Note

We deviate from the usual path and temporarily leave the NumberGuess game. The concept of interfaces is a little easier to describe if we have several classes sharing some features, which the NumberGuess game doesn’t have. I will, however, propose a possible extension to the NumberGuess game using an interface in one of the exercises.

The most important question now is this: How do the graphics objects get communicated between the modules? Here is one obvious idea: Because the clients produce the graphics objects, why not also let the clients provide the classes for them? At first that doesn’t sound bad, but there is a major drawback: The info collector module needs to know how to handle each client’s graphics object classes, and it also needs to be updated when new clients want to transfer their objects. Such a strategy is thus not flexible enough for a good program.

Let us try to turn it the other way around: The info collector module provides all graphics object classes and the clients use them to communicate data. Although this remedies the proliferation of different classes in the collector module, there is a different problem with this approach. Say, for example, the info collector gets a software update and provides an altered version for a graphics object class. If this happens, we must also update all clients, leading to a lot of work, including increased expenses in professional projects. So this approach is not the best either. What can we do?

We can introduce a new concept that does not describe how things are to be done, but only what needs to be done. This somehow mediates between the different program components and for this reason it is called an interface. If such an interface does not depend on the implementation and clients only depend on interfaces, the probability for a need to change clients is much lower if the info collector changes. You can also consider the interface as some kind of contract between the parties: Just like in real life, if the wording in a contract is satisfied the contract is fulfilled even when the way it is done is subject to some kind of diversity.

Before I can further explain this, let’s work out the details of the graphics collector example a little more. We add the following responsibility to the graphics collector: The graphics collector must be able to take polygon objects that do the following:
  • Tell about the number of corners they have.

  • Tell us the coordinates of each corner.

  • Tell about their fill color.

You are free to extend this at will, but for our aim those three characteristics are sufficient. We now introduce an interface declaration and write this:
interface GraphicsObject {
    fun numberOfCorners(): Int
    fun coordsOf(index:Int): Pair<Double, Double>
    fun fillColor(): String
}
The Pair<Double, Double> represents a pair of floating-point numbers for the x- and y-coordinate of a point. We let the graphics collector module define the interface, because the interface is what the clients need to know from the graphics collector module to communicate with it. The implementation of the three functions is, however, exclusively the clients’ business, because for the graphics collector module the how of the contract fulfillment doesn’t matter. The interface itself, though, is just a declaration of intent, so the client modules have to define what to do to fulfill the contract. Another way of saying this is the clients have to implement the interface functions. This new situation is depicted in Figure 2-2.
../images/476388_1_En_2_Chapter/476388_1_En_2_Fig2_HTML.jpg
Figure 2-2.

Module communication with interfaces

For example, for a triangle a client might provide this:
class Triangle : GraphicsObject {
    override fun numberOfCorners(): Int {
       return 3
    }
    override fun coordsOf(index:Int):
          Pair<Double,Double> {
        return when(index) {
            0 -> Pair(-1.0, 0.0)
            1 -> Pair(1.0, 0.0)
            2 -> Pair(0.0, 1.0)
            else throw RuntimeException(
                "Index ${index} out of bounds")
        }
    }
    override fun fillColor(): String {
        return "red"
    }
}
For Kotlin it is permissible to write “ = ...” for functions if their result is calculable with a single expression, so the Triangle class can actually be written as follows:
class Triangle : GraphicsObject {
    override fun numberOfCorners() = 3
    override fun coordsOf(index:Int) =
        when(index) {
            0 -> Pair(-1.0, 0.0)
            1 -> Pair(1.0, 0.0)
            2 -> Pair(0.0, 1.0)
            else -> throw RuntimeException(
                "Index ${index} out of bounds")
        }
    override fun fillColor() = "red"
}

where we also used the fact that Kotlin can infer the return type automatically in many cases. The : GraphicsObject in the class declaration expresses that Triangle adheres to the GraphicsObject contract, and the override in front of each function expresses that the function implements an interface function. The Triangle class, of course, also might contain any number of noninterface functions; we just don’t need one in this example.

Note

The : in the class header can be translated to “implements” or “is a ...” if there is an interface name to the right of it.

Inside the coordsOf() function we use a couple of new constructs we haven’t seen yet. For now, the when(){ } selects one of the x -> ... branches depending on the argument, and the throw RuntimeException() stops the program flow and writes an error message to the terminal. We’ll talk about those constructs in more detail in subsequent chapters.

Note

You can see that for the Triangle example we allow corner indexes 0, 1, and 2. It is common in many computer languages to start any kind of indexing at 0. Kotlin is no exception here.

We still need the accessor function inside one of the collector module’s classes that a client needs to register a graphics object. We call it add() and it could read like this:
class Collector {
    ...
    fun add(graphics:GraphicsObject) {
        // do something with it...
    }
}
The clients now write something like this:
...
val collector = [get hold of it]
val triang:GraphicsObject = Triangle()
collector.add(triang)
...

We could have also written val triang:Triangle = Triangle() and the program would run without error. There is, however, a huge conceptual difference between those two. Can you tell why? The answer is this: If we write val triang:Triangle = Triangle() we express passing the Triangle class to the collector, which is what we actually didn’t want to do. This is because we wanted to have a proper separation of the clients from the collector and use only the interface GraphicsObject for communication. The only acceptable way to express this is writing val triang:GraphicsObject = Triangle().

Note

Internally the same object gets passed to the collector if we write either triang:Triangle or triang:GraphicsObject. But we don’t only want to write programs that work; they must also properly express what they do. For that reason, triang:GraphicsObject is the much better option.

To get you started for your own experiments, in the following listings I provide a basic implementation of this interfacing procedure. First, in one file, we write a graphics object collector and also add the interface.
interface GraphicsObject {
    fun numberOfCorners(): Int
    fun coordsOf(index:Int): Pair<Double, Double>
    fun fillColor(): String
}
object Collector {
    fun add(graphics:GraphicsObject) {
        println("Collector.add():")
        println("Number of corners: " +
            graphics.numberOfCorners())
        println("Color: " +
            graphics.fillColor())
    }
}
You can see we use a singleton object here to simplify access. In another file we create a GraphicsObject and access the collector.
class Triangle : GraphicsObject {
    override fun numberOfCorners() = 3
    override fun coordsOf(index:Int) =
        when(index) {
            0 -> Pair(-1.0, 0.0)
            1 -> Pair(1.0, 0.0)
            2 -> Pair(0.0, 1.0)
            else -> throw RuntimeException(
                "Index ${index} out of bounds")
        }
    override fun fillColor() = "red"
}
fun main(args:Array<String>) {
    val collector = Collector
    val triang:GraphicsObject = Triangle()
    collector.add(triang)
}

You can see that it is possible to assign a singleton object to a val, although you could always also use the direct singleton object access notation described earlier in this chapter.

Although the concept of interfaces is not easy to grasp for a beginning developer, trying to understand interfaces from the very beginning and using them wherever possible is an invaluable aid for writing good software.

Exercise 20

Elementary particles have at least three things in common: a mass, a charge, and a spin. Create an interface ElementaryParticle with three corresponding functions to fetch: mass():Double, charge():Double, and spin():Double. Create classes Electron and Proton that implement the interface. An electron returns mass 9.11 · 10-31, to be entered as 9.11e-31, charge −1.0, and spin 0.5. A proton returns mass 1.67·10-27, to be entered as 1.67e-27, charge and spin 0.5.

Exercise 21

Taking the interface and the classes from Exercise 20, which one is true?
  1. 1.

    An ElementaryParticle can be instantiated: var p = ElementaryParticle().

     
  2. 2.

    An Electron can be instantiated: val electron = Electron().

     
  3. 3.

    A Proton can be instantiated: val proton = Proton().

     
  4. 4.

    The initialization var p:ElementaryParticle = Electron() is possible.

     
  5. 5.

    The reassignment p = Proton() is possible.

     
  6. 6.

    The initialization var p:Proton = Electron() is possible.

     

Exercise 22

Imagine for the NumberGuess game we want to be able to try different functions of random number generation. Create an interface RandomNumberGenerator with one function fun rnd(minInt:Int, maxInt:Int): Int. Create a class StdRandom implementing that interface using the current code from the MainActivity class: val span = maxInt - minInt + 1; return minInt + Math.floor(Math.random()*span).toInt(). Create another class RandomRandom also implementing the interface, but with a property val rnd:Random = Random() (add import java.util.* to the imports) and using the code minInt + rnd.nextInt( maxInt - minInt + 1 ). Add a property of type RandomNumberGenerator to the activity, using either of the implementations. Alter the start() function from the activity to use that interface.

Structuring and Packages

For Kotlin applications it is possible to write all classes, interfaces, and singleton objects into a single file in the main folder java. Whereas for experiments and small projects this is totally acceptable, for larger projects you shouldn’t do this. Midsize to larger projects will inevitably have classes, interfaces, and singleton objects that can be grouped into modules doing different things from a bird’s-eye view perspective. Having large files implies some kind of conceptual flatness real projects don’t actually have.

Note

To avoid always repeating the list, I henceforth use the term structure unit for classes, singleton objects, companion objects, and interfaces.

For this reason Kotlin allows us to put structure units into different packages corresponding to different folders and spanning different namespaces. The first thing we need to establish is a hierarchical structure. This means we assign structure units to different nodes in a tree. Each node thus contains a couple of structure units that show high cohesion, meaning they strongly relate to each other.

A Structured Project

Let’s look at the NumberGuess example to find out what this structuring actually means. Up until now, including all the improvements and also the exercises, we have the following classes, interfaces, and singleton objects: the activity itself, a console class, a constants object, two classes and one interface for random numbers, and one class for user data. From this we identify the following packages:
  • The root for the activity class.

  • A package random for the random numbers. We put the interface right into the package, and the two implementations into a subpackage impl.

  • A gui package for the Console view element.

  • A model package for the user data class. Developers often use the term model to refer to data structures and data relations.

  • A common package for the Constants singleton object.

We put this in corresponding directories and subdirectories under src and thus get the packages and folder structure depicted in Figure 2-3.
../images/476388_1_En_2_Chapter/476388_1_En_2_Fig3_HTML.jpg
Figure 2-3.

Packaging

As a convention, you must add a package declaration into each of the files reflecting this packaging structure. The syntax is:
package the.hierarchical.position
...
So, for example, the RandomRandom.kt file must start with
package kotlinforandroid.book.numberguess.random.impl
class RandomRandom {
    ...
}

Exercise 23

Prepare this structure in an Android Studio project. Start with empty files. Hint: Packages (i.e., folders), classes, interface, and singleton objects all can be initialized by right-clicking an item from the left side package structure in the Android Studio main window and selecting New.

Namespaces and Importing

As already mentioned, the hierarchical structure also spans namespaces. For example, the Console class lives by virtue of the kotlinforandroid.book.numberguess.gui package declaration in the kotlinforandroid.book.numberguess.gui namespace. This means there cannot be another Console class in the same package, but there can be Console classes in other packages, because they all have a different namespace.

Caution

Kotlin allows you to use a package declaration that differs from the hierarchical position in the file system. However, do yourself a favor and keep packages and file paths synchronized, otherwise you’ll end up with a complete mess.

Structure units (i.e., classes, interfaces, singleton objects, and companion objects) can use other structure units from the same package by just using their simple names. If they use structure units from other packages, though, they must use their fully qualified name, which means it is necessary to prepend the package name with dots as separators. The fully qualified name for Console, for example, reads kotlinforandroid.book.numberguess.gui.Console. There is, however, a way to avoid typing lots of long names to refer to structure units from other packages: As a shortcut, you can import the referred-to structure unit by using an import statement. We have already seen that in a couple of examples, without further explaining it. To import the Console class, for example, you write directly under the package declaration:
package kotlinforandroid.book.numberguess
import kotlinforandroid.book.numberguess.gui.Console
class Activity {
    ...
}
In this case and anywhere in this file you can just use Console to address the kotlinforandroid.book.numberguess.gui.Console class. A file can have any number of such import statements. To also import the Constants class, write this:
package kotlinforandroid.book.numberguess
import kotlinforandroid.book.numberguess.gui.Console
import kotlinforandroid.book.numberguess.common.
       Constants
class Activity {
    ...
}

Note

IDEs like Android Studio provide help to do these imports for you. If you type a simple name Android Studio tries to determine what package might be intended. You can then press Alt+Enter with the mouse over the name to perform the import.

There is even a shortcut for importing all structure units from a package by using an asterisk (*) as a wildcard. So, for example, to import all the classes from package kotlinforandroid.book.numberguess.random.impl, you would write
package kotlinforandroid.book.numberguess
import kotlinforandroid.book.numberguess.
       random.impl.*
class Activity {
    ...
}

You can see the common root of all packages of the NumberGuess game reads kotlinforandroid.book.numberguess. Android Studio did that while we initialized the project. It is common practice to prepend a reverse domain name that points to you as a developer, or your educational institution or your company, plus a name for your project. For example, if you own a domain john.doe.com and your project is named elysium, you would use com.doe.john.elysium as your root package.

Note

There is no actual need for such a domain to exist. If you can’t use an existing domain, you can use a made-up one. Just make sure the probability of clashes with existing projects is low. If you don’t plan to ever publish your software, you can use what you want, including not using a domain root at all.

Exercise 24

Distribute all the code we have for the NumberGuess game into the files of the new structure from the previous section.

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

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