Enumerated Annotations in Flutter Isar: The Data Corruption Trap

Using Isar’s @enumerated annotation for Dart enums risks severe data corruption, a danger many developers overlook due to its apparent…

Back to all articles

Using Isar’s @enumerated annotation for Dart enums risks severe data corruption, a danger many developers overlook due to its apparent convenience.

💥 The core issue lies in the brittle, hidden dependency @enumerated creates. It stores enum values based on the exact definition of your enum at the time of writing, using either:

  • EnumType.index: Stores the integer position (0, 1, 2…)
  • EnumType.name: Stores the literal string name (“pending”, “inProgress”…)

Any subsequent change to the enum’s definition in your Dart code (reordering, inserting, deleting, or renaming members) will cause Isar to misinterpret existing stored data when it’s read back using the new definition. This leads to silent data corruption or broken data links.

Critically, Isar provides no built-in safe migration path for these enum changes. Furthermore, the original Isar package is abandoned, meaning these flaws are permanent. While the isar_community fork is a valuable effort, it updates only sporadically, and migrating requires separate evaluation.

This is not theoretical. In our own production applications at Saropa, we identified 50+ instances of @enumerated usage. Before we fixed it, this led to real-world database corruption for our users and fixing this across the codebase and migrating client data was a significant, costly, and painful undertaking.

This guide dissects the vulnerability, illustrates the failure modes, and provides a robust mitigation strategy to safeguard your application data.

“The only constant in life is change.” — Heraclitus

How it Fails: Paths to Corruption

The vulnerability lies in how @enumerated persists enums, leading to two failure modes when definitions change:

Scenario 1: Index Corruption

Your initial enum looks like this:

// pending=0, completed=1
enum TaskStatus { pending, completed }

You save a task with the status TaskStatus.completed, so Isar stores the index 1. Later, you insert a new status:

// pending=0, reviewed=1, completed=2
enum TaskStatus { pending, reviewed, completed }

When your application now loads the task where index 1 was stored, Isar uses the new definition mapping to TaskStatus.reviewed. Your original completed task is silently corrupted.

Scenario 2: Name Corruption

Failure Mode 2: Using EnumType.name (Storing String Name “pending”, “medium”…)

Your initial enum looks like this:

enum Priority { low, medium, high }

You save a task with Priority.medium. Isar stores the literal string name “medium”. Later, you refactor the enum, renaming medium to normal:

// 'medium' is gone, replaced by 'normal'
enum Priority { low, normal, critical }

When loading the task, Isar looks for a Priority member matching the stored string “medium”. Since no member with that name exists in the new definition, Isar cannot map the value. This typically results in a deserialization error or the field becoming null. The original priority information is effectively lost or inaccessible.

In Summary: Data loss occurs when the enum definition in your Dart code changes (members are reordered, inserted, deleted, or renamed).

Why This is Especially Dangerous

Several factors compound the risk:

  1. No Automatic Migration: Isar provides no mechanism to handle these enum definition changes automatically. If you modify an enum used with @enumerated, the responsibility falls entirely on you to manually detect the change and write complex, error-prone migration code to update all affected records before the new app version reads the data.
  2. No Nullable Enum Support: @enumerated fields could not be marked as nullable (?). This often forced developers needing optional enum values into using String? or int? representations anyway, inheriting the same fundamental risks tied to enum evolution if not handled carefully, and still requiring manual migration logic.
  3. Abandoned: As the original package is unmaintained, these flaws are permanent.

Mitigation: The Safe Nullable String Pattern

The most robust way to handle enums safely in Isar 3.1.8 (and generally recommended for schema evolution) is to store a stable string representation and handle the mapping within your application code.

Recommended Pattern:

// PATTERN FOR ISAR
enum TaskStatus {
  pending, inProgress, completed;

  static TaskStatus? find(String? name) { /* ... robust find logic ... */ }
}

enum TaskPriority {
  low, normal, critical; // Note: 'medium' was renamed to 'normal'

  static TaskPriority? find(String? name) {
    if (name == null) return null;
    
    // Example mapping during read - optionally flag for updating
    // if (name == 'medium') return TaskStatus.normal;

    return TaskPriority.values.firstWhereOrNull((e) => e.name == name);
  }
}

@collection
class TaskDBModel {
  TaskDBModel({this.description, this.statusName, this.priorityName});

  Id id = Isar.autoIncrement;

  String? description;

  @Index()
  String? statusName; /// ref: [status]

  @Index()
  String? priorityName; /// ref: [priority]

  /// helper for [statusName]
  @ignore TaskStatus? get status => TaskStatus.find(statusName);

  /// helper for [priorityName]
  @ignore TaskPriority? get priority => TaskPriority.find(priorityName);
}

“Distrust and caution are the parents of security.” — Benjamin Franklin

Data Migration Steps

If you are currently using @enumerated, migrating to the safe pattern is crucial:

  1. Add New Fields: Add the new String? enumName fields (e.g., statusName, priorityName) to your Isar model class, marked with @Index() if you need to query them.
  2. Write Migration Logic: Create a one-time migration routine. This routine must:
    - Query all existing records containing the old @enumerated field.
    - For each record, read the enum value from the @enumerated field.
    - Get its .name property (the string representation).
    - Write this string name to the new String? enumName field.
    - Save the updated record back to Isar.
  3. Deploy & Monitor: Deploy the application version containing both the migration logic (which should run once) and the new code using the String? / @ignore pattern. Monitor carefully for any issues.
  4. Remove Old Fields (Optional): Once you are confident the migration is complete and stable, create a new schema version that removes the old, unsafe @enumerated fields to clean up your database model.

Important Considerations

  • Performance: Querying by indexed String? fields is generally efficient, though potentially slightly less optimized than Isar’s internal (but unsafe) enum handling. Storing strings uses marginally more space than integer indices. These are minor tradeoffs for data integrity.
  • Long-Term Alternatives: Given the critical issues in an unmaintained version like 3.1.8, strongly consider migrating your persistence layer entirely to actively maintained and safer alternatives such as Drift (Moor), Realm, ObjectBox, or even sqflite with manual mapping.

Urgent Recommendations for Isar Developers

  1. Understand Your Exposure: Immediately search your entire codebase for any usage of “@enumerated”.
  2. Prioritize Migration: If @enumerated is found, urgently plan and execute the data migration to the String? + @ignore getter pattern described above. This is critical for data safety.
  3. Refactor Queries: Update all Isar queries that previously targeted @enumerated fields to use the new String? fields.

Take Action for Data Safety

Isar’s @enumerated feature harbors a critical risk of data corruption due to its flawed coupling with evolving enum definitions.

This isn’t a theoretical concern; it causes real-world data loss and necessitates costly fixes, as experienced firsthand at Saropa. The lack of safe migration paths and the package’s abandoned status make it imperative to address this vulnerability.

For existing projects, migrating away from @enumerated is a critical step. For the long term, evaluate actively supported database alternatives.

Don’t let the Isar’s enumerated trap catch you or your users!


References:

[edit: removed excessive emoji use]

Final Word 🪅

Illustration from article
saropa.com
Share this article

Your feedback is essential to us, and we genuinely value your support. When we learn of a mistake, we acknowledge it with a correction. If you spot an error, please let us know at blog@saropa.com and learn more at saropa.com.

Originally published by Saropa on Medium on April 16, 2025. Copyright © 2025