Applications
All Doodle apps run within an Application
. It is the entry-point for your business logic, and often the first class you write. Doodle fully initializes your app at constructor time, so there is no additional run or start method to implement. You can provide custom tear-down logic via the shutdown
method though.
import io.nacular.doodle.application.Application
//sampleStart
class UsefulApp: Application {
init {
println("Hi!")
}
override fun shutdown() {}
}
//sampleEnd
Launching an application
You can either launch an app top-level, or nested within another app. The Application class does not change regardless of the launch mode. That is because apps have no knowledge of the mode they will run in, making them independent of platform concepts by default.
App launch is platform-specific, allowing apps to customize their setup based on this context.
Top-level applications
Most apps will run independent of others and exist purely within the context of a page, or element within it (for Web apps). Use the application
function to launch apps this way. The result is a full-screen experience by default, with the app taking up the entire page and control all aspects of it. You can also provide an HTML element when launching a top-level Web app. This allows you to host Doodle apps in non-Doodle contexts. The apps in this documentation are top-level within specific elements.
Closing the page cleans up any apps within it. Removing the element hosting an app or explicitly calling shutdown
has the same effect.
package usefulapp
import UsefulApp
import io.nacular.doodle.application.application
//sampleStart
fun main() {
// launch full-screen
application {
UsefulApp()
}
}
//sampleEnd
Nested applications (Web)
Doodle Web apps can also be run within other Doodle Web apps. This is done by placing the nested app in a View that the host app manages. An app launched this way has the same functionality as a top-level one. Its lifecycle however, is tied to the host View.
You simply use an ApplicationViewFactory
(available via the AppViewModule
) to create nested apps.
- Inner App
- Outer App
- Outer App Launcher
package io.nacular.doodle.docs.apps.nesting
import io.nacular.doodle.application.Application
import io.nacular.doodle.controls.ColorPicker
import io.nacular.doodle.core.Display
import io.nacular.doodle.docs.utils.doodleColor
import io.nacular.doodle.drawing.opacity
import io.nacular.doodle.layout.Insets
import io.nacular.doodle.layout.constraints.constrain
import io.nacular.doodle.layout.constraints.fill
//sampleStart
class InnerApp(display: Display): Application {
init {
// Shows a color picker
display += ColorPicker(doodleColor opacity 0.75f).apply {
changed += { _,_,new -> println(new) }
}
// The picker grows with the display, but is inset a little
display.layout = constrain(display.first(), fill(insets = Insets(2.0)))
}
override fun shutdown() {}
}
//sampleEnd
package io.nacular.doodle.docs.apps.nesting
import io.nacular.doodle.application.Application
import io.nacular.doodle.application.ApplicationViewFactory
import io.nacular.doodle.application.Modules.Companion.PointerModule
import io.nacular.doodle.core.Display
import io.nacular.doodle.core.center
import io.nacular.doodle.core.height
import io.nacular.doodle.core.width
import io.nacular.doodle.drawing.Color.Companion.White
import io.nacular.doodle.drawing.paint
import io.nacular.doodle.geometry.Rectangle
import io.nacular.doodle.geometry.centered
import io.nacular.doodle.utils.Resizer
import org.kodein.di.instance
import kotlin.math.min
//sampleStart
class OuterApp(display: Display, appView: ApplicationViewFactory): Application {
init {
// NOTE: Any module needed by InnerApp MUST be provided to the factory.
// PointerModule used here to enable interaction with the color picker.
display += appView(modules = listOf(PointerModule)) {
InnerApp(display = instance()) // Init inner app
}.apply {
bounds = Rectangle(
min(400.0, display.width - 20),
min(300.0, display.height - 20)
).centered(at = display.center)
Resizer(this).apply { movable = false }
}
display.fill(White.paint)
}
override fun shutdown() {}
}
//sampleEnd
package outerapp
import io.nacular.doodle.application.ApplicationViewFactory.Companion.AppViewModule
import io.nacular.doodle.application.Modules.Companion.PointerModule
import io.nacular.doodle.application.application
import io.nacular.doodle.docs.apps.nesting.OuterApp
import org.kodein.di.instance
fun main() {
//sampleStart
// NOTE: Modules used by the outer app are not available to the inner one.
// The PointerModule is used here to allow resizing of the View that holds the inner app.
application(modules = listOf(AppViewModule, PointerModule)) {
OuterApp(display = instance(), appView = instance())
}
//sampleEnd
}
Adding a nested app's View to the Display triggers the app's initialization. Shutdown the app by removing the host View from the Display.
Modularity
Doodle uses dependency injection when creating apps. The lambda provided when launching an app is actually a Kodein binding context that lets you inject instances from Doodle modules, or your own.
package applications
import io.nacular.doodle.application.Modules.Companion.PointerModule
import io.nacular.doodle.application.application
import org.kodein.di.DI.Module
import org.kodein.di.instance
//sampleStart
fun main() {
application(modules = listOf(
PointerModule,
// ...,
Module(name = "A Custom Module") {
// custom Kodein bind statements
},
/*...*/)) {
MyApp(instance())
}
}
//sampleEnd