toml_viewer 1.0.0 copy "toml_viewer: ^1.0.0" to clipboard
toml_viewer: ^1.0.0 copied to clipboard

A Flutter widget for displaying TOML files as interactive, expandable tree views with syntax highlighting, partial-parse error reporting, light/dark theme support, custom styling, extensible builders, [...]

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:toml_viewer/toml_viewer.dart';

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

class DemoApp extends StatefulWidget {
  const DemoApp({super.key});

  @override
  State<DemoApp> createState() => _DemoAppState();
}

class _DemoAppState extends State<DemoApp> {
  ThemeMode _themeMode = ThemeMode.system;

  void _toggleTheme() {
    setState(() {
      _themeMode = switch (_themeMode) {
        ThemeMode.light => ThemeMode.dark,
        ThemeMode.dark => ThemeMode.light,
        ThemeMode.system =>
          WidgetsBinding.instance.platformDispatcher.platformBrightness ==
                  Brightness.dark
              ? ThemeMode.light
              : ThemeMode.dark,
      };
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'TOML Viewer Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorSchemeSeed: Colors.teal,
        brightness: Brightness.light,
        useMaterial3: true,
      ),
      darkTheme: ThemeData(
        colorSchemeSeed: Colors.teal,
        brightness: Brightness.dark,
        useMaterial3: true,
      ),
      themeMode: _themeMode,
      home: DemoHome(onToggleTheme: _toggleTheme),
    );
  }
}

class DemoHome extends StatelessWidget {
  final VoidCallback onToggleTheme;

  const DemoHome({super.key, required this.onToggleTheme});

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 7,
      child: Scaffold(
        appBar: AppBar(
          title: const Text('TOML Viewer Demo'),
          actions: [
            IconButton(
              icon: Icon(
                Theme.of(context).brightness == Brightness.dark
                    ? Icons.light_mode
                    : Icons.dark_mode,
              ),
              tooltip: 'Toggle theme',
              onPressed: onToggleTheme,
            ),
          ],
          bottom: const TabBar(
            isScrollable: true,
            tabs: [
              Tab(text: 'Asset File'),
              Tab(text: 'Inline String'),
              Tab(text: 'From Map'),
              Tab(text: 'Error Handling'),
              Tab(text: 'Controller'),
              Tab(text: 'Custom Style'),
              Tab(text: 'Builders'),
            ],
          ),
        ),
        body: const TabBarView(
          children: [
            _AssetDemo(),
            _InlineStringDemo(),
            _FromMapDemo(),
            _ErrorHandlingDemo(),
            _ControllerDemo(),
            _CustomStyleDemo(),
            _BuildersDemo(),
          ],
        ),
      ),
    );
  }
}

// ── Tab 1: Asset file with theme-aware config ──

class _AssetDemo extends StatelessWidget {
  const _AssetDemo();

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            'Loaded from assets/demo.toml',
            style: Theme.of(context).textTheme.labelLarge,
          ),
          Text(
            'Colours auto-adapt to light/dark theme via TomlViewerConfig.of(context)',
            style: Theme.of(context).textTheme.bodySmall,
          ),
          const SizedBox(height: 12),
          Expanded(
            child: TomlView.asset(
              'assets/demo.toml',
              config: TomlViewerConfig.of(context, expandMode: false),
            ),
          ),
        ],
      ),
    );
  }
}

// ── Tab 2: Inline TOML string ──

class _InlineStringDemo extends StatelessWidget {
  const _InlineStringDemo();

  static const _toml = '''
[app]
name = "My App"
version = "1.2.3"
debug = true

[app.window]
width = 1024
height = 768
fullscreen = false

[network]
timeout = 30
retries = 3
base_url = "https://api.example.com"

[[users]]
name = "Alice"
role = "admin"

[[users]]
name = "Bob"
role = "editor"

[[users]]
name = "Charlie"
role = "viewer"
''';

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            'Parsed from an inline TOML string',
            style: Theme.of(context).textTheme.labelLarge,
          ),
          const SizedBox(height: 12),
          Expanded(
            child: TomlView(
              content: _toml,
              config: TomlViewerConfig.of(context),
            ),
          ),
        ],
      ),
    );
  }
}

// ── Tab 3: From a pre-parsed Map ──

class _FromMapDemo extends StatelessWidget {
  const _FromMapDemo();

  static const _data = <String, dynamic>{
    'title': 'Pre-parsed data',
    'count': 42,
    'pi': 3.14159,
    'enabled': true,
    'tags': ['flutter', 'toml', 'viewer'],
    'nested': {
      'level1': {
        'level2': {
          'deep_value': 'hello from the deep',
        },
      },
    },
  };

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            'Rendered from a Map<String, dynamic>',
            style: Theme.of(context).textTheme.labelLarge,
          ),
          Text(
            'Useful when you already have parsed data or want to display any map as a tree',
            style: Theme.of(context).textTheme.bodySmall,
          ),
          const SizedBox(height: 12),
          Expanded(
            child: TomlView.fromMap(
              _data,
              config: TomlViewerConfig.of(context),
            ),
          ),
        ],
      ),
    );
  }
}

