1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Flutter / Dart】Equatableってなんぞや

Last updated at Posted at 2025-06-10

勉強中にAIが生成してくれたモデルが Equatable を継承していました。

🤔 ん?Equatableって何だっけ?

自分なりに調べてまとめてみました!

🧠 Equatableとは?

オブジェクト同士で比較できるようにするパッケージ!

Dartにはもともと ==hashCode を使ってオブジェクトを比較する方法がありますが、
Equatableを使うとその処理を超シンプルに書けます!

📦 Equatableの導入方法

equatable

まずはパッケージの追加方法から👇

pubspec.yamlに以下を追記:

dependencies:
  equatable: ^2.0.5

✅ 結論

「Dart の == / hashCode を、最小限のコードで “値比較” に切り替えられる便利パッケージ」

コードベースで理解する Equatable

Equatableを使わない場合

class User {
  final String name;
  final int age;

  User(this.name, this.age);
}

void main() {
  final user1 = User('太郎', 25);
  final user2 = User('太郎', 25);

  print(user1 == user2); // false 😢
}

結果はfalse!
→ 異なるインスタンスとみなされてしまうためです。

手動で比較を実装すると…

@override
bool operator ==(Object other) {
  return other is User &&
    other.name == name &&
    other.age == age;
}

@override
int get hashCode => name.hashCode ^ age.hashCode;

書くのも面倒だし、プロパティが増えたら保守も大変…!

✅ Equatableを使った場合

import 'package:equatable/equatable.dart';

class User extends Equatable {
  final String name;
  final int age;

  User(this.name, this.age);

  @override
  List<Object?> get props => [name, age];
}

props リストに比較対象を追加するだけ!

void main() {
  final user1 = User('太郎', 25);
  final user2 = User('太郎', 25);

  print(user1 == user2); // true 🎉
}

🧩 propsリストのポイントと注意点

propsとは?

Equatableで比較対象とするプロパティを指定するgetterメソッド!

比較したいものだけ props に追加しましょう💡
比較対象外にしたいものはリストから外すだけ!

⚠️ 注意:可変リストは直接比較されない!

class BadExample extends Equatable {
  final List<String> items;

  @override
  List<Object?> get props => [items]; // 同じ内容でも別リストなら≠
}

たとえ同じ要素でも、異なるリストインスタンスなら比較結果は false に!

✅ 解決策:List.from()やスプレッド演算子を使う

SearchState(results: [...apiResults]) // これで比較可能に!

→ 新しいリストを作ることで、前回のリストと 内容 で比較できるようになります

🧪 比較対象から外した場合の例

class User extends Equatable {
  final String name;
  final int age;
  final String id;

  User(this.name, this.age, this.id);

  @override
  List<Object?> get props => [name, age]; // idは比較しない!
}

void main() {
  final user1 = User('太郎', 25, 'abc123');
  final user2 = User('太郎', 25, 'xyz789');

  print(user1 == user2); // true 🎯
}

toString()も自動生成できる!

Equatableには stringify という便利プロパティがあります!

class User extends Equatable {
  final String name;
  final int age;

  User(this.name, this.age);

  @override
  List<Object?> get props => [name, age];

  @override
  bool get stringify => true; // これでtoString()も自動生成!
}

print(User('太郎', 25)); // 出力: User(太郎, 25)

ログ出力も見やすくなるので、デバッグにも最適!

プロパティ追加時の違い

手動の場合(めんどい…)

@override
bool operator ==(Object other) {
  return other is User &&
    other.name == name &&
    other.age == age &&
    other.email == email &&  // 追加
    other.phone == phone;    // 追加
}

@override
int get hashCode =>
  name.hashCode ^
  age.hashCode ^
  email.hashCode ^   // 追加
  phone.hashCode;    // 追加

✅ Equatableなら(超ラク!)

@override
List<Object?> get props => [name, age, email, phone]; // 追加するだけ!

🔥 状態管理との相性も◎

検索画面で考えると分かりやすい!

商品クラスと検索状態

class Product extends Equatable {
  final String id;
  final String name;

  const Product(this.id, this.name);

  @override
  List<Object?> get props => [id, name];
}
class SearchState extends Equatable {
  final String keyword;
  final List<Product> results;
  final bool isLoading;

  const SearchState({
    required this.keyword,
    required this.results,
    required this.isLoading,
  });

  @override
  List<Object?> get props => [keyword, results, isLoading];
}

💡 ポイント
searchAPIから返されるresultsは、たとえ検索結果の内容が同じでも、APIを呼び出すたびに新しいリストインスタンスが生成されます。
しかしEquatableがリストの中身を一つひとつ比較してくれるおかげで、SearchState全体としては「同じ状態」だと正しく判定できるのです。

🔍 検索処理と結果の比較

class SearchNotifier extends StateNotifier<SearchState> {
  SearchNotifier() : super(
    SearchState(keyword: '', results: [], isLoading: false)
  );

  Future<void> search(String keyword) async {
    state = SearchState(
      keyword: keyword,
      results: state.results,
      isLoading: true,
    );

    final results = await searchAPI(keyword);

    state = SearchState(
      keyword: keyword,
      results: results,
      isLoading: false,
    );
  }
}

🧪 実際の挙動

// 通常検索
searchNotifier.search('本');     // 画面更新 ✅
searchNotifier.search('本棚');   // 画面更新 ✅

// 同じ結果の再検索
await searchNotifier.search('本棚'); 
// → 新しいStateインスタンスは作られるが、
//   Riverpodは古いStateと新しいStateを `==` で比較する。
// → Equatableが「前と同じ内容」と判定するため `true` となり、
//   Riverpodは「状態は変化していない」と判断して再描画をスキップ! 🎉

Equatableにより同じ状態と判断される=無駄な再描画を防げる!

Equatableを使わないと…

// 🚨 Equatableなしの具体的な問題
// 1. スクロール位置がリセット 
// 2. 選択状態が消える 
// 3. アニメーションが途切れる 
// 4. バッテリー消費増加 
print('SearchScreen全体が再ビルドされました!');

毎回ビルドされてパフォーマンス悪化…💦

📝 まとめ

Equatableのメリット

メリット 内容
✅ コードが簡潔 == / hashCodeの記述が不要!
✅ 比較漏れ防止 props で明示できる
✅ 保守性が高い プロパティ追加もラクラク
✅ 状態管理と相性◎ Bloc / Riverpodでの再描画制御に便利
toString()も楽! stringify = true で自動生成
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?