Your First App

Build your first Nocterm application

In this guide, you'll build an interactive counter app. You'll learn how to create components, manage state, and handle keyboard input.

Create the app

Create a file called counter.dart and add this code:

import 'package:nocterm/nocterm.dart';

void main() {
  runApp(const Counter());
}

class Counter extends StatefulComponent {
  const Counter({super.key});

  @override
  State<Counter> createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int _count = 0;

  @override
  Component build(BuildContext context) {
    return Focusable(
      focused: true,
      onKeyEvent: (event) {
        if (event.logicalKey == LogicalKey.space) {
          setState(() => _count++);
          return true;
        }
        return false;
      },
      child: Container(
        decoration: BoxDecoration(
          border: BoxBorder.all(color: Colors.gray),
        ),
        margin: EdgeInsets.all(8),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Counter: $_count'),
            SizedBox(height: 1),
            Text('Press SPACE to increment', style: TextStyle(color: Colors.gray)),
          ],
        ),
      ),
    );
  }
}

Run the app:

dart run counter.dart

Press the spacebar to increment the counter. Press Ctrl+C to exit.

How it works

Let's break down each part of the code.

runApp

The runApp() function starts your app:

void main() {
  runApp(const Counter());
}

This function takes your root component and displays it in the terminal. It sets up the terminal (enables raw mode, alternate screen), handles rendering, and processes input events.

StatefulComponent

Counter extends StatefulComponent because it needs to track the count:

class Counter extends StatefulComponent {
  const Counter({super.key});

  @override
  State<Counter> createState() => _CounterState();
}

A StatefulComponent separates the component (which is immutable) from its state (which changes over time). The createState() method creates the state object.

State and setState

The _CounterState class holds the count and rebuilds the UI when it changes:

class _CounterState extends State<Counter> {
  int _count = 0;

  @override
  Component build(BuildContext context) {
    // ... UI code
  }
}

When you call setState(), Nocterm rebuilds the component with the new state:

setState(() => _count++);

Always wrap state changes in setState(). This tells Nocterm to redraw the UI.

The build method

The build() method returns the UI for your component:

Component build(BuildContext context) {
  return Focusable(
    // ... component tree
  );
}

Nocterm calls build() when:

  • The component first appears
  • You call setState()
  • The parent component rebuilds

Handling keyboard input

The Focusable component captures keyboard events:

Focusable(
  focused: true,
  onKeyEvent: (event) {
    if (event.logicalKey == LogicalKey.space) {
      setState(() => _count++);
      return true;  // Event handled
    }
    return false;  // Not handled, pass to next component
  },
  child: // ...
)

The onKeyEvent callback receives each key press. Return true if you handled the event, or false to pass it along.

Layout components

The UI uses layout components to arrange text:

Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
    Text('Counter: $_count'),
    SizedBox(height: 1),
    Text('Press SPACE to increment', style: TextStyle(color: Colors.gray)),
  ],
)
  • Column stacks children vertically
  • mainAxisAlignment.center centers them vertically
  • SizedBox adds spacing between elements

Try it yourself

Modify the app to:

  • Increment on up arrow, decrement on down arrow
  • Add a reset button (press R to reset to 0)
  • Display the count in a different color when it's above 10
  • Add a border that changes color based on the count

Next steps