// ── Tab 4: Error handling ──

class _ErrorHandlingDemo extends StatefulWidget {
  const _ErrorHandlingDemo();

  @override
  State<_ErrorHandlingDemo> createState() => _ErrorHandlingDemoState();
}

class _ErrorHandlingDemoState extends State<_ErrorHandlingDemo> {
  final List<String> _errorLog = [];

  static const _brokenToml = '''
# This section is valid
[database]
server = "192.168.1.1"
port = 5432

# This section has an error (unterminated string)
[broken_section]
name = "missing end quote
value = 123

# This section is also valid
[logging]
level = "info"
enabled = true
''';

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            'Partial parse with inline error highlighting',
            style: Theme.of(context).textTheme.labelLarge,
          ),
          Text(
            'Valid sections render normally; broken sections show error annotations',
            style: Theme.of(context).textTheme.bodySmall,
          ),
          const SizedBox(height: 12),
          Expanded(
            flex: 3,
            child: TomlView(
              content: _brokenToml,
              config: TomlViewerConfig.of(
                context,
                onParseErrors: (errors) {
                  WidgetsBinding.instance.addPostFrameCallback((_) {
                    if (!mounted) return;
                    setState(() {
                      _errorLog.clear();
                      for (final e in errors) {
                        _errorLog.add('Line ${e.line}:${e.column} - ${e.message}');
                      }
                    });
                  });
                },
              ),
            ),
          ),
          if (_errorLog.isNotEmpty) ...[
            const Divider(),
            Text(
              'Error callback log:',
              style: Theme.of(context).textTheme.labelMedium,
            ),
            const SizedBox(height: 4),
            Expanded(
              flex: 1,
              child: Container(
                width: double.infinity,
                padding: const EdgeInsets.all(8),
                decoration: BoxDecoration(
                  color: Theme.of(context).colorScheme.surfaceContainerHighest,
                  borderRadius: BorderRadius.circular(8),
                ),
                child: ListView.builder(
                  itemCount: _errorLog.length,
                  itemBuilder: (context, i) => Text(
                    _errorLog[i],
                    style: Theme.of(context).textTheme.bodySmall?.copyWith(
                          fontFamily: 'monospace',
                        ),
                  ),
                ),
              ),
            ),
          ],
        ],
      ),
    );
  }
}

// ── Tab 5: Expand controller ──

class _ControllerDemo extends StatefulWidget {
  const _ControllerDemo();

  @override
  State<_ControllerDemo> createState() => _ControllerDemoState();
}

class _ControllerDemoState extends State<_ControllerDemo> {
  late final TomlExpandController _controller;

  @override
  void initState() {
    super.initState();
    _controller = TomlExpandController(defaultExpanded: false);
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  static const _toml = '''
[server]
host = "localhost"
port = 8080

[server.tls]
enabled = true
cert = "/etc/ssl/cert.pem"
key = "/etc/ssl/key.pem"

[database]
url = "postgres://localhost/mydb"
pool_size = 10

[cache]
driver = "redis"
ttl = 3600

[[routes]]
path = "/api/users"
method = "GET"
handler = "listUsers"

[[routes]]
path = "/api/users"
method = "POST"
handler = "createUser"
''';

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            'Programmatic expand/collapse control',
            style: Theme.of(context).textTheme.labelLarge,
          ),
          const SizedBox(height: 8),
          Wrap(
            spacing: 8,
            runSpacing: 8,
            children: [
              FilledButton.tonalIcon(
                onPressed: _controller.expandAll,
                icon: const Icon(Icons.unfold_more, size: 18),
                label: const Text('Expand All'),
              ),
              FilledButton.tonalIcon(
                onPressed: _controller.collapseAll,
                icon: const Icon(Icons.unfold_less, size: 18),
                label: const Text('Collapse All'),
              ),
              OutlinedButton(
                onPressed: () => _controller.toggle('server'),
                child: const Text('Toggle server'),
              ),
              OutlinedButton(
                onPressed: () => _controller.toggle('database'),
                child: const Text('Toggle database'),
              ),
              OutlinedButton(
                onPressed: () => _controller.toggle('server.tls'),
                child: const Text('Toggle server.tls'),
              ),
            ],
          ),
          const SizedBox(height: 12),
          Expanded(
            child: TomlView(
              content: _toml,
              expandController: _controller,
              config: TomlViewerConfig.of(context, expandMode: false),
            ),
          ),
        ],
      ),
    );
  }
}

