Skip to main content

Applications

Every Doodle app starts the same way: with one class that implements Application. It's where your app's logic lives, and it's almost always the first file you'll create in a new project. Let's build one up from nothing.

Here's where we're headed:

  1. What an Application class looks like, and the curious case of the missing start() method
  2. How to actually launch one
  3. How your app gets hold of the things it needs, like the Display
  4. How to switch on optional features — mouse, touch, and friends — without dragging along everything else

The smallest possible Application​

Let's start as small as Doodle will let us. Here's a complete, valid app. It doesn't draw anything yet — it just shows us the shape of the thing.

package usefulapp import io.nacular.doodle.application.Application //sampleStart class HelloApp: Application { init { println("Hello from Doodle!") } override fun shutdown() {} } //sampleEnd

Take a second to notice what's not there. HelloApp implements Application, and it has exactly two moving parts: an init block and a shutdown method. No onCreate, no componentDidMount, no run(). That really is the whole contract.

Coming from another framework?

If you're arriving from Android, React, or anything in that family, this will feel a little bare at first. Most UI frameworks hand you a separate lifecycle method that runs after your object exists. Doodle skips that step: your setup code goes straight into the constructor (Kotlin's init block) and runs the moment the class is created. Nothing to remember to call later.

So where's the start() method?​

There isn't one, and the reason is quietly elegant: Doodle finishes constructing your app before it shows anything. By the time your Application object exists, it's already fully assembled — so "starting" it amounts to creating it. There's no awkward in-between state where your app exists but hasn't woken up yet.

The one method you do have to implement is shutdown. Doodle calls it when your app is closing — window closed, page navigated away — so it's your chance to tidy up: cancel background work, close connections, that sort of thing. If you've got nothing to clean up, leave it empty, exactly as we did above.

Launching your app​

An Application class on its own won't do much; something has to bring it to life. That's the job of the application (Web, Desktop) function.

package usefulapp import io.nacular.doodle.application.application //sampleStart fun main() { application { HelloApp() } } //sampleEnd

This is the main() you'll write for very nearly every Doodle project. Call application { }, construct your class inside the block, and Doodle handles the rest — opening a window on Desktop, or taking over the page on Web, then building your app inside it.

What you've just written is a top-level launch: your app owns the entire window or page. That's what you'll want almost every time, so it's the only style we'll dwell on here. Two more specialized options are waiting for you down the road:

  • Nested apps — a Doodle app running inside another Doodle app (Web only).
  • Multiple windows — one app conducting several windows (Desktop only).

Neither is needed to get going, so feel free to file them away for later.

Getting hold of what your app needs​

Our HelloApp is still a bit of a hermit — it can't draw anything, because it has no way to reach the screen. For that it needs a Display: the object standing in for the window (or page) your app draws into.

Getting one is delightfully undramatic. You ask for it in the constructor:

package moreusefulapp import io.nacular.doodle.application.Application import io.nacular.doodle.core.Display import io.nacular.doodle.core.view import io.nacular.doodle.drawing.Color.Companion.Black import io.nacular.doodle.drawing.text //sampleStart class HelloApp(display: Display): Application { init { display += view { render = { text("Hello, world!", color = Black) } } } override fun shutdown() {} } //sampleEnd package moreusefulapp import io.nacular.doodle.application.application import org.kodein.di.instance //sampleStart fun main() { application { HelloApp(display = instance()) } } //sampleEnd

Two things shifted from the last version. HelloApp now takes a Display parameter, and when we build it inside application { }, we hand it instance() rather than constructing one ourselves.

That instance() call is the interesting bit. It asks Doodle for a ready-made Display and passes it straight to your constructor. You'll never need to build a Display by hand — or most other Doodle-provided objects, for that matter. You simply declare what you need and ask for it.

Why not just pass the Display in directly?

What you're looking at is dependency injection, handled by a small library called Kodein. The short version: rather than your app going off to hunt down the objects it needs, it lists them as constructor parameters and Doodle supplies them. Keep an eye out for instance() — it shows up everywhere your app needs something Doodle provides.

The Display has a whole page to itself — Displays picks up from here and shows you how to actually arrange things on it.

Switching on optional features with Modules​

Mouse clicks, keyboard input, drag-and-drop, animations — none of these are included by default. That's deliberate: Doodle leaves them out so your app stays lean, and lets you switch on precisely what you use. The switch is a module.

Say we want HelloApp to notice when someone clicks it. That means reaching for PointerModule (Web, Desktop):

package applications import io.nacular.doodle.application.Modules.Companion.PointerModule import io.nacular.doodle.application.application import moreusefulapp.HelloApp import org.kodein.di.instance //sampleStart fun main() { application(modules = listOf(PointerModule)) { HelloApp(display = instance()) } } //sampleEnd

And that's the pattern you'll see again and again: pass a list of modules to application(modules = ...), and everything they provide becomes available through instance() inside your app. Pointer input, keyboard input, drag-and-drop, popups, animations — nearly every feature in these docs works this way, and each page will tell you which module it needs.

:::note Defining your own module You can write modules of your own too, for things that have nothing to do with Doodle — your services, your configuration, whatever you like. That's a road for another day; the Kodein documentation is waiting whenever you want to take it. :::

Recap​

  • Your app is a single class implementing Application.
  • Setup goes in init { }, running the moment your app is constructed; cleanup goes in shutdown().
  • Launch it with application { } from inside main().
  • Anything you need from Doodle — starting with Display — comes in as a constructor parameter, supplied by instance().
  • Optional features are opt-in, passed along as application(modules = listOf(...)).

Next: your app can get hold of a Display now, so let's go find out what to do with it — on to Displays.