Chapter 5

Object-Oriented Programming in Kotlin

IN THIS CHAPTER

check Using classes with finesse

check Working with Kotlin’s classes and interfaces

check Being part of Kotlin’s inner circle

check Putting your eggs (your file, that is) in a basket

Kotlin can work in the object-oriented programming (OOP) paradigm, much as Java does. It’s not limited to OOP, though. If your heart so desires, you can use it as a functional programming language, too. Chapter 3 in this minibook covers the highlights of both functional and OOP in Kotlin. That chapter focuses on the object-oriented nature of Android development, though, because that’s the topic of this book.

There are significant differences between Java and Kotlin in OOP implementation, so even if you’re familiar with Java OOP, read this chapter to prepare for future chapters on Android programming using Kotlin. This chapter covers some of object-oriented programming's finer points.

Static Fields and Methods

Listing 5-1 reproduces a small portion of the source code of Android's Toast class from Toast.java. (To open the file in Android Studio, right-click any Toast entry in your code and choose Go To⇒  Declaration from the context menu.) This class relies on Java even when you're using it from Kotlin, so the underlying class uses Java principles. Using the class differs in Kotlin when compared to Java. (For comparison purposes, you can read about the Kotlin version of this class at https://developer.android.com/reference/kotlin/android/widget/Toast.html and the Java version of this class at https://developer.android.com/reference/android/widget/Toast.html.)

LISTING 5-1: An Unrepresentative Sample of Android's Toast Class Code

public class Toast {


public static final int LENGTH_LONG = 1;


public static Toast makeText(Context context,
CharSequence text,
int duration) {
Toast result = new Toast(context);

LayoutInflater inflate = (LayoutInflater) context.
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflate.inflate
(com.android.internal.
R.layout.transient_notification, null);
TextView tv = (TextView)v.findViewById
(com.android.internal.R.id.message);
tv.setText(text);

result.mNextView = v;
result.mDuration = duration;

return result;
}

public void show() {
if (mNextView == null) {
throw new RuntimeException
("setView must have been called");
}

INotificationManager service = getService();

String pkg = mContext.getPackageName();

TN tn = mTN;

try {
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
}

According to the code in Listing 5-1, the Toast class has a static field named LENGTH_LONG and a static method named makeText. Anything that's declared to be static belongs to the whole class, not to any particular instance of the class. When you create the static field, LENGTH_LONG, you create only one copy of the field. This copy stays with the entire Toast class. No matter how many instances of the Toast class you create — one, nine, or none — you have just one LENGTH_LONG field.

Remember Kotlin doesn't support static fields (as described in Chapter 2 of this minibook). However, you can create an equivalent of a static field when necessary. Here is the equivalent of the LENGTH_LONG field in Kotlin:

companion object {
const val LENGTH_LONG = 1
}

Contrast this situation with the one in Chapter 3 of this minibook. In that chapter, the Account class has fields called name, address, and balance. The fields aren't static, so every instance of the Account class has its own name, its own address, and its own balance. One instance has name Barry Burd and balance 24.02, and another instance has name John Q. Public with balance –471.03. To refer to Burd's balance, you may write something like

val myAccount = Account()

myAccount.name = "Burd"
myAccount.address = "222 Cyberspace Lane";
myAccount.balance = 24.02

To refer to a non-static member of a class, you write the name of an object (such as myAccount), followed by a dot and then the name of the member (such as balance).

But the Toast class's LENGTH_LONG field is static. When you create a Toast instance, you don't create a new LENGTH_LONG field. The Toast class has one LENGTH_LONG field, and that's that. Accordingly, you refer to LENGTH_LONG by prefacing the field name with the Toast class name, followed by a dot:

Toast.LENGTH_LONG

In fact, a typical use of Toast in an Android app refers to the static field LENGTH_LONG and the static method makeText:

Toast.makeText
(getApplication(), "Whoa!", Toast.LENGTH_LONG).show();

A call to the Toast class's makeText method returns an actual object — an instance of the Toast class. (You can verify this by referring to the first line of the makeText method in Listing 5-1, earlier in the chapter.) So in an Android app, an expression such as

Toast.makeText
(getApplication(), "Whoa!", Toast.LENGTH_LONG)

stands for an object. And (again, according to Listing 5-1) each object created from the Toast class has its own non-static show() method. That's why you normally follow a Toast.makeText call with .show().

Here's one final word about Listing 5-1: In addition to being static, the LENGTH_LONG field is also final. A final field is one whose value cannot be changed. In other words, when you declare LENGTH_LONG, you can initialize its value to 1 (as in Listing 5-1). But elsewhere in the code, you can't write LENGTH_LONG = 2. (For that matter, you can't even write LENGTH_LONG = 1 elsewhere in the code.)

Technical Stuff Many programming languages, including Kotlin, use the word constant (or the abbreviation const) to refer to a variable whose value cannot be changed.

Interfaces and Callbacks

Because you'll be looking at both Kotlin and Java interfaces when working through your Android code, this section presents both. Listing 5-2 contains a snippet from Android's pre-declared Java code. The listing contains a Java interface.

LISTING 5-2: Android's OnClickListener Interface

public interface OnClickListener {
void onClick(View v);
}

The Kotlin version of this same interface (which you won’t find in the underlying libraries) looks like this:

interface OnClickListener {
fun onClick(View v)
}

An interface is like a class, but it’s different. (So, what else is new? A cow is like a planet, but it’s quite a bit different. Cows moo; planets hang in space.) Anyway, when you hear the word interface, you can start by thinking of a class. Then, in your head, note the following things:

  • A class doesn't extend an interface; instead, a class implements an interface. Later in this chapter, you can see the following line of code:

    class MyListener implements OnClickListener

    To implement the same interface using Kotlin, you use

    class MyListener: OnClickListener

  • A class can extend only one parent class, but a class can implement more than one interface. For example, if you want MyListener objects to listen for long clicks as well as regular clicks, you can write

    class MyListener implements OnClickListener,
    OnLongClickListener {

    A long click is what nondevelopers would probably call a touch-and-hold motion.

    Technical Stuff Kotlin doesn’t allow true multiple inheritance, so you can still inherit from just one parent class. However, by using a fancy feature called class delegation, in which you delegate handling of inherited features to another class, you can create something that looks a lot like multiple inheritance but doesn’t come with the problems that multiple inheritance can create. Covering class delegation is outside the scope of this book, but you can read about it at https://sites.google.com/a/athaydes.com/renato-athaydes/posts/solutionstomultipleinheritanceinkotlin if you really want to know how to do something fancy.

    As with Java, you can implement multiple interfaces in Kotlin with ease. Here is the Kotlin version of the Java example that appeared earlier in this bullet:

    class MyListener: OnClickListener, OnLongClickListener {

  • An interface can extend another interface. For example, in the following line of code, a homegrown interface named SomeListener extends Android's built-in OnClickListener interface:

    public interface SomeListener extends OnClickListener {

    The Kotlin version of the same code looks like this:

    interface SomeListener: OnClickListener {

  • An interface can extend more than one interface.
  • An interface's methods have no bodies of their own. In Listing 5-2, the onClick method has no body — no curly braces and no statements to execute. In place of a body, there’s just a semicolon (just the function declaration in Kotlin).

    Technical Stuff A method with no body, like the method defined in Listing 5-2, is an abstract method.

    Technical Stuff Starting with Java 8, a method declared inside an interface can have a body. A method of this kind is called a default method. As an Android developer, you can use these features, but only when you configure your project correctly. To do this, you update your build.gradle (Module:app) by adding a compileOptions block as well as JavaVersion.VERSION_1_8, as shown here:

    compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
    }

    When working with Kotlin, you don't have to worry about any changes to support default methods.

  • When you implement an interface, you provide bodies for all the interface’s methods. That’s why the MyListener class in Listing 5-3 has an onClick() method. By announcing that it will implement the OnClickListener interface, the MyListener class agrees that it will give meaning to the interface's onClick() method. In this situation, giving meaning means declaring an onClick() method with curly braces, a body, and maybe some statements to execute.

LISTING 5-3: Implementing Android's OnClickListener Interface in Java

package com.allmycode.samples;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MyActivity extends Activity {

Button button;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

button = ((Button) findViewById(R.id.button1));

button.setOnClickListener(new MyListener(this));
}
}

class MyListener implements OnClickListener {
Activity activity;

MyListener (Activity activity) {
this.activity = activity;
}

@Override
public void onClick(View arg0) {
((MyActivity) activity).button.setBackgroundColor
(android.graphics.Color.GRAY);
}
}

Listing 5-3 doesn't illustrate the most popular way to implement the OnClickListener interface, but the listing presents a straightforward use of interfaces and their implementations. The Kotlin version of the same MyListener class looks like this:

package com.example.myfirstapp

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

button.setOnClickListener(MyListener())
}
}

class MyListener : View.OnClickListener {
override fun onClick(v: View?) {
v!!.setBackgroundColor(android.graphics.Color.GRAY)
}
}

Remember When you override the function onClick() in Kotlin, you must set v as nullable. This means that you must use a !! operator to access the v members. Fortunately, the IDE automatically addresses this issue for you, but it's something to be aware of because of Kotlin’s handling of null values.

When you announce that you’re going to implement an interface (as in class MyListener implements OnClickListener or class MyListener : View.OnClickListener), the compiler takes this announcement seriously. In the body of the class, if you fail to give meaning to any of the interface's methods, the compiler yells at you.

Fortunately, Android Studio tells you about any missing implementations. It displays messages telling you which methods to implement. As an alternative, you can choose Code⇒  Implement Methods to tell the IDE to implement the methods for you automatically.

You can think of an interface as a kind of contract. When you write

class MyListener implements OnClickListener

or (for Kotlin):

class MyListener : View.OnClickListener

you're binding MyListener to the contract described in Listing 5-2. That contract states, “You, the implementing class, hereby agree to provide a body for each of the abstract methods declared in the interface and to indemnify and hold harmless this interface for any damages, mishaps, or embarrassments from wearing pocket protectors.”

As a member of society, you have exactly two biological parents, but you can enter into agreements with any number of companies. In the same way, a Java class has only one parent class, but a class can implement many interfaces. Likewise, even though Kotlin has some tricky ways to get around implementing just one parent class, the reality is that you can implement only one parent class, in the strictest sense, and implement as many interfaces as you want.

The interface-implementing hierarchy (if you can call it a “hierarchy”) cuts across the class-extension hierarchy. Figure 5-1 illustrates this idea, showing class extensions vertically and displaying interface implementations horizontally. (Android's KeyboardView class lives in the android.inputmethodservice package. Both KeyboardView and the homegrown MyListener class in Listing 5-3 implement Android's OnClickListener interface.)

Illustration depicting how the interface-implementing hierarchy cuts across the class-extension hierarchy displaying class extensions vertically and displaying interface implementations horizontally.

FIGURE 5-1: The interface hierarchy cuts across the class hierarchy.

Event handling and callbacks

The big news in Listing 5-3, shown in the preceding section, is the handling of the user's button click. Anything the user does (such as pressing a key, touching the screen, or whatever) is an event. The code that responds to the user’s press or touch is the event-handling code.

Technical Stuff Some things that the user doesn't do are also events. For example, when you turn on a device's GPS sensor and the sensor gets its first fix, Android calls the onGpsStatusChanged event handler.

Listing 5-3 deals with the click event with three parts of its code:

  • The MyListener class declaration says that this class implements OnClickListener.
  • The activity's onCreate() method sets the button's click handler to a new MyListener object.
  • The code for the MyListener class has an onClick() method.

Taken together, all three of these tricks make the MyListener class handle button clicks. Figure 5-2 illustrates the process. (The details are for Java, but the Kotlin details are similar.)

Illustration of the details for dealing an event with three parts of its code: MyListener class; the onCreate() method; and the onClick() method.

FIGURE 5-2: Handling an event.

When the user clicks the button, Android says, “Okay, the button was clicked. So, what should I do about that?” And the answer is, “Call an onClick() method.” It's as if Android has code that looks like this:

OnClickListener object1;
if (buttonJustGotClicked()) {
object1.onClick(infoAboutTheClick);
}

Of course, behind every answer is yet another question. In this situation, the follow-up question is, “Where does Android find onClick() methods to call?” And there’s another question: “What if you don’t want Android to call certain onClick() methods that are lurking in your code?”

Well, that's why you call the setOnClickListener() method. In Listing 5-3, the call

button.setOnClickListener(new MyListener(this));

or (for Kotlin)

button.setOnClickListener(MyListener())

creates a new MyListener object. You tell Android, “Put the new object's onClick method on your list of methods to be called. Call this object’s onClick() method whenever the button is clicked.”

And in response to this request, Android asks, “Oh, yeah? How do I know that your MyListener object has an onClick() method that I can call?” And before you can answer the question, Android notices that your MyListener class implements the OnClickListener interface. So (because of the code in Listing 5-2), your MyListener object has an onClick() method.

Technical Stuff Of course, Android doesn't really ask, “How do I know that your MyListener object has an onClick() method?” For one thing, Android doesn't say anything because Android doesn't have a mouth. And for another thing, Android's code to call onClick() declares the object containing the onClick() method to be of type OnClickListener(). So, if your MyListener method doesn't implement OnClickListener, the compiler notices a type inconsistency (and the compiler complains vigorously).

So here's the sequence of events (follow along in Figure 5-2): Your app registers a listener with Android. Then your app goes about its business. When a relevant event takes place (such as the clicking of a button), Android calls back to your app's code. Android calls the onClick() method inside whatever object you registered.

Android calls back to your app's code, so the term callback describes the mechanism that Android uses to handle events.

Tip Starting with version 1.6 (also known as Donut or API Level 4), Android provides a way to respond to button clicks, keystrokes, and other things without all the complicated code in this chapter's listings. That is, you can handle certain events without implementing the OnClickListener interface. Even so, you can't write Kotlin or Java code without understanding events and callbacks. At many points in your Android development life, you have to write the kind of code that you find in this chapter.

For more information on handling events without implementing OnClickListener, see Book 1, Chapter 5.

An object remembers who created it

This section is Java-specific. If you're not interested in how Java works because you don’t plan to go into the library code, you can skip it. Kotlin still does the things that this section discusses, but it does them in the background (hence the shortness of the Kotlin version of the example code found in Listing 5-3).

When you’re working with Java code, the preceding section raises several questions about the interaction between your app and Android's callback. But that section misses one of the questions, which is, “In the onClick() method of Listing 5-3, how does the code know what button means?” Listing 5-3 contains two classes — MyActivity and MyListener. Without jumping through some hoops, one class doesn't know anything about another class's fields.

Illustration of how a listener remembers its creator. The MyActivity instance has a button and MyListener refers to an activity, and that activity
contains a button.

FIGURE 5-3: How a listener remembers its creator.

In Listing 5-3, the keyword this sits inside the code that defines the MyActivity class:

button.setOnClickListener(new MyListener(this));

In Java, this refers to “the object that contains the current line of code.” So, in Listing 5-3, the word this refers to an instance of MyActivity — the activity that's being displayed on the device's screen. The current MyActivity instance has a button. So far, so good.

Later in Listing 5-3, the MyListener constructor tucks a reference to the current activity into one of its fields. (See Figure 5-3.)

Looking again at Figure 5-3, MyListener refers to an activity, and that activity contains a button. When Android calls the onClick() method, the method executes an instruction that's very much like this one:

activity.button.setBackgroundColor(android.graphics.Color.GRAY);

The instruction takes the referenced activity's button and sets the button's background color to gray. To make things work properly, you have to do some casting in the onClick() method of Listing 5-3. For example, when you see the statement:

button = ((Button) findViewById(R.id.button1));

what you're telling the compiler is that the output of findViewById() needs to be turned into a Button object, so that it matches the type of button. Otherwise, the assignment could result in an error.

A less wordy way to implement an interface

If you read the preceding section and then you read this section, you'll probably want to send us a nasty email message. The preceding section describes an admittedly convoluted way to make a listener remember which activity's button to tweak. You need to know how Listing 5-3 works, but if you modify Listing 5-3 so that the activity is its own listener, things become much simpler. Listing 5-4 shows you how to do it.

LISTING 5-4: An Activity Eats Its Own Dog Food

package com.allmycode.samples;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MyActivity extends Activity
implements OnClickListener {

Button button;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

button = ((Button) findViewById(R.id.button1));

button.setOnClickListener(this);
}

@Override
public void onClick(View arg0) {
button.setBackgroundColor
(android.graphics.Color.GRAY);
}
}

The combination of automation in Android Studio and the concise nature of Kotlin make things even easier. Here is the Kotlin version of the same upgrade:

package com.example.myfirstapp

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}

fun onClick(v: View) {
button.setBackgroundColor(android.graphics.Color.GRAY)
}
}

The earlier section starts with a question: “In the onClick() method, how does the code know what button means?” In this section, that question goes away just as your lap goes away when you stand up.

In Listing 5-4, both the button and the onClick() method are members inside the activity. So the onClick() method has free-and-easy access to the button. You don't need an Activity field as in Listing 5-3, and you don't need any fancy casting from Activity to MyActivity.

When working with Java, you have to remind Android that MyActivity contains an onClick() method, and you do that by adding implements OnClickListener to the declaration of MyActivity. You must also remind Android to notify the current MyActivity object whenever the button gets clicked. You do this reminding by writing

button.setOnClickListener(this);

which, roughly speaking, translates to “Hey, Android! When someone clicks the button, call the onClick() method that's inside the this object (a MyActivity object, which fortunately implements OnClickListener).” All this connectivity takes place in the background when working with Kotlin, but it still takes place.

The pattern in Listing 5-4 (having an Activity implement whatever interface it requires) is a very common application-programming idiom.

Classes That Must (and Must Not) Be Extended

Java and Kotlin take very different views on extensions. For example, in Kotlin, all classes are final by default. To inherit a class in Kotlin, you must open the class. Having all classes final by default adds a level of security and makes some tasks faster. However, it also means that you have to perform additional work just to override behaviors in an existing class. The following sections compare Java and Kotlin with regard to extensions. You need to know both when working with Android because many of the underlying library classes take the Java approach (given that they're written in Java).

The need to override

Suppose you want to add functionality to an existing Java class. You like Android's Activity class, but the pre-declared Activity class displays nothing on the screen. Do you rewrite Android's Activity class? No.

Instead of rewriting an existing class, you extend the class. Even in a do-nothing Android “Hello” application, you write

public class MyActivity extends Activity

Then, in the MyActivity class's declaration, you write

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}

Your MyActivity class creates new functionality by extending most of Android's Activity functionality while overriding the Activity class's brain-dead onCreate() method.

Remember When working with a Java class in Kotlin, you can override the functions just as if you were programming in Java. This is the reason that the Kotlin version of the code can also override the onCreate() method.

Java's final classes

In object-oriented programming, extending a class is the noblest thing you can do.

But some classes aren't meant to be extended. Take, for example, Java's String class. A String is a String is a String. You don't want somebody's MyString.length method to return the length of time it takes to scramble a string's characters. To prevent someone from doing something unexpected, unconventional, or unusual with a string's methods, the creators of Java made the String class final:

public final class String

Some of Android's pre-declared classes are also final, including the Telephony and MediaStore classes.

Kotlin's open classes

As previously mentioned, all classes in Kotlin are final by default. To create a class that you can override, you must declare the class as being open. In addition, you must declare all functions, fields, and other elements that you want to override as being open. Here is an example of the Kotlin open classes:

fun main(args: Array<String>) {
val primary: MySaying = MySaying()
val secondary: YourSaying = YourSaying()

primary.printSaying()
secondary.printSaying()
}

open class MySaying {
open fun printSaying() {
println("This is my saying.")
}
}

class YourSaying: MySaying() {
override fun printSaying() {
println("This is your saying.")
}
}

You still have to initialize MySaying when extending YourSaying, which is why it appears as MySaying(). To override printSaying(), you must also include the override keyword.

Kotlin extensions

Unlike with Java, you can extend classes in Kotlin by using a special kind of function, the extension function. An extension function adds something new to a class without changing anything about the existing class. For example, you might want a new way to work with a List that Kotlin doesn't currently provide. Instead of creating an entirely new List class, you simply add the required functionality. Here’s an example of working with an extension in Kotlin:

fun main(args: Array<String>) {
val doSaying: MySaying = MySaying()

doSaying.printSaying()
doSaying.printSaying("My custom saying.")
}

class MySaying {
fun printSaying() {
println("This is my saying.")
}
}

fun MySaying.printSaying(saying: String){
println(saying)
}

The extension function uses the form class_name.function_name. This extension adds a version of printSaying() to the original class that accepts a String argument. This extension method works with final classes (the default), so you can make a kind of modification to Kotlin final classes without having to write everything from scratch.

Remember As long as the function declaration is different, you can add just about anything you want. However, you can't override existing functionality by declaring a new function with the same signature (the same name and parameter list) as an existing function. The two functions must be different.

Abstract classes

Both Kotlin and Java provide abstract classes, and they work just about the same for both languages. Although a final class hates to be extended, an abstract class insists on being extended. Android's ViewGroup is an example of an abstract class. (See Listing 5-5.)

LISTING 5-5: A Small Part of Android's ViewGroup Class

public abstract class ViewGroup {

public void bringChildToFront(View child) {
int index = indexOfChild(child);
if (index >= 0) {
removeFromArray(index);
addInArray(child, mChildrenCount);
child.mParent = this;
}
}

protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
}

Android's ViewGroup.java file is more than 3,700 lines long. So Listing 5-5 has only a tiny fraction of the file's code. But you can see from Listing 5-5 how a class becomes abstract. To no one's surprise, the word abstract precedes the word class. But the word abstract also starts the declaration of some methods belonging to the class. (Kotlin abstract classes and functions also begin with the word abstract.)

The founders of Android decided that the idea of a ViewGroup is useful. They were correct because your favorite Android layouts (LinearLayout, RelativeLayout, and so on) are subclasses of ViewGroup. They also understood that from one kind of ViewGroup to another, some functionality doesn't change. For example, Listing 5-5 defines a bringChildToFront() method, and subclasses of ViewGroup inherit this method.

But the founders also realized that some aspects of a ViewGroup make no sense unless you work with a particular kind of group. For example, a LinearLayout positions things one after another, and a RelativeLayout positions things above, below, and to the side of one another. So Listing 5-5 doesn't have a full-blown onLayout() method. The onLayout() declaration in Listing 5-5 has no method body. But Android requires each subclass of the ViewGroup class to declare its own onLayout() method. Both Kotlin and Java enforce this requirement when (as in Listing 5-5) you declare method onLayout() to be abstract.

As a developer, you can't create an object from an abstract class. If you write

ViewGroup group = new ViewGroup();

or (for Kotlin)

val group: ViewGroup = ViewGroup()

the compiler tells you that you're behaving badly. To do something useful with the ViewGroup class, you need a subclass of the ViewGroup class. The subclass has a concrete version of each abstract method in the ViewGroup class:

package com.allmycode.samples;

import android.content.Context;
import android.view.ViewGroup;

public class MyLayout extends ViewGroup {

public MyLayout(Context context) {
super(context);
}

@Override
protected void onLayout(boolean changed,
int l, int t, int r, int b);
}
}

