Flutter: Dart言語速習

DartはFlutterの魂です — Dartをマスターすることは、交響曲を演奏する前に楽譜を読むことを学ぶようなものです。

📋 前提条件: 以下を先に完了している必要があります

1. このレッスンで学ぶこと


2. 開発者のリアルなストーリー

(1) 悩み:コールバック地獄と型の混乱

Bobはバックエンド開発者からFlutterに転向しました。JavaScriptの柔軟性に慣れていた彼は、Dartで何度も壁にぶつかりました:varfinalを混同し、非同期コールバックを5レベル深くネストし、ジェネリックコレクションの型エラーに悩まされました。ショッピングカートの価格計算中、暗黙のintからdoubleへの変換バグにより99.99ドルが99ドルになり — リリース後にユーザーからの苦情が殺到しました。

(2) Dartのソリューション

Dartは強い型付け言語であり、コンパイル時に型エラーを検出します。async/awaitにより非同期コードが同期コードのように明確になり、final/constが意図しない変更を防ぎます。

DART
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;

// Simplified class definition (for demonstration)
class Product {
  final int id;
  final String name;
  final double price;
  final String? imageUrl;
  const Product({required this.id, required this.name, required this.price, this.imageUrl});
  factory Product.fromJson(Map<String, dynamic> json) => Product(
    id: json['id'] as int,
    name: json['name'] as String,
    price: (json['price'] as num).toDouble(),
    imageUrl: json['image_url'] as String?,
  );
  Product copyWith({String? name, double? price}) => Product(
    id: id, name: name ?? this.name, price: price ?? this.price, imageUrl: imageUrl,
  );
  static List<Product> fromJsonList(List data) => data.map((j) => Product.fromJson(j as Map<String, dynamic>)).toList();
}

class CartItem {
  final Product product;
  final double price;
  final int quantity;
  CartItem({required this.product, required this.price, this.quantity = 1});
}

// Strong typing prevents price bugs
double calculateTotal(List<CartItem> items) {
  return items.fold(0.0, (sum, item) => sum + item.price * item.quantity);
}

// async/await eliminates callback hell
Future<List<Product>> fetchProducts() async {
  final response = await http.get(Uri.parse('/api/v1/products'));
  return Product.fromJsonList(jsonDecode(response.body));
}
TEXT
> 出力: Flutter SDK(Flutter 3.x / Dart 3.x)でローカルに実行してください。PistonサーバーにはFlutterがインストールされていません。ローカルマシンで`flutter run`を使用して比較してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

(3) 結果:型安全性 + 明確な非同期処理

Dartに切り替えた後、型エラーはコンパイル時に検出され、非同期コードはフラットになり、ショッピングカートの価格精度問題は二度と発生しませんでした。


3. 変数と型システム

Dartは型推論とSound Null Safetyを持つ強い型付け言語です。

100%
sequenceDiagram
    participant Bob
    participant ShopAPI
    Bob->>ShopAPI: fetchProducts() [async]
    ShopAPI-->>Bob: Future<List<Product>>
    Bob->>Bob: await parses JSON
    Bob->>Bob: Renders product list
TEXT
> 出力: Flutter SDK(Flutter 3.x / Dart 3.x)でローカルに実行してください。PistonサーバーにはFlutterがインストールされていません。ローカルマシンで`flutter run`を使用して比較してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

(1) 基本型

説明
int 64ビット整数 int quantity = 3;
double 64ビット浮動小数点 double price = 99.99;
String UTF-16文字列 String name = "ShopApp";
bool ブール値 bool inStock = true;
num int + doubleの親型 num value = 42;

(2) 変数宣言キーワード

キーワード 可変性 代入タイミング 用途
var 可変 実行時 型が推論可能なローカル変数
final 不変 実行時 実行時定数(例:APIレスポンス)
const 不変 コンパイル時 コンパイル時定数(例:カラー値)
late 遅延初期化 実行時 使用前に必ず代入する必要がある

