Chapter 20

Interfaces, Threads, and Runnable

This is the chapter where our games grow up. Until now, Android has been quietly running our game loop for us: we call invalidate(), and Android decides when to call onDraw again. It has worked, but it has also been holding us back, and for a serious game it simply is not good enough. In this chapter we learn why, and we take control of the loop ourselves—running it on our own separate thread.

To get there, we need one more piece of object-oriented Kotlin: the interface. Interfaces are how we describe a capability—a promise that a class can do something—and the single most important interface for us is called Runnable, which is how we hand a chunk of code to a thread to run. Along the way we will meet SurfaceView, a special kind of view designed precisely so that a background thread can draw to it quickly and safely.

This is the densest theory chapter in the book, because it brings several new ideas together. Take it slowly. By the end you will understand the architecture that every real-time game in the rest of this book—and most 2D Android games in the world—is built on.

Code for this chapter: the sketches in this chapter are in the Chapter 20 folder of the accompanying repository at github.com/EliteIntegrity/Learning-Kotlin-by-Building-Android-Games.

In this chapter, we will:

Let's begin with interfaces.

Interfaces: Contracts for Behavior

In Chapter 18 we met the abstract class—a base type that subclasses extend, sharing its data and filling in its abstract methods. An interface is a close cousin, but with a different purpose. Where an abstract class says "this is a kind of thing," an interface says "this can do a certain thing." It is a pure contract: a list of methods a class promises to provide, with no data of its own behind them.

Here is an interface:

interface Updatable {
    fun update()
}

The interface keyword declares it. Inside, fun update() is a method with no body—just the promise that any class claiming to be Updatable will have an update method. An interface is nothing but a contract: "if you are Updatable, you can be updated."

A class signs the contract by listing the interface after a colon, and providing the method with override:

class Player : Updatable {
    override fun update() {
        // move the player...
    }
}

So far this looks much like extending an abstract class. The differences are what make interfaces special, and there are two big ones.

First, a class can implement many interfaces, but extend only one class. You get a single parent class—your one "is-a" type—but you can promise as many capabilities as you like. We can define a second interface:

interface Drawable {
    fun draw(canvas: Canvas, paint: Paint)
}

and have a class be both updatable and drawable at once, listing them separated by commas:

class Player : Updatable, Drawable {
    override fun update() { /* ... */ }
    override fun draw(canvas: Canvas, paint: Paint) { /* ... */ }
}

Player now promises two capabilities. And if Player also needed to extend a class—say GameObject—it could do that and implement both interfaces together: class Player : GameObject(), Updatable, Drawable. One parent class, any number of interface contracts. (Notice the parent class has parentheses, because we call its constructor; interfaces do not, because they have nothing to construct.)

Second, an interface holds no state. An abstract class can have real properties with values—var health = 100. An interface cannot store data like that; it only describes methods (and, at most, properties without stored values). It is a contract about behavior, not a container for data. That is the heart of the distinction: extend a class to inherit what something is and has; implement an interface to promise what something can do.

You have actually been living this idea already. Our Particle and our Enemy subclasses all had update and draw methods—they were informally "updatable" and "drawable." Interfaces let us state that contract formally, so that completely unrelated classes can all promise "I can be updated" and be treated the same way. That turns out to matter enormously, and the most important example is coming right up.

The Runnable Interface

Kotlin and Java come with a built-in interface that is about to become central to everything: Runnable. Its entire contract is a single method:

interface Runnable {
    fun run()
}

(You do not write this—it already exists. It is shown here just so you can see how simple it is.)

Runnable means exactly one thing: "this is a chunk of code that can be run." A class that implements Runnable promises to provide a run() method, and inside that run() goes the code you want executed. On its own that is unremarkable—but Runnable is the standard way to hand a block of work to a thread, so that the work runs alongside the rest of your program rather than in the middle of it. To see why we would want that, we need to talk about the thread our app has been running on all along.

The Problem with Our Game Loop