Here's the Kotlin version of this code:

class MyLayout: ViewGroup(context) {
override fun onLayout(changed: Boolean, l: Int,
t: Int, r: Int, b: Int) {

}
}

Inner Classes

Here's big news! You can define a class inside another class! Most classes don't live inside another class, and most classes don't contain other classes. But when the idea behind one class screams out to be part of another class, feel free to create a class within a class. Oddly enough, this is one area in which Kotlin and Java work alike.

Named inner classes

For the user, Listing 5-6 behaves the same way as Listings 5-3 and 5-4. But in Listing 5-6, the MyActivity class contains its own MyListener class.

LISTING 5-6: A Class within a Class

package com.allmycode.samples;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MyActivity extends Activity {

Button button;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

button = ((Button) findViewById(R.id.button1));

button.setOnClickListener(new MyListener());
}

class MyListener implements OnClickListener {

@Override
public void onClick(View arg0) {
button.setBackgroundColor
(android.graphics.Color.GRAY);
}
}
}

Here is the Kotlin version:

package com.example.myfirstapp

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

button.setOnClickListener(MyListener())
}

class MyListener : View.OnClickListener {
override fun onClick(v: View?) {
v!!.setBackgroundColor(android.graphics.Color.GRAY)
}
}
}

The MyListener class in Listing 5-6 is an inner class. An inner class is a lot like any other class. But within an inner class's code, you can refer to the enclosing class's fields. For example, the onClick() method inside MyListener uses the name button, and button is defined in the enclosing MyActivity class.