▶ サンプル

TEXT
> 出力: Flutter SDK(Flutter 3.x / Dart 3.x)でローカルに実行してください。PistonサーバーにはFlutterがインストールされていません。ローカルマシンで`flutter run`を使用して比較してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

: 変数宣言の比較

DART
// var: type inferred, mutable
var productName = 'Flutter T-Shirt';
productName = 'Dart Hoodie'; // OK

// final: runtime constant, immutable
final double totalPrice = 29.99 * 3;
// totalPrice = 100.0; // Error: final variable

// const: compile-time constant
const double discountRate = 0.15;
const String storeName = 'ShopApp';

// late: deferred initialization
late String userToken;
void login() {
  userToken = 'eyJhbGciOiJIUzI1NiIs...';
}
TEXT
> 出力: Flutter SDK(Flutter 3.x / Dart 3.x)でローカルに実行してください。PistonサーバーにはFlutterがインストールされていません。ローカルマシンで`flutter run`を使用して比較してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

4. コレクション操作

(1) List、Map、Set

コレクション 特徴 リテラル構文
List<E> 順序あり、重複を許可 [1, 2, 3]
Map<K,V> キーと値のペア {'key': 'value'}
Set<E> 順序なし、一意 {1, 2, 3}

▶ サンプル

TEXT
> 出力: Flutter SDK(Flutter 3.x / Dart 3.x)でローカルに実行してください。PistonサーバーにはFlutterがインストールされていません。ローカルマシンで`flutter run`を使用して比較してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

: 基本的なコレクション操作

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

// Product class definition shown above

// List: ordered collection
List<Product> products = [
  Product(id: 1, name: 'Laptop', price: 1299.99),
  Product(id: 2, name: 'Phone', price: 899.99),
];

// Map: key-value pairs
Map<String, double> prices = {
  'Laptop': 1299.99,
  'Phone': 899.99,
  'Tablet': 599.99,
};

// Set: unique items
Set<String> categories = {'Electronics', 'Clothing', 'Books'};

// Access and modify
products.add(Product(id: 3, name: 'Tablet', price: 599.99));
prices['Headphones'] = 199.99;
categories.add('Electronics'); // Ignored, already exists
TEXT
> 出力: Flutter SDK(Flutter 3.x / Dart 3.x)でローカルに実行してください。PistonサーバーにはFlutterがインストールされていません。ローカルマシンで`flutter run`を使用して比較してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

(2) collection-if / collection-for

Dart独自のコレクション内条件/ループ構文は、UI構築を大幅に簡素化します。

▶ サンプル

TEXT
> 出力: Flutter SDK(Flutter 3.x / Dart 3.x)でローカルに実行してください。PistonサーバーにはFlutterがインストールされていません。ローカルマシンで`flutter run`を使用して比較してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

: collection-ifとcollection-for

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

// Product class definition shown above

// collection-if: conditional items
bool isAdmin = true;
var menuItems = [
  'Home',
  'Products',
  'Cart',
  if (isAdmin) 'Admin Panel',
];

// collection-for: expand items
var productNames = ['Laptop', 'Phone', 'Tablet'];
var descriptions = [
  for (var name in productNames) '$name - Best Price'
];
// ['Laptop - Best Price', 'Phone - Best Price', 'Tablet - Best Price']

// Combined in Widget list
Widget buildProductList(List<Product> products) {
  return Column(
    children: [
      for (var p in products)
        ListTile(title: Text(p.name), trailing: Text('\$${p.price}')),
    ],
  );
}
TEXT
> 出力: Flutter SDK(Flutter 3.x / Dart 3.x)でローカルに実行してください。PistonサーバーにはFlutterがインストールされていません。ローカルマシンで`flutter run`を使用して比較してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

5. 関数の機能

(1) パラメータの種類

