ZetCode

Dart Stdout

last modified April 4, 2025

The Stdout class in Dart represents the standard output stream. It is part of Dart's dart:io library and is accessed through the top-level stdout variable. It is essential for command-line applications.

Stdout extends IOSink, which itself extends StringSink, giving it both string-writing and raw byte-writing capabilities. It provides more control than the built-in print function, including character code output, terminal detection, and asynchronous stream support.

Basic Definition

Stdout is a class that extends IOSink. It is accessed through the stdout top-level variable exported by dart:io. A separate stderr variable provides access to the standard error stream.

Key methods include write, writeln, writeAll, writeCharCode, add, addStream, and flush. Properties such as hasTerminal, terminalColumns, and supportsAnsiEscapes allow output to adapt to the terminal environment.

Basic Stdout Usage

This example demonstrates basic console output using the Stdout class. We compare write, writeln, and the built-in print function to show how they differ.

main.dart
import 'dart:io';

void main() {
  stdout.write('Hello, ');
  stdout.write('Dart!');
  stdout.writeln();
  stdout.writeln('This is a new line');
  
  print('Using print() for comparison');
}

write appends text without a trailing newline. writeln appends text followed by a newline. An empty writeln() call emits only a newline. Unlike print, write does not add a newline automatically, which is useful when building output incrementally.

$ dart main.dart
Hello, Dart!
This is a new line
Using print() for comparison

Writing Bytes to Stdout

This example demonstrates writing raw bytes and individual characters to standard output using add, writeCharCode, and addStream.

main.dart
import 'dart:io';

void main() async {
  // add() writes a list of bytes; ASCII 72-111 spells 'Hello', 10 is newline
  stdout.add([72, 101, 108, 108, 111, 10]);

  // writeCharCode writes a single character by its Unicode code point
  stdout.writeCharCode(68);  // D
  stdout.writeCharCode(97);  // a
  stdout.writeCharCode(114); // r
  stdout.writeCharCode(116); // t
  stdout.writeln();

  // addStream consumes a Stream of byte lists (Stream<List<int>>)
  final byteStream = Stream.fromIterable([
    [66, 121, 101], // 'Bye'
  ]);
  await stdout.addStream(byteStream);
  stdout.writeln();
}

add writes a List<int> of byte values directly to the stream. writeCharCode writes a single character given its Unicode code point. addStream consumes a Stream<List<int>> asynchronously and must be awaited before further writes to maintain correct ordering.

$ dart main.dart
Hello
Dart
Bye

Controlling Line Behavior

This example illustrates the difference between write, writeln, writeAll, and flush.

main.dart
import 'dart:io';

void main() async {
  // writeln appends a newline automatically
  stdout.writeln('First line');

  // write does not append a newline
  stdout.write('Hello, ');
  stdout.write('Dart!');
  stdout.writeln(); // explicit newline

  // writeAll joins items with a separator
  stdout.writeAll(['apple', 'banana', 'cherry'], ', ');
  stdout.writeln();

  // flush returns a Future; await it to ensure all buffered output is sent
  await stdout.flush();
}

writeln adds a trailing newline; write does not. writeAll writes each item from an iterable separated by the given string. flush returns a Future<void> that completes when all buffered data has been written to the underlying sink, so it should be awaited in an async function.

$ dart main.dart
First line
Hello, Dart!
apple, banana, cherry

Checking Terminal Capabilities

This example demonstrates checking terminal properties before output.

main.dart
import 'dart:io';

void main() {
  if (stdout.hasTerminal) {
    print('Terminal width: ${stdout.terminalColumns}');
    print('Terminal height: ${stdout.terminalLines}');
    
    if (stdout.supportsAnsiEscapes) {
      stdout.writeln('\x1B[31mRed Text\x1B[0m');
    }
  } else {
    stdout.writeln('No terminal detected');
  }
}

hasTerminal returns true when stdout is connected to an interactive terminal. terminalColumns and terminalLines give the terminal dimensions in columns and rows. Checking supportsAnsiEscapes before emitting escape sequences ensures portability across different terminal emulators and piped output.

$ dart main.dart
Terminal width: 80
Terminal height: 24
Red Text

Synchronous vs Asynchronous Writing

This example compares synchronous writes via write with asynchronous stream consumption via addStream.

main.dart
import 'dart:io';

void main() async {
  // Synchronous writes execute immediately
  stdout.write('Sync 1 ');
  stdout.write('Sync 2 ');
  stdout.writeln();

  // codeUnits returns List<int>; addStream expects Stream<List<int>>
  await stdout.addStream(Stream.fromIterable([
    'Async 1\n'.codeUnits,
    'Async 2\n'.codeUnits,
  ]));

  stdout.writeln('Done');
}

write and writeln are synchronous and execute immediately. addStream is asynchronous and accepts a Stream<List<int>>. The codeUnits getter on a string returns a List<int> of UTF-16 code units, making it directly compatible. Using await ensures the stream is fully consumed before continuing.

$ dart main.dart
Sync 1 Sync 2 
Async 1
Async 2
Done

Stdout and Stderr

This example shows how to separate normal program output and diagnostic messages by writing to stdout and stderr respectively.

main.dart
import 'dart:io';

void main() {
  stdout.writeln('Starting data processing...');

  for (var i = 1; i <= 3; i++) {
    stdout.writeln('Processing record $i');
  }

  stderr.writeln('Warning: record 2 had missing fields');

  stdout.writeln('Finished. 3 records processed.');
  stderr.writeln('Error: could not save summary file');
}

stdout and stderr are independent streams. stdout carries normal program output while stderr carries warnings and error messages. They can be redirected independently from the shell (e.g., 2>errors.log), making it easy to separate logs from diagnostics.

$ dart main.dart
Starting data processing...
Processing record 1
Processing record 2
Processing record 3
Finished. 3 records processed.

Formatted Table Output

This example prints a neatly aligned table to the console using string padding methods available on all Dart strings.

main.dart
import 'dart:io';

void main() {
  final headers = ['Name', 'Score', 'Grade'];
  final rows = [
    ['Alice', '95', 'A'],
    ['Bob',   '78', 'B+'],
    ['Carol', '62', 'C'],
  ];

  stdout.writeln(headers.map((h) => h.padRight(10)).join(''));
  stdout.writeln('-' * 30);

  for (final row in rows) {
    stdout.writeln(row.map((cell) => cell.padRight(10)).join(''));
  }
}

padRight(width) pads a string to the given width with trailing spaces. Mapping each cell and joining the results with an empty string produces a fixed-width row. Combined with writeln, this creates clearly aligned tabular console output without any external dependencies.

$ dart main.dart
Name      Score     Grade     
------------------------------
Alice     95        A         
Bob       78        B+        
Carol     62        C         

Best Practices

Source

Dart Stdout Documentation

This tutorial covered Dart's Stdout class with practical examples demonstrating basic output, byte writing, line control, terminal detection, asynchronous stream consumption, stderr separation, and formatted table output.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Dart tutorials.