Every Android app runs its code on a single main thread, known as the UI thread. A thread is simply a line of execution—a sequence of instructions the device carries out one after another. The UI thread is a busy one: it runs your onCreate, it delivers every touch to onTouchEvent, it lays out and draws your views via onDraw, and it handles a constant stream of system housekeeping. All of that, on one thread, one thing at a time.

Now recall how our game loop has worked. At the end of onDraw, we call invalidate(), which tells Android "this view is out of date, please draw it again." But notice the word please. We are not running the loop; we are asking the UI thread to get back to us when it has a moment. We do not control the timing, we cannot measure it precisely, and our drawing has to take its turn among everything else the UI thread is doing.

This causes two real problems. The first is timing we don't control: frames arrive when the system gets around to them, which makes smooth, predictable motion hard to guarantee. The second is more serious. Because everything runs on the one UI thread, any heavy work we do in onDraw—updating thousands of objects, complex collision checks—blocks that thread. And a blocked UI thread cannot respond to touches or system events. Block it for too long and Android shows the dreaded "Application Not Responding" dialog and may kill your app.

The solution is to stop borrowing the UI thread for our game loop and instead run the loop on a thread of our own. Our thread can spin as steadily as we like, measure its own timing exactly, and do all the heavy game work without ever freezing the UI thread that handles touches. This is how real games are built.

A good place to pause. That is the conceptual heart of the chapter, and it is a lot to absorb. If your head is full, this is a natural spot to put the book down, make a coffee, and let interfaces and the UI-thread idea settle. The rest of the chapter builds directly on what you have just read, so it is worth being fresh for it. When you are ready, we will put a second thread to work.

Threads: Doing Two Things at Once

A thread lets a program do more than one thing at the same time. Your app already has its UI thread; we are going to create a second thread to run our game loop, so the two run side by side—the UI thread handling touches and system events, our thread updating and drawing the game.

This is exactly where Runnable comes in. To make a thread, we create a Thread object and hand it a Runnable—the code we want it to run. When we start the thread, it calls that Runnable's run() method on the new thread, and whatever loop we wrote inside run() runs there, independently:

class GameView : Runnable {
    private var playing = false

    override fun run() {
        while (playing) {
            update()    // move everything
            draw()      // paint the frame
        }
    }
}

In the preceding sketch, GameView implements Runnable, so it must provide run(). Inside run() is our game loop—a while loop (Chapter 6) that keeps updating and drawing for as long as playing is true. This is the real game loop we have been building toward since Chapter 6: an actual loop, that we wrote, that we control.

To set it running on its own thread, we wrap it in a Thread and start it:

val gameThread = Thread(gameView)   // give the thread our Runnable
gameThread.start()                  // start() runs run() on the new thread

Thread(gameView) creates a thread and tells it "your job is this Runnable." Calling .start() launches the thread and, on that new thread, calls gameView.run(). From that moment, the loop spins away on its own thread while the UI thread carries on with its own business. Note the crucial difference: we call .start(), not .run(). Calling run() directly would just execute the loop on the current thread, defeating the whole purpose. start() is what creates the new thread.

We will also need to stop the thread cleanly when the app is paused—we do that by setting playing = false, which lets the while loop finish, and then waiting for the thread to end. We will see the full, careful version next chapter. The key idea for now: a thread is a second worker, and Runnable's run() is the job we give it.

SurfaceView: A Canvas a Background Thread Can Draw To

There is one catch, and it is an important one. A normal View—the kind we have used for every project so far—can only be drawn on the UI thread. Android calls its onDraw for us, on the UI thread, and we are not allowed to draw to it from anywhere else. So if our game loop now lives on a separate thread, it cannot draw to an ordinary View at all.

Android's answer is a special view built exactly for this situation: SurfaceView. A SurfaceView provides a drawing surface that a background thread can draw to, directly and quickly. It is the standard foundation for 2D games on Android.

Drawing to a SurfaceView follows a three-step ritual, through a helper object called a SurfaceHolder that manages the surface:

  1. Lock the canvas. Ask the holder for the canvas, which locks it for our exclusive use: val canvas = holder.lockCanvas().
  2. Draw. Paint the frame on that canvas exactly as we always have—canvas.drawColor(...), canvas.drawCircle(...), and so on.
  3. Unlock and post. Hand the finished canvas back to be shown on screen: holder.unlockCanvasAndPost(canvas).

