Kotlin and Compose for Desktop - The Balloon Years

2021-05-12

Wait? Aren't you that crazy Android guy? This isn't Android...

I know, right! Still, I'm not just a crazy Android developer. From time to time, I also build desktop applications and I used to be pretty good at Swing development. In recent years though, I just didn't do a lot of desktop work, even though I still have clients that use some of my older efforts. As a freelancer, I am often called upon to solve esoteric problems, one of which turned out helping some Java backend developers pick up a few Kotlin skills.

Instead of following the usual path, I thought, why not embrace the relatively new Compose for Desktop paradigm and build some fun, mindless programs, using Kotlin instead of Java? Turns out, this wasn't an entirely bad idea and I'll show you one of them here.

From Turbo Pascal to Kotlin

Back in the day, my programming language of choice was Turbo Pascal on good old MS-DOS 6. Somewhere along the way I migrated to C++ and later Java and sort of forgot about my Pascal and Borland Graphics Interface (BGI) days. I recently discovered some old Pascal source code I had printed out. I will show you how I went about recreating one of those old apps in Compose for Desktop.

Who this is for

If you've done a fair bit of Compose, whether for desktop or via Jetpack on Android, this is probably a waste of time for you. This is more aimed at the person that's done the initial "Hello World!" example and wants to go just a tad bit further, without feeling overwhelmed. I've built this example app in a style that should be familiar to most developers that started off building their own little graphical games and demos, mostly in the early 90's!

I'm also going to assume that you've done some casual graphics programming and understand concepts like borders, colours, canvas etc. sufficiently that you could use the Compose documentation to find answers to questions that might come up.

Finally, even though I used this code over a few days to trains peeps in Kotlin, I'm not going to do that here. I will assume that you have some very basic Kotlin knowledge (I can't do fancy things anyhow) and that you know what a data class etc. is and can read basic Kotlin syntax at least.

Note about the code

There's a lot of it.

Sometimes the code formatting is weird. Sorry. Also, you should know this code was, when it was written, the best code ever. Pure poetry, without a blemish. Any errors that have snuck in is probably because of your crappy browser. It used to compile and run, but, yeah, time is not kind to old code, so be warned! This was done using Compose "0.3.1", if that matters in the future.

Of special importance is the fact that this is not great Compose code. In fact, it's rather crappy, but it's a start! My intention was not to create very clean or reliable code, but rather introduce Kotlin concepts by way of solving problems related to the displaying of some graphical elements on the screen. I would urge you to study the official Compose documentation for best-practices regarding compose.

Before we begin

I'm going to assume that you have Compose configured, in some sample project, in your favourite IDE and that you can run the example code from the Compose tutorial.

Basically, I expect you to have some code that looks a lot like this:

The expect output, more or less, should resemble:

composesample

Prepare yourself

Let's get started, and prepare our code for our adventure. Change your main function to the following:

balloon compose blank

Alright! We've changed our window size and title and added a Surface that will house our actual content. The use of a surface is just a shortcut to make it easier to stick to Material Design principles. You could do just about the same thing without it, but it's a helpful way to do layouts. It becomes quite important when you build complex interfaces where depth of visual components matter.

We had our surface fill the window to the max, added a border and a background colour. Here, I specified the DarkGray colour for the border along with a custom colour for the surface background. Compose has some predefined colours, but I find it more useful to specify my own colours sometimes.

Our own Composable

Compose has the concept of Composable, so, let's make our own one. Add the following code to your source file.

The Canvas composable is exactly what you think it is... A canvas! We can do all kinds of things on a canvas, as we will see a bit later.

Right now, this does absolutely nothing, but that's good, because it means we have no compiler errors! Yes! You can stop here if you like, it doesn't get more stable than this!

Alright alright alright. Let's go on!

Using our composable

Change your main function to include calling our custom composable:

When you run your program now, you should see the following:

custom composable