Listings 5-4 and 5-6 are very similar. In both listings, you circumvent the complexities described in the section “An object remembers who created it,” earlier in this chapter. For this chapter's example, the choice of Listing 5-4 or Listing 5-6 is largely a matter of taste.

Anonymous inner classes

Notice that the code in Listing 5-6 uses the MyListener class only once. (The only use is in a call to button.setOnClickListener.) So, do you really need a name for something that's used only once? No, you don't. You can substitute the entire definition of the inner class inside the call to button.setOnClickListener. When you do this, you have an anonymous inner class. Listing 5-7 shows you how it works.

LISTING 5-7: A Class with No Name (Inside a Class with a Name)

package com.allmycode.samples;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MyActivity extends Activity {

Button button;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

button = ((Button) findViewById(R.id.button1));

button.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View arg0) {
button.setBackgroundColor
(android.graphics.Color.GRAY);
}
});
}

}

Here's the Kotlin version of the same code:

package com.example.myfirstapp

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

button.setOnClickListener(object: View.OnClickListener {
override fun onClick(v: View?) {
v!!.setBackgroundColor(android.graphics.Color.GRAY)
}
})
}
}

Inner classes are good for things like event handlers, such as the onClick() method in this chapter's examples. The most difficult thing about an anonymous inner class is keeping track of the parentheses, the curly braces, and the indentation. So our humble advice is to start by writing code without any inner classes, such as the code in Listing 5-3 or Listing 5-4. Later, when you become bored with ordinary classes, experiment by changing some of your ordinary classes into inner classes.

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

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