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:
- What an
Applicationclass looks like, and the curious case of the missingstart()method - How to actually launch one
- How your app gets hold of the things it needs, like the
Display - 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.
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.
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 inshutdown(). - Launch it with
application { }from insidemain(). - Anything you need from Doodle — starting with
Display— comes in as a constructor parameter, supplied byinstance(). - 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.