Because of the sane defaults of the drawCircle composable, we have a big yellow circle in the middle of the screen. In our next step, we'll see how we can reposition the circle.

Specifying our circle position

Let's update our custom composable to accept an Offset, a container for an X/Y position.

@Composable
fun drawBalloons(offset: Offset) = Canvas(Modifier.clipToBounds()) {
    drawCircle(color = Color.Yellow, radius = 60f, center = offset)
}

Change our calling code in the main to send an offset through as a parameter:

drawBalloons(Offset(80f, 150f))

When we run this, we get our circle at the specified offset!

balloon offset

But it is Balloons, not Balloon, fool

Oh yes, that's true. Drawing just one balloon is a bit silly. What if we wanted to draw a whole bunch of them? Like, perhaps, 10? That's a bunch, isn't it?

We could copy and paste our drawCircle instruction twenty times, but, well, that's a bit of a mission to maintain. Fortunately, composables fit nicely into the Kotlin world, so we can use familiar code to make this happen.

In fact, it couldn't be easier! Try replacing your call to drawCircle with this snippet:

for (i in 1..10) {
    drawBalloons(Offset(10f + (130f * i), 150f))
}

You should end up with this:

balloons everywhere

Mmmm. Ok, yes, I see. That's rather meh. How about we spice things up just a tad bit? Change your drawCircle call to this:

drawBalloons(Offset(500 * Random.nextFloat(), 500 * Random.nextFloat()))

Now you'll get something a bit more usable!

balloons random

We can do better

That's alright and so on, but it's not really handy, is it? How about we improve things a little bit? Since we know that we want to store a position and colour for each balloon, why don't we go ahead and add a data class called Balloon to do just that. I know, it's a snappy name!

While we are at it, let's create a convenience function called buildBalloons that will build a list of however many balloons we required, each with a random position and colour. Then we fiddle with our drawBalloon code a bit, and the main function and there we go!

balloon data

Not a bad bit of work! Now it's actually starting to look like something. If only we could make them move, right?

Moving along swiftly

Let's get things moving! No, really. Excuse the bad pun! There is a serious amount of changes to the code, and, for the first time, I've even included the imports.

Basically, what we want to do, is keep track of the X and Y position of each balloon, track whether that position is currently increasing or decreasing, and at what speed. Then, we also want to recalculate the position on every frame redraw, so it looks like the balloon is bouncing around the canvas.

Most of the code is pretty straight-forward, except for that ticker business, right? Compose redraws composables when they change, so, we sort of trick it by starting a coroutine that fetches us the milliseconds at which each frame is drawn. Since this value changes on every redraw, we trigger a redraw, which triggers a redraw, which, well, ok, you get the point. It's not a great explanation, I know, but it's a topic for another article in the future! For now, we can use this side-effect to create a bit of an animation.

Popping that code into your favourite IDE should render a result similar to this:

moving

That's not half bad! We have some balloons bouncing around the screen. Perhaps we can refine them just a bit?

Adding something extra

The plain, flat colours of our balloons are not all that great. What about adding a bit more refinement in the form of a gradient. It's a canvas, after all, let's paint on it!

We'll create a list of colour combinations and select a random one for each balloon when we create it. Then we'll use that gradient to draw our balloons, instead of the single, flat colour. Because our balloons are also moving around the canvas, we'll get a bonus, see if you can spot it.

balloon gradient

In summary

So, what's the Turbo Pascal connection then? Surprisingly, this is pretty much a clone of a program I wrote decades ago, in Turbo Pascal, that did the same thing in a 320x200 graphics window (Mode 13H) on a 80286 machine!

Anyhow, I hope you had some fun and learned a thing or two about Compose along the way. This is of course not best practice or even semi-decent code, but sometimes it's alright to write crappy code just to have some fun.

One last thing

See if you can figure out how to make 99 almost red balloons!

balloons lots



Return to the blog index