🎯

Dart Enum . 記法の使い所

に公開

Swiftみたいに省略記法があった!

SwiftのEnumには、.を使って定数を呼び出す記法があるのですが、Dartにはないのかなと思っていたら、あった😅

強強モバイルエンジニアたっつーさんに教えていただきました🙇
https://x.com/tatsutakein/status/2001304233300328802?s=20

本当にあったよ(°_°)

https://dart.dev/language/dot-shorthands

enum LogLevel { debug, info, warning, error }

/// Returns the color code to use for the specified log [level].
String colorCode(LogLevel level) {
  // Use dot shorthand syntax for enum values in switch cases:
  return switch (level) {
    .debug => 'gray', // Instead of LogLevel.debug
    .info => 'blue', // Instead of LogLevel.info
    .warning => 'orange', // Instead of LogLevel.warning
    .error => 'red', // Instead of LogLevel.error
  };
}

// Example usage:
String warnColor = colorCode(.warning); // Returns 'orange'

以前だと
昔からある方法。

final appBarColor = switch (_selectedLevel) {
      LogLevel.debug => Colors.grey,
      LogLevel.info => Colors.blue,
      LogLevel.warning => Colors.orange,
      LogLevel.error => Colors.red,
    };

.で省略すると...
短くなった!

final appBarColor = switch (_selectedLevel) {
  .debug => Colors.grey,
  .info => Colors.blue,
  .warning => Colors.orange,
  .error => Colors.red,
};

こちらの本にも使い方解説しております👇

https://zenn.dev/joo_hashi/books/34fa9e03ab3440/viewer/cce490

🤔活用例を考える❓

公式がログレベルのサンプルコードを出していたのでログレベルを表示するソースコードを考えてみた。といっても自分で全部やったわけではなくClaudeCode + Flutter & Dart MCP Serverを活用しました。こっちの方が綺麗なコード書いてくれたので😅

こちらがデモ

https://youtube.com/shorts/MS5_0P4FG-w?si=akFebv4ifnAoUyc3

sample
main.dart
import 'package:flutter/material.dart';

enum LogLevel { debug, info, warning, error }

/// Record型でログレベルの表示情報を返す
/// Dart 3.0 switch式の `.` shorthand を使用(swift caseは不使用)
({Color color, IconData icon, String title, String message, Color? bg}) levelInfo(LogLevel level) {
  return switch (level) {
    .debug => (
      color: Colors.grey,
      icon: Icons.bug_report,
      title: 'Debug Mode',
      message: 'Detailed logs enabled for debugging.',
      bg: null,
    ),
    .info => (
      color: Colors.blue,
      icon: Icons.info,
      title: 'Info',
      message: 'General information about app state.',
      bg: null,
    ),
    .warning => (
      color: Colors.deepOrange,
      icon: Icons.warning_amber_rounded,
      title: 'Warning',
      message: 'Something needs attention.',
      bg: Colors.orange.shade100,
    ),
    .error => (
      color: Colors.red,
      icon: Icons.error_outline,
      title: 'CRITICAL ERROR',
      message: 'Immediate action required!',
      bg: Colors.red.shade50,
    ),
  };
}

/// ログレベルに応じたWidgetを返す(switch式でWidget自体を切り替え)
Widget buildLogWidget(LogLevel level) {
  final info = levelInfo(level);

  return switch (level) {
    .debug => _DebugWidget(info: info),
    .info => _InfoWidget(info: info),
    .warning => _WarningWidget(info: info),
    .error => _ErrorWidget(info: info),
  };
}

/// Debug用Widget
class _DebugWidget extends StatelessWidget {
  final ({Color color, IconData icon, String title, String message, Color? bg}) info;

  const _DebugWidget({required this.info});

  
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        Icon(info.icon, size: 80, color: info.color),
        const SizedBox(height: 16),
        Text(
          info.title,
          style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: info.color),
        ),
        Text(info.message, style: TextStyle(color: Colors.grey.shade600)),
      ],
    );
  }
}

/// Info用Widget
class _InfoWidget extends StatelessWidget {
  final ({Color color, IconData icon, String title, String message, Color? bg}) info;

  const _InfoWidget({required this.info});

  
  Widget build(BuildContext context) {
    return Card(
      elevation: 2,
      child: Padding(
        padding: const EdgeInsets.all(24.0),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(info.icon, size: 60, color: info.color),
            const SizedBox(height: 12),
            Text(
              info.title,
              style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: info.color),
            ),
            const SizedBox(height: 8),
            Text(info.message),
          ],
        ),
      ),
    );
  }
}