種類 構文 必須 デフォルト値
位置指定 (a, b) はい なし
オプショナル位置指定 ([a = 0]) いいえ 対応
名前付き ({required a}) required時は必須 対応
オプショナル名前付き ({a = 0}) いいえ 対応

▶ サンプル

TEXT
> 出力: Flutter SDK(Flutter 3.x / Dart 3.x)でローカルに実行してください。PistonサーバーにはFlutterがインストールされていません。ローカルマシンで`flutter run`を使用して比較してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

: 関数パラメータのパターン

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

// Named parameters (recommended for Flutter)
Widget buildPriceTag({
  required double price,
  String currency = 'USD',
  double fontSize = 16.0,
}) {
  return Text(
    '$currency \$${price.toStringAsFixed(2)}',
    style: TextStyle(fontSize: fontSize),
  );
}

// Call with named params
buildPriceTag(price: 99.99);
buildPriceTag(price: 49.99, currency: 'EUR', fontSize: 20.0);
TEXT
> 出力: Flutter SDK(Flutter 3.x / Dart 3.x)でローカルに実行してください。PistonサーバーにはFlutterがインストールされていません。ローカルマシンで`flutter run`を使用して比較してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

(2) 匿名関数とクロージャ

▶ サンプル

TEXT
> 出力: Flutter SDK(Flutter 3.x / Dart 3.x)でローカルに実行してください。PistonサーバーにはFlutterがインストールされていません。ローカルマシンで`flutter run`を使用して比較してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

: Flutterにおけるクロージャ

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

// Product class definition shown above

// Anonymous function (lambda)
var expensiveItems = products.where((p) => p.price > 500.0).toList();

// Closure: captures surrounding variables
double discountThreshold = 100.0;
var discountedItems = products.map((p) {
  // Closure captures discountThreshold
  if (p.price > discountThreshold) {
    return p.copyWith(price: p.price * 0.9);
  }
  return p;
}).toList();

// Callback in Flutter widget
ElevatedButton(
  onPressed: () {
    // Closure captures context variables
    addToCart(product);
  },
  child: const Text('Add to Cart'),
)
TEXT
> 出力: Flutter SDK(Flutter 3.x / Dart 3.x)でローカルに実行してください。PistonサーバーにはFlutterがインストールされていません。ローカルマシンで`flutter run`を使用して比較してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

6. 非同期プログラミング

(1) Futureとasync/await

概念 説明 例え
Future<T> 非同期操作のプレースホルダー レストランの注文番号
async 非同期関数をマーク システムに「待ちます」と伝える
await Futureの完了を待つ 食べる前に料理を待つ

▶ サンプル

TEXT
> 出力: Flutter SDK(Flutter 3.x / Dart 3.x)でローカルに実行してください。PistonサーバーにはFlutterがインストールされていません。ローカルマシンで`flutter run`を使用して比較してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

: async/awaitによるネットワークリクエスト

DART
import 'dart:convert';
import 'package:http/http.dart' as http;

// Product class definition shown above

Future<List<Product>> fetchProducts({int page = 1}) async {
  try {
    final response = await http.get(
      Uri.parse('https://api.shopapp.com/v1/products?page=$page'),
    );
    if (response.statusCode == 200) {
      final List data = jsonDecode(response.body);
      return data.map((json) => Product.fromJson(json)).toList();
    } else {
      throw Exception('Failed to load products: ${response.statusCode}');
    }
  } catch (e) {
    // Handle network errors
    rethrow;
  }
}
TEXT
> 出力: Flutter SDK(Flutter 3.x / Dart 3.x)でローカルに実行してください。PistonサーバーにはFlutterがインストールされていません。ローカルマシンで`flutter run`を使用して比較してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

(2) Stream:非同期データフロー

▶ サンプル

TEXT
> 出力: Flutter SDK(Flutter 3.x / Dart 3.x)でローカルに実行してください。PistonサーバーにはFlutterがインストールされていません。ローカルマシンで`flutter run`を使用して比較してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

: リアルタイムカート更新のためのStream

