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.
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.
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.
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.
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
Property Declaration
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
Exercise 3
How can it be fixed?
Class Initialization
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.
Exercise 4
Exercise 5
An Invoice in Kotlin
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
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
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
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
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.
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.
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.
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
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.
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 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
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.
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
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
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
additionally the default birthday of 1900-01-01 would be used.
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.
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.
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.
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.
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.
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.
Exercise 16
- 1.
Using a lot of singleton objects helps to improve code quality.
- 2.
It is possible to instantiate singleton objects.
- 3.
To declare singleton objects, use any of object, singleton, or singleton object.
- 4.
Singleton objects don’t have a state.
- 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.
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.
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.
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.
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.
Tell about the number of corners they have.
Tell us the coordinates of each corner.
Tell about their fill color.
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 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.
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
- 1.
An ElementaryParticle can be instantiated: var p = ElementaryParticle().
- 2.
An Electron can be instantiated: val electron = Electron().
- 3.
A Proton can be instantiated: val proton = Proton().
- 4.
The initialization var p:ElementaryParticle = Electron() is possible.
- 5.
The reassignment p = Proton() is possible.
- 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
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.
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.
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.
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.