// ── Tab 6: Custom style (fonts, spacing, icons, separator) ──

class _CustomStyleDemo extends StatelessWidget {
  const _CustomStyleDemo();

  static const _toml = '''
[project]
name = "toml_viewer"
version = "0.1.0"
description = "A Flutter TOML viewer"

[project.dependencies]
flutter = ">=3.0.0"
toml = "^0.15.0"

[[contributors]]
name = "Sudhi S"
role = "maintainer"

[[contributors]]
name = "Open Source"
role = "community"
''';

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            'Custom style: fonts, spacing, icons, separator',
            style: Theme.of(context).textTheme.labelLarge,
          ),
          Text(
            'TomlViewerStyle controls indentation, row spacing, separator text, expand icons, and per-element TextStyles',
            style: Theme.of(context).textTheme.bodySmall,
          ),
          const SizedBox(height: 12),
          Expanded(
            child: TomlView(
              content: _toml,
              config: TomlViewerConfig.of(context).copyWith(
                style: const TomlViewerStyle(
                  rootKeyStyle: TextStyle(
                    fontSize: 16,
                    fontWeight: FontWeight.bold,
                  ),
                  nonRootKeyStyle: TextStyle(fontSize: 14),
                  tableKeyStyle: TextStyle(
                    fontSize: 14,
                    fontStyle: FontStyle.italic,
                  ),
                  valueStyle: TextStyle(
                    fontSize: 14,
                    fontFamily: 'monospace',
                  ),
                  typeStyle: TextStyle(
                    fontSize: 12,
                    fontStyle: FontStyle.italic,
                  ),
                  indentation: 24,
                  rowSpacing: 6,
                  rowVerticalPadding: 6,
                  separator: ' : ',
                  separatorGap: 6,
                  collapsedIcon: Icons.add_circle_outline,
                  expandedIcon: Icons.remove_circle_outline,
                  expandIconSize: 18,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

// ── Tab 7: Custom builders & interaction callbacks ──

class _BuildersDemo extends StatelessWidget {
  const _BuildersDemo();

  static const _toml = '''
[server]
host = "localhost"
port = 8080
debug = true
max_connections = 1000
timeout = 30.5

[paths]
home = "/home/user"
config = "/etc/app/config.toml"
''';

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            'Custom builders & tap interactions',
            style: Theme.of(context).textTheme.labelLarge,
          ),
          Text(
            'valueBuilder renders booleans as icons and numbers with a tinted background; tap any value for a SnackBar',
            style: Theme.of(context).textTheme.bodySmall,
          ),
          const SizedBox(height: 12),
          Expanded(
            child: TomlView(
              content: _toml,
              config: TomlViewerConfig.of(context).copyWith(
                valueBuilder: (context, value, path) {
                  if (value is bool) {
                    return Chip(
                      avatar: Icon(
                        value ? Icons.check_circle : Icons.cancel,
                        size: 16,
                        color: value ? Colors.green : Colors.red,
                      ),
                      label: Text(value.toString(),
                          style: const TextStyle(fontSize: 12)),
                      visualDensity: VisualDensity.compact,
                      materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
                    );
                  }
                  if (value is num) {
                    return Container(
                      padding: const EdgeInsets.symmetric(
                          horizontal: 6, vertical: 2),
                      decoration: BoxDecoration(
                        color: Theme.of(context)
                            .colorScheme
                            .primaryContainer
                            .withValues(alpha: 0.3),
                        borderRadius: BorderRadius.circular(4),
                      ),
                      child: Text(
                        value.toString(),
                        style: TextStyle(
                          fontFamily: 'monospace',
                          color: Theme.of(context).colorScheme.primary,
                        ),
                      ),
                    );
                  }
                  return null; // default for everything else
                },
                onValueTap: (context, key, value, path) {
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(
                      content: Text('Tapped $path = $value'),
                      duration: const Duration(seconds: 1),
                    ),
                  );
                },
                onValueLongPress: (context, key, value, path) {
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(
                      content: Text('Long-pressed $path'),
                      duration: const Duration(seconds: 1),
                    ),
                  );
                },
              ),
            ),
          ),
        ],
      ),
    );
  }
}
5
likes
150
points
146
downloads
screenshot

Documentation

API reference

Publisher

verified publishersudhi.in

Weekly Downloads

A Flutter widget for displaying TOML files as interactive, expandable tree views with syntax highlighting, partial-parse error reporting, light/dark theme support, custom styling, extensible builders, and full customisation.

Repository (GitHub)
View/report issues
Contributing

Topics

#toml #viewer #config #tree-view #syntax-highlighting

Funding

Consider supporting this project:

github.com

License

MIT (license)

Dependencies

flutter, toml

More

Packages that depend on toml_viewer