DART
import 'dart:async';

// Simplified class definition (for demonstration)
class Product {
  final String name;
  final double price;
  const Product({required this.name, required this.price});
}

class CartItem {
  final Product product;
  int quantity;
  CartItem({required this.product, this.quantity = 1});
}

// StreamController for cart updates
class CartBloc {
  final _cartStream = StreamController<List<CartItem>>.broadcast();

  Stream<List<CartItem>> get cartStream => _cartStream.stream;
  List<CartItem> _items = [];

  void addToCart(Product product) {
    _items.add(CartItem(product: product, quantity: 1));
    _cartStream.sink.add(_items);
  }

  void dispose() {
    _cartStream.close();
  }
}

// Listen to stream
cartBloc.cartStream.listen((items) {
  print('Cart updated: ${items.length} items, total: \$${calculateTotal(items)}');
});
TEXT
> 出力: Flutter SDK(Flutter 3.x / Dart 3.x)でローカルに実行してください。PistonサーバーにはFlutterがインストールされていません。ローカルマシンで`flutter run`を使用して比較してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

7. クラスとミックスイン

(1) クラスとコンストラクタ

コンストラクタの種類 構文 目的
デフォルト ClassName() 標準的なインスタンス化
名前付き ClassName.named() 複数の初期化方法
ファクトリ factory ClassName() インスタンス生成の制御(例:キャッシュ/シングルトン)
Const const ClassName() コンパイル時定数オブジェクト

▶ サンプル

TEXT
> 出力: Flutter SDK(Flutter 3.x / Dart 3.x)でローカルに実行してください。PistonサーバーにはFlutterがインストールされていません。ローカルマシンで`flutter run`を使用して比較してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

: 商品データモデル

DART
class Product {
  final int id;
  final String name;
  final double price;
  final String? imageUrl;
  final double rating;

  // Main constructor with named params
  const Product({
    required this.id,
    required this.name,
    required this.price,
    this.imageUrl,
    this.rating = 0.0,
  });

  // Named constructor from JSON
  factory Product.fromJson(Map<String, dynamic> json) {
    return Product(
      id: json['id'] as int,
      name: json['name'] as String,
      price: (json['price'] as num).toDouble(),
      imageUrl: json['image_url'] as String?,
      rating: (json['rating'] as num?)?.toDouble() ?? 0.0,
    );
  }

  // Copy with pattern for immutable updates
  Product copyWith({String? name, double? price}) {
    return Product(
      id: id,
      name: name ?? this.name,
      price: price ?? this.price,
      imageUrl: imageUrl,
      rating: rating,
    );
  }
}
TEXT
> 出力: Flutter SDK(Flutter 3.x / Dart 3.x)でローカルに実行してください。PistonサーバーにはFlutterがインストールされていません。ローカルマシンで`flutter run`を使用して比較してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

(2) ミックスイン:コードの再利用

▶ サンプル

TEXT
> 出力: Flutter SDK(Flutter 3.x / Dart 3.x)でローカルに実行してください。PistonサーバーにはFlutterがインストールされていません。ローカルマシンで`flutter run`を使用して比較してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

: 共通機能のためのMixin

DART
// Product class definition shown above

// Mixin: reusable behavior without inheritance
mixin PriceFormatter {
  String formatPrice(double price, {String currency = 'USD'}) {
    return '$currency \$${price.toStringAsFixed(2)}';
  }

  String formatDiscount(double original, double discounted) {
    double percent = ((original - discounted) / original * 100);
    return '${percent.toStringAsFixed(0)}% OFF';
  }
}

// Apply mixin to class
class ProductCard with PriceFormatter {
  final Product product;
  ProductCard(this.product);

  String get priceLabel => formatPrice(product.price);
  String get discountLabel => formatDiscount(100.0, product.price);
}