That lock–draw–post cycle is the background-thread equivalent of everything we did inside onDraw. The drawing code itself is identical—Canvas and Paint work just as they always have. The only difference is that we now decide when each frame is drawn, from our own thread, by locking and posting the canvas ourselves, instead of waiting for Android to call onDraw.

So the full picture comes together like this: a class that extends SurfaceView (so it has a surface we can draw to) and implements Runnable (so it can be the body of a thread). Its run() method holds the game loop. The loop updates the game, locks the canvas, draws the frame, and posts it—over and over, on its own thread, at a steady pace we control. That single sentence describes the engine behind every game in the rest of this book.

The Shape of What's Coming

Next chapter we build exactly that, as a small, complete, reusable game-loop engine. Here is the skeleton, so the pieces have a place to land:

class GameView(context: Context) : SurfaceView(context), Runnable {

    private var gameThread: Thread? = null
    private var playing = false

    override fun run() {
        while (playing) {
            update()    // move the game forward
            draw()      // lock canvas, paint, post
            // ...and pace the loop to a steady frame rate
        }
    }

    fun resume() { /* start the thread */ }
    fun pause() { /* stop the thread cleanly */ }
}

Notice how it embodies this whole chapter at once. It extends one class, SurfaceView, for its drawable surface. It implements one interface, Runnable, so it can be the job of a thread—our first real use of being both a subclass and an interface implementer simultaneously. Its run() is the game loop. And resume/pause will start and stop the thread in step with the app's lifecycle. Everything in this chapter exists to make that little class possible.

Summary

You added the last big piece of object-oriented Kotlin and the foundation of real-time games. An interface is a contract describing a capability: a list of methods a class promises to provide, with no data of its own. Unlike a class, of which you can extend only one, a class may implement many interfaces—and the all-important built-in interface Runnable describes a chunk of code (its run() method) that a thread can run. You learned why our invalidate() loop on the single UI thread holds us back, and how running the loop on a thread of our own frees us to control timing and avoid freezing the interface. Finally, SurfaceView gives a background thread a surface it can draw to, through the lock–draw–post cycle of its SurfaceHolder.

It is fine if not all of this is solid yet—threading is one of those topics that truly clicks only when you see it run. In the next chapter we build the threaded game loop for real: a SurfaceView that implements Runnable, runs its loop on its own thread, paces itself to a steady sixty frames per second, and moves objects by time rather than by frame—so they travel at the same real speed on every device. It is the engine we will build every remaining game on top of.

AI Exercise (Optional)

Interfaces are a great topic to firm up with an AI, because the line between "use an interface" and "use an abstract class" is a real design judgment. Optional as always.

Open your AI chatbot and paste in this prompt:

"I am learning Kotlin OOP. I understand classes, inheritance, abstract classes, polymorphism, and now interfaces (declared with interface, implemented with a colon and override, with the rules that a class can implement many interfaces but extend only one class, and that interfaces hold no stored data). Please do two things. First, explain in plain language the difference between an abstract class and an interface, and give me one clear rule of thumb for choosing between them. Second, write a small Kotlin example with two interfaces—Updatable (with update()) and Drawable (with draw())—and a single class GameObject that implements BOTH, with simple bodies. Then show a function that takes a parameter of type Updatable and calls update() on it, and explain why that function would accept a GameObject."

This asks the AI for the exact distinction you just learned and then puts interfaces to work polymorphically—a function that accepts anything Updatable, regardless of its actual class. That last idea (treating objects by the interface they implement, not their concrete type) is the payoff of interfaces, and worth seeing demonstrated.

When the answer comes back, check its rule of thumb against this chapter's: extend a class for what something is, implement an interface for what something can do. Does its GameObject correctly list both interfaces and override both methods? Does it explain that the function accepts a GameObject because a GameObject is Updatable—it signed that contract? If the abstract-class-versus-interface explanation is muddy, ask it for a concrete game example of each.