/// Warning用Widget
class _WarningWidget extends StatelessWidget {
  final ({Color color, IconData icon, String title, String message, Color? bg}) info;

  const _WarningWidget({required this.info});

  
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(20),
      decoration: BoxDecoration(
        color: info.bg,
        borderRadius: BorderRadius.circular(12),
        border: Border.all(color: Colors.orange, width: 2),
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Icon(info.icon, size: 40, color: info.color),
          const SizedBox(width: 16),
          Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            mainAxisSize: MainAxisSize.min,
            children: [
              Text(
                info.title,
                style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: info.color),
              ),
              Text(info.message, style: TextStyle(color: info.color)),
            ],
          ),
        ],
      ),
    );
  }
}

/// Error用Widget
class _ErrorWidget extends StatelessWidget {
  final ({Color color, IconData icon, String title, String message, Color? bg}) info;

  const _ErrorWidget({required this.info});

  
  Widget build(BuildContext context) {
    return Card(
      elevation: 8,
      color: info.bg,
      child: Padding(
        padding: const EdgeInsets.all(32.0),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(info.icon, size: 60, color: info.color),
            const SizedBox(height: 16),
            Text(
              info.title,
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: info.color),
            ),
            const SizedBox(height: 8),
            Text(info.message, style: TextStyle(color: info.color)),
          ],
        ),
      ),
    );
  }
}

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

class MainApp extends StatelessWidget {
  const MainApp({super.key});

  
  Widget build(BuildContext context) {
    return const MaterialApp(home: LogLevelDemoView());
  }
}

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

  
  State<LogLevelDemoView> createState() => _LogLevelDemoViewState();
}

class _LogLevelDemoViewState extends State<LogLevelDemoView> {
  LogLevel _selectedLevel = LogLevel.info;

  
  Widget build(BuildContext context) {
    // switch式でAppBarの色を取得
    final appBarColor = switch (_selectedLevel) {
      .debug => Colors.grey,
      .info => Colors.blue,
      .warning => Colors.orange,
      .error => Colors.red,
    };

    return Scaffold(
      appBar: AppBar(
        title: const Text('Log Level Switcher'),
        backgroundColor: appBarColor,
        foregroundColor: Colors.white,
      ),
      body: Column(
        children: [
          Expanded(
            child: Center(
              child: AnimatedSwitcher(
                duration: const Duration(milliseconds: 300),
                child: KeyedSubtree(
                  key: ValueKey(_selectedLevel),
                  child: buildLogWidget(_selectedLevel),
                ),
              ),
            ),
          ),
          Container(
            padding: const EdgeInsets.all(16.0),
            color: Colors.grey.shade100,
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                const Text(
                  'Select Log Level:',
                  style: TextStyle(fontWeight: FontWeight.bold),
                ),
                const SizedBox(height: 10),
                Wrap(
                  alignment: WrapAlignment.center,
                  spacing: 10,
                  runSpacing: 10,
                  children: LogLevel.values.map((level) {
                    final isSelected = _selectedLevel == level;
                    final buttonColor = levelInfo(level).color;
                    return ElevatedButton(
                      style: ElevatedButton.styleFrom(
                        backgroundColor: isSelected ? buttonColor : Colors.white,
                        foregroundColor: isSelected ? Colors.white : buttonColor,
                        side: BorderSide(color: buttonColor),
                        elevation: isSelected ? 2 : 0,
                      ),
                      onPressed: () {
                        setState(() {
                          _selectedLevel = level;
                        });
                      },
                      child: Text(level.name.toUpperCase()),
                    );
                  }).toList(),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

最後に

省略記法の.があると今の参画してる案件でFlutterに強いエンジニアさんから教わったのですが本当にあったとは。。。
SwiftのEnumみたいにSwitchのところで、.の省略記法が使えるのは有り難かった!

SwiftのEnum .が気になる方いたらこちら見てみてください👀

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/enumerations/

directionToHead の型は、CompassPoint の取りうる値のいずれかで初期化されるときに推論されます。一度 directionToHeadCompassPoint として宣言されると、より短いドット構文を使って別の CompassPoint の値に設定できます。

directionToHead = .east

directionToHead の型はすでに判明しているため、その値を設定する際に型名を省略できます。これにより、明示的に型指定された列挙値を使用するコードは非常に読みやすくなります。

Switch文による列挙値のマッチング

個々の列挙値をswitch文で照合できます。

directionToHead = .south
switch directionToHead {
case .north:
    print("Lots of planets have a north")
case .south:
    print("Watch out for penguins")
case .east:
    print("Where the sun rises")
case .west:
    print("Where the skies are blue")
}
// Prints "Watch out for penguins".

Discussion