勉強中にAIが生成してくれたモデルが Equatable
を継承していました。
🤔 ん?Equatableって何だっけ?
自分なりに調べてまとめてみました!
🧠 Equatableとは?
オブジェクト同士で比較できるようにするパッケージ!
Dartにはもともと ==
や hashCode
を使ってオブジェクトを比較する方法がありますが、
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 で自動生成 |