// Abstract class: cannot be instantiated
abstract class Repository<T> {
  Future<List<T>> getAll();
  Future<T> getById(int id);
  Future<T> create(T item);
}
TEXT
> 出力: Flutter SDK(Flutter 3.x / Dart 3.x)でローカルに実行してください。PistonサーバーにはFlutterがインストールされていません。ローカルマシンで`flutter run`を使用して比較してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

8. 完成例:ShopAppデータレイヤー

⚙️ 依存パッケージのインストール: flutter pub add http

DART
import 'dart:convert';
import 'package:http/http.dart' as http;

// Model
class Product {
  final int id;
  final String name;
  final double price;
  final String category;

  const Product({
    required this.id,
    required this.name,
    required this.price,
    required this.category,
  });

  factory Product.fromJson(Map<String, dynamic> json) => Product(
        id: json['id'] as int,
        name: json['name'] as String,
        price: (json['price'] as num).toDouble(),
        category: json['category'] as String,
      );
}

// Repository with async/await
class ProductRepository {
  final String baseUrl = 'https://api.shopapp.com/v1';

  Future<List<Product>> fetchProducts({int page = 1, int limit = 20}) async {
    final response = await http.get(
      Uri.parse('$baseUrl/products?page=$page&limit=$limit'),
    );
    if (response.statusCode != 200) {
      throw Exception('API error: ${response.statusCode}');
    }
    final List data = jsonDecode(response.body)['items'] as List;
    return data.map((j) => Product.fromJson(j as Map<String, dynamic>)).toList();
  }

  Future<Product> fetchById(int id) async {
    final response = await http.get(Uri.parse('$baseUrl/products/$id'));
    return Product.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
  }
}

// Usage
void main() async {
  final repo = ProductRepository();
  final products = await repo.fetchProducts(page: 1);
  for (var p in products) {
    print('${p.name}: \$${p.price.toStringAsFixed(2)}');
  }
}
TEXT
> 出力: Flutter SDK(Flutter 3.x / Dart 3.x)でローカルに実行してください。PistonサーバーにはFlutterがインストールされていません。ローカルマシンで`flutter run`を使用して比較してください。実際のUI/状態はプラットフォームにより多少異なる場合があります。

❓ よくある質問

Q finalとconstの本当の違いは?
A finalは実行時に一度だけ代入され、変更できません。constはコンパイル時に決定され、リテラルまたはconstコンストラクタでなければなりません。
Q Dartに三項演算子はありますか?
A はい、condition ? value1 : value2のほか、??(null認識合体演算子)と?.(null安全アクセス)もあります。
Q 非同期関数はFutureを返さなければならないのですか?
A いいえ、しかし推奨されます。非同期関数はvoidを返す場合でも非同期で実行されます。
Q StreamとFutureの違いは?
A Futureは一度きりの非同期結果です。Streamは継続的に生成される非同期データシーケンスで、リアルタイムの株価のようなものです。
Q ミックスインと抽象クラスのどちらを選ぶべきか?
A is-a関係なしに振る舞いを追加する(例:ログ、フォーマット)場合はミックスインを使用。is-a関係を必要とする型契約を定義する場合は抽象クラスを使用します。
Q Dartはnull安全性に対応していますか?
A Dart 2.12+ではSound Null Safetyが強制されます。Stringはnullにできず、String?はnullにできます。コンパイラが実行時のnull参照エラーを保証します。

📖 まとめ


📝 練習問題

  1. 基本(難易度 ⭐): id(int)、name(String)、email(String?)を持つUserクラスを作成し、fromJsonファクトリコンストラクタを実装してください。
  2. 中級(難易度 ⭐⭐): async/awaitを使ったシミュレーションネットワークリクエスト関数を書き、Future<List<Product>>を返すようにしてください。Future.delayedで2秒の遅延をシミュレートします。
  3. チャレンジ(難易度 ⭐⭐⭐): StreamControllerを使ってカート変更をブロードキャストするCartManagerクラスを実装し、アイテムの追加/削除とクリア操作に対応してください。

← 前へ | 次へ →

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%