logging_to_logcat 1.1.0 copy "logging_to_logcat: ^1.1.0" to clipboard
logging_to_logcat: ^1.1.0 copied to clipboard

PlatformAndroid

Adds activateLogcat() method to logging's Logger class which results in everything that's logged to Logger being displayed by Android's logcat.

example/lib/main.dart

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:logging_to_logcat/logging_to_logcat.dart';

const String fineMessage = 'This is a fine message';
const String configMessage = 'This is a config message';
const String infoMessage = 'This is an info message';
const String warningMessage = 'This is a warning message';
const String errorMessage = 'This is an error message';

void initLogging() {
  Logger.root
    ..level = Level.ALL
    ..activateLogcat();
}

void addLog() {
  final log = Logger('ExampleLogger');
  log
    ..fine(fineMessage)
    ..config(configMessage)
    ..info(infoMessage)
    ..warning(warningMessage)
    ..severe(errorMessage);
}

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

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  static const int _maximumRecords = 50;
  final List<LogRecord> _records = <LogRecord>[];
  late final StreamSubscription<LogRecord> _logSubscription;

  @override
  void initState() {
    super.initState();
    initLogging();
    _logSubscription = Logger.root.onRecord.listen((LogRecord record) {
      if (!mounted) {
        return;
      }

      setState(() {
        _records.add(record);
        if (_records.length > _maximumRecords) {
          _records.removeAt(0);
        }
      });
    });
  }

  @override
  void dispose() {
    _logSubscription.cancel();
    super.dispose();
  }

  void _clearRecords() {
    setState(_records.clear);
  }

  @override
  Widget build(BuildContext context) {
    final colorScheme = ColorScheme.fromSeed(
      seedColor: const Color(0xFF4F46E5),
    );
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: colorScheme,
        useMaterial3: true,
        scaffoldBackgroundColor: colorScheme.surface,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Logcat Relay'),
          actions: <Widget>[
            IconButton(
              tooltip: 'Clear entries',
              onPressed: _records.isEmpty ? null : _clearRecords,
              icon: const Icon(Icons.delete_outline),
            ),
          ],
        ),
        body: Column(
          children: <Widget>[
            _StatusCard(recordCount: _records.length),
            Expanded(
              child: _records.isEmpty
                  ? const _EmptyLogState()
                  : _LogList(records: _records),
            ),
            _ActionBar(onPressed: addLog),
          ],
        ),
      ),
    );
  }
}

class _StatusCard extends StatelessWidget {
  const _StatusCard({required this.recordCount});

  final int recordCount;

  @override
  Widget build(BuildContext context) {
    final colorScheme = Theme.of(context).colorScheme;
    return Card(
      margin: const EdgeInsets.fromLTRB(16, 12, 16, 8),
      color: colorScheme.secondaryContainer,
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Row(
          children: <Widget>[
            CircleAvatar(
              backgroundColor: colorScheme.secondary,
              foregroundColor: colorScheme.onSecondary,
              child: const Icon(Icons.terminal_rounded),
            ),
            const SizedBox(width: 12),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: <Widget>[
                  Text(
                    'Android Logcat',
                    style: Theme.of(context).textTheme.titleMedium,
                  ),
                  const SizedBox(height: 2),
                  Text(
                    'Forwarding Flutter logs in real time',
                    style: Theme.of(context).textTheme.bodySmall,
                  ),
                ],
              ),
            ),
            Column(
              crossAxisAlignment: CrossAxisAlignment.end,
              children: <Widget>[
                _ActiveBadge(color: colorScheme.primary),
                const SizedBox(height: 6),
                Text(
                  '$recordCount entries',
                  style: Theme.of(context).textTheme.labelMedium,
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

class _ActiveBadge extends StatelessWidget {
  const _ActiveBadge({required this.color});

  final Color color;

  @override
  Widget build(BuildContext context) {
    return DecoratedBox(
      decoration: ShapeDecoration(
        color: color.withValues(alpha: 0.14),
        shape: const StadiumBorder(),
      ),
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
        child: Row(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            Icon(Icons.circle, color: color, size: 8),
            const SizedBox(width: 5),
            Text('ACTIVE', style: Theme.of(context).textTheme.labelSmall),
          ],
        ),
      ),
    );
  }
}

class _EmptyLogState extends StatelessWidget {
  const _EmptyLogState();

  @override
  Widget build(BuildContext context) {
    final colorScheme = Theme.of(context).colorScheme;
    return Center(
      child: Padding(
        padding: const EdgeInsets.all(32),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            Icon(
              Icons.receipt_long_outlined,
              size: 56,
              color: colorScheme.outline,
            ),
            const SizedBox(height: 16),
            Text(
              'No entries yet',
              style: Theme.of(context).textTheme.titleMedium,
            ),
            const SizedBox(height: 6),
            Text(
              'Send the sample messages to see the records that are also written to Android Logcat.',
              textAlign: TextAlign.center,
              style: Theme.of(context).textTheme.bodyMedium,
            ),
          ],
        ),
      ),
    );
  }
}

class _LogList extends StatelessWidget {
  const _LogList({required this.records});

  final List<LogRecord> records;

  @override
  Widget build(BuildContext context) {
    return ListView.separated(
      padding: const EdgeInsets.fromLTRB(16, 8, 16, 12),
      itemCount: records.length + 1,
      separatorBuilder: (_, index) =>
          index == 0 ? const SizedBox(height: 4) : const SizedBox(height: 8),
      itemBuilder: (BuildContext context, int index) {
        if (index == 0) {
          return Text(
            'RECENT ENTRIES',
            style: Theme.of(context).textTheme.labelMedium,
          );
        }
        return _LogRecordCard(record: records[index - 1]);
      },
    );
  }
}

class _LogRecordCard extends StatelessWidget {
  const _LogRecordCard({required this.record});

  final LogRecord record;

  @override
  Widget build(BuildContext context) {
    final color = _levelColor(record.level, Theme.of(context).colorScheme);
    return Card(
      clipBehavior: Clip.antiAlias,
      margin: EdgeInsets.zero,
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Icon(_levelIcon(record.level), color: color),
            const SizedBox(width: 12),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: <Widget>[
                  Text(
                    record.message,
                    style: Theme.of(context).textTheme.bodyLarge,
                  ),
                  const SizedBox(height: 8),
                  Row(
                    children: <Widget>[
                      _LevelChip(level: record.level, color: color),
                      const SizedBox(width: 8),
                      Expanded(
                        child: Text(
                          '${_formatTime(record.time)}  ·  ${record.loggerName}',
                          maxLines: 1,
                          overflow: TextOverflow.ellipsis,
                          style: Theme.of(context).textTheme.labelMedium,
                        ),
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _LevelChip extends StatelessWidget {
  const _LevelChip({required this.level, required this.color});

  final Level level;
  final Color color;

  @override
  Widget build(BuildContext context) {
    return DecoratedBox(
      decoration: ShapeDecoration(
        color: color.withValues(alpha: 0.12),
        shape: const StadiumBorder(),
      ),
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
        child: Text(
          level.name,
          style: Theme.of(context).textTheme.labelSmall?.copyWith(color: color),
        ),
      ),
    );
  }
}

class _ActionBar extends StatelessWidget {
  const _ActionBar({required this.onPressed});

  final VoidCallback onPressed;

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      top: false,
      minimum: const EdgeInsets.fromLTRB(16, 8, 16, 16),
      child: SizedBox(
        width: double.infinity,
        child: FilledButton.icon(
          onPressed: onPressed,
          icon: const Icon(Icons.play_arrow_rounded),
          label: const Text('Emit sample logs'),
        ),
      ),
    );
  }
}

Color _levelColor(Level level, ColorScheme colorScheme) {
  if (level == Level.SHOUT || level == Level.SEVERE) {
    return colorScheme.error;
  }
  if (level == Level.WARNING) {
    return const Color(0xFFB45309);
  }
  if (level == Level.INFO) {
    return colorScheme.primary;
  }
  if (level == Level.CONFIG) {
    return const Color(0xFF7C3AED);
  }
  return const Color(0xFF0F766E);
}

IconData _levelIcon(Level level) {
  if (level == Level.SHOUT || level == Level.SEVERE) {
    return Icons.error_outline_rounded;
  }
  if (level == Level.WARNING) {
    return Icons.warning_amber_rounded;
  }
  if (level == Level.INFO) {
    return Icons.info_outline_rounded;
  }
  return Icons.terminal_rounded;
}

String _formatTime(DateTime time) {
  String twoDigits(int value) => value.toString().padLeft(2, '0');
  String threeDigits(int value) => value.toString().padLeft(3, '0');
  return '${twoDigits(time.hour)}:${twoDigits(time.minute)}:${twoDigits(time.second)}.${threeDigits(time.millisecond)}';
}
12
likes
160
points
5.16k
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Adds activateLogcat() method to logging's Logger class which results in everything that's logged to Logger being displayed by Android's logcat.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter, logging

More

Packages that depend on logging_to_logcat

Packages that implement logging_to_logcat