Configuring Isar in Flutter: indexes, queries, reactivity

When an app's database grows, queries start to slow down and the UI begins to lag. We set up local storage in Flutter turnkey: we design the schema, indexes, and reactive queries so that speed doesn't degrade as data grows. We'll discuss your task and come back with an estimate and a plan.

Development and support of all types of mobile applications:

Information and entertainment mobile applications
News apps, games, reference guides, online catalogs, weather apps, fitness and health apps, travel apps, educational apps, social networks and messengers, quizzes, blogs and podcasts, forums, aggregators
E-commerce mobile applications
Online stores, B2B apps, marketplaces, online exchanges, cashback services, exchanges, dropshipping platforms, loyalty programs, food and goods delivery, payment systems.
Business process management mobile applications
CRM systems, ERP systems, project management, sales team tools, financial management, production management, logistics and delivery management, HR management, data monitoring systems
Electronic services mobile applications
Classified ads platforms, online schools, online cinemas, electronic service platforms, cashback platforms, video hosting, thematic portals, online booking and scheduling platforms, online trading platforms

These are just some of the types of mobile applications we work with, and each of them may have its own specific features and functionality, tailored to the specific needs and goals of the client.

Showing 1 of 1All 1734 services
Configuring Isar in Flutter: indexes, queries, reactivity
Medium
from 1 day to 3 days

Our competencies:

Frequently Asked Questions

Latest works

  • Development of a mobile application for FEEDME
    Development of a mobile application for FEEDME
    971
  • Development of a mobile application for XOOMER
    Development of a mobile application for XOOMER
    869
  • Development of a mobile application for RHL
    Development of a mobile application for RHL
    1289
  • Development of a mobile application for ZIPPY
    Development of a mobile application for ZIPPY
    1143
  • Development of a mobile application for Affhome
    Development of a mobile application for Affhome
    1076
  • Development of a mobile application for the FLAVORS company
    Development of a mobile application for the FLAVORS company
    677

Isar Flutter: why local database queries slow down as data grows

A local Flutter database almost never slows down at the start: with a hundred records, any query responds instantly, and this creates a false sense that the schema is designed correctly.

The problem shows up later, when collections grow to tens of thousands of objects — the list screen starts opening with a delay, search "hangs" on input, and scrolling stutters.

The cause is not the storage itself, but the fact that indexes and queries to Isar Flutter were designed at random, without relying on real read scenarios.

The bottleneck is usually one of three. The first is filtering by a field without an index: instead of selecting by key, the engine scans the entire collection, and response time grows linearly with the data.

The second is sorting and pagination "the brute-force way," when the required order is not supported by an index and each query rebuilds the selection from scratch.

The third is reactive subscriptions to overly broad queries: any change in the collection rebuilds the entire list on the screen, even though only one record changed.

The cost of such an architecture is not visible immediately, but it accumulates: the user perceives the app as "heavy," the number of support requests grows, and the team spends time on spot "crutches" instead of fixing the schema.

Worst of all, the degradation is non-linear — up to a certain volume everything is tolerable, and then the behavior changes abruptly.

We break down how this is addressed: how to design indexes for real queries, limit reactive subscriptions, and keep response time stable regardless of how many records the user has accumulated.

What a well-designed data storage and access scheme delivers

Query speed in Isar is determined not by the size of the database, but by whether the query hits an index.

As long as a filter runs on a field without an index, the engine scans every record in the collection — with tens of thousands of objects this is noticeable to the naked eye: the list opens with a delay, and search "lags" as you type.

An index turns that scan into a point read, and a query by key or date range stops depending on how many records have accumulated in total.

Memory consumption grows because of the way the application reads data. A query that returns whole objects pulls all fields into memory, including heavy ones — nested lists, long texts, references to related collections.

If a screen only needs a name and a date, it is wiser to return a projection of the required fields and load related entities with a separate query by identifier. That way the memory peak on a screen stays flat instead of spiking every time a section is opened.

Reactivity is the third part of the picture. A subscription to a query delivers updates when data changes, but if you subscribe to the entire collection, a re-render is triggered by any change and drags along unnecessary work.

A subscription to a narrow indexed query updates only those screens that the change actually affects.

What this looks like in numbers

  • A query on an indexed field stops slowing down as the database grows: response time stays at the same level even with a tenfold increase in volume.
  • Projections instead of full objects reduce peak memory consumption on lists several times over.
  • Narrow subscriptions eliminate re-renders that are unrelated to data changes.

Resilience to growth is the consequence of these three decisions. A scheme with well-thought-out indexes, projections, and narrow subscriptions does not require rewriting the data access layer when collections grow by an order of magnitude.

Approaches to organizing local storage: comparing options

Local storage in Flutter is not "one library for everything." The choice depends on three things: the data structure (flat key-value pairs or related entities), the nature of access (point reads by identifier or queries with filters and sorting), and reactivity requirements — whether the interface should redraw itself when a record changes. Below are the real alternatives that teams choose between at the start of a project.

Option When it fits Limitations
Key-value storage Settings, flags, tokens, small reference lists No relationships between entities or conditional queries; complex filters have to be done in memory
Relational database Strict schema, reports, one-to-many relationships, aggregates Heavier to set up, schema migrations require manual control
Document storage Flexible schema, nested objects, quick start Relationships are less pronounced, some validation falls to the application code
Object storage with indexes Related entities, queries by fields, reactive streams Indexes need to be designed in advance, otherwise queries degrade as data grows

Access mode deserves separate mention: synchronous reads are convenient for small reference lists, but on large queries they block the interface, so heavy requests are moved to asynchronous streams.

Reactive mode — subscribing to collection changes — eliminates manual screen updates, but requires careful unsubscription, otherwise memory leaks occur.

Practical conclusion: for settings, key-value storage is enough; for account and related data, object storage with indexes; a relational database is justified where reports and aggregates are needed. Mixing modes in a single application is fine: a dedicated tool for each class of task.

How we set up Isar Flutter: stages of engineering work

Setting up Isar in Flutter is not "plug in the package and forget it" — it is working with the data schema: whether a search over the local database takes fractions of a second across tens of thousands of records or hits a full collection scan depends on how indexes and queries are designed. Below is how we carry out this work step by step, from audit to handover.

  1. Audit of the current schema. We look at collections, the relationships between them, field types, and which queries are actually executed in the application. It often turns out that some filters run on non-indexed fields, and nested objects are stored in such a way that a query pulls in extra data.
  2. Index design. For each search and sort scenario, we define a composite index with the correct field order: first the equality field, then the range, then the sort. We remove duplicate indexes — each one speeds up reads but slows down writes and takes up space in the database file.
  3. Query setup. We rewrite queries to match the indexes: we replace in-memory filtering with conditions on the database side, add pagination instead of loading the entire collection, and for reactive screens we wire up query observation instead of manually re-reading data after every change.
  4. Testing on real volumes. We run scenarios on data close to production: tens of thousands of records, concurrent writes, cold start. We measure query and write times, look at where locks appear, and adjust the schema before users see it.
  5. Migrations and compatibility. We think through how the schema will change going forward: versioning, data migration during app updates, behavior when rolling back to a previous version. This protects against loss of local data for some users after a release.
  6. Documentation and handover. We record the final schema, the purpose of each index, and the rules for adding new queries. Your team gets not a one-off patch, but a clear set of guidelines that keeps the database fast as the product evolves.

Describe your task — we will come back with an estimate and a work plan for your data schema.

Indexes, reactive queries, and schema migrations

An index in Isar is not "a speedup just in case" but a solution for a specific query.

A composite index on a pair of fields works only if the order of fields in the query matches the order in the index; when filtering by a single "trailing" field, it is not applied.

Therefore, we index what filtering and sorting actually use: external identifiers, statuses, dates. An index on a low-selectivity field (a flag with two values) grows the database file without any read benefit — it is better not to create it.

We build reactivity on watch queries rather than on manual state updates. The stream emits a new result set when the affected objects change, and the UI redraws selectively.

To avoid catching unnecessary rebuilds, we narrow the query to a specific list or a single object and do not subscribe to the entire collection: subscribing to a broad set triggers the interface on every write to the database.

Schema migrations are the most underrated part. Isar stores the schema version, and if it does not match, opening the database fails unless the transition is described.

We make data versioning explicit: we add a migration step, move fields, and when renaming, we create a new field and migrate the values rather than reusing the old one.

  • Indexes — only for real filters and sorts, with verification of field composition and order.
  • Reactive subscriptions — narrow, with unsubscription when leaving the screen so as not to keep the stream alive.
  • Schema versioning — an explicit version and step-by-step migrations instead of "recreate the database."
  • Opening error handling — a separate branch for a version mismatch rather than an application crash.
  • Migration tests — a run on a copy of real data before release, not on an empty database.

This approach to Isar eliminates the class of failures where, after an update, the application does not start for some users. We will look further at how this maps onto a specific data schema.

What the client receives after storage setup

Storage setup is not "installed a library and forgot about it." After the work, the client's team is left with a set of artifacts that allow the application to be developed without the author: a new developer opens the repository and sees how the data is structured, why these particular indexes were chosen, and what to do when the model changes. Below is exactly what we hand over.

  • Collection schemas — a description of each entity with field types, relationships, and uniqueness rules. They show which data is stored locally and which is pulled from the server, and where conflicts during synchronization are possible.
  • Index configurations — which fields are indexed, whether the indexes are composite or single, and which queries they were created for. We separately flag indexes that are expensive to maintain on writes, so they are not added "just in case."
  • Migration procedures — the step-by-step order for changing the schema: how a field is added, how data is transferred, how a failed migration is rolled back. With versioning, so that an update does not overwrite user data.
  • Query documentation — typical selections, filters, and sorts with code examples and an explanation of which index speeds them up. Plus a list of queries that are deliberately not optimized, and why.
  • Reactivity rules — which data streams subscribe to collection changes, how the interface updates, and how to avoid unnecessary re-renders during bulk writes.
  • Storage tests — checks for migration correctness, relationship integrity, and behavior during concurrent writes. They run in the shared build, not manually.
  • Maintenance guide — how to add a collection, rebuild indexes, roll back a release, and where to look when data diverges.

The set stays in your repository and is updated along with the code. Describe your task — we will come back with an estimate and a work plan for your application.

How is Isar different from other local solutions in Flutter?

Isar is an embedded object database that stores data in a single file and runs in the same process as the application.

Unlike solutions built on top of SQL, there is no separate translation layer between objects in code and rows in a table: the model is described by classes, and queries are built through a typed interface.

This eliminates an entire class of errors — schema desynchronization, manual parsing of results, type mismatches when reading.

The key difference from classic SQL solutions is the reactivity model. Isar can return a stream that updates itself when data changes, so the interface is redrawn without manually polling the database and without an intermediate state management layer.

Where with SQL you would have to manually invalidate the cache and monitor the freshness of the query results, here subscribing to a query solves the task entirely.

In terms of speed on bulk operations, Isar wins due to the absence of overhead for query translation and working with memory directly. Indexes and composite indexes are configured declaratively, so querying by multiple fields remains predictable even on large volumes.

But the advantage is not universal: complex analytical queries with grouping and joins are still more convenient and reliable to do in SQL — that is its native territory, not an add-on.

There are also scenarios where Isar falls short. If you need synchronization between devices with conflict resolution, there are no built-in mechanisms here — you will have to build the logic yourself.

For applications with strict requirements for server-side validation and a complex role-based access model, a relational database with schema-level constraints provides more guarantees.

And if the team already works closely with SQL, switching to an object model will require retraining and rewriting the data access layer.

In practice, the choice looks like this: Isar is chosen where the speed of local operations, a reactive interface, and simplicity of the model are important — lists, feeds, offline mode, caching server responses.

SQL remains stronger in reporting, complex relationships, and where data integrity is critical at the schema level. The decision is made not on a "better/worse" basis, but on what load profile a particular application has.

Case study: speeding up queries in an app with a growing database

What it was like before the changes

The app is a catalog with an offline mode built on Isar: the local database stores products, categories, and order history, while synchronization with the server runs in the background.

At launch everything worked, but the database grew: first tens of thousands of records, then hundreds of thousands. Category lists and search results began opening with a noticeable delay, and on low-end devices there were dropped frames during scrolling.

The root cause turned out to be not the volume of data, but the way we accessed it. Filters and sorting ran on fields without indexes, and the app pulled the entire set of objects from the database and filtered out the excess in memory.

At the same time, synchronization rewrote related entities in full, and reactive subscriptions on screens fired on every such update — the UI redrew more often than the data changed.

What was changed

We rebuilt the schema: we added composite indexes for real scenarios — category plus date, status plus modification date, and a separate index for searching by name.

We rewrote the queries so that filtering and sorting were performed on the database side rather than in memory, and we added pagination instead of reading the entire set.

We switched synchronization to targeted updates: instead of rewriting related entities in full, only the changed fields are modified.

We narrowed reactive subscriptions to specific queries — the list screen no longer reacts to an update of a product card that the user has not opened.

Metrics before and after

Metric Before After
Opening the category list 1.8–2.4 s 180–260 ms
Catalog search 3.1 s 240–310 ms
Memory consumption on the list screen 210–260 MB 90–120 MB
Dropped frames during scrolling regularly isolated

We also separately closed off a class of failures during synchronization: previously, if the connection dropped in the middle of rewriting related entities, some records remained in an intermediate state, and the user saw a discrepancy between the local database and the server.

Targeted updates within a single transaction eliminated this window — incomplete states no longer occur after a failure.

The work took two weeks, including a run on real database exports.

Common technical concerns about Isar

Isar in Flutter is adopted when an application needs a local database with reactive queries and predictable performance on large volumes. Below is a breakdown of the concerns teams most often come with.

How compatible is Isar with the current Flutter stack?

Isar is a native database for Dart, and it works on mobile platforms, on desktop, and in web builds through a separate backend.

Schemas are described with ordinary classes with annotations, and the generator builds access code at build time — no manual mapping of rows and columns is needed. If the project already has a repository layer, Isar fits behind it without rewriting screens.

What data volume can it handle without degradation?

Queries go through indexes rather than full scans, so a lookup by a composite key stays fast even with hundreds of thousands of records. Indexes are declared in the schema: unique, composite, case-insensitive.

It is important to keep an eye on the size of indexed fields — a bloated index slows down writes more than the database itself grows.

How does reactivity work, and will there be unnecessary rebuilds?

Subscribing to a query returns a stream that is rebuilt when the affected objects change. Updates are pinpoint: editing one record does not trigger recalculation of the entire collection if the query is limited by a filter. In practice, this removes manual state synchronization between screens.

What about migrations when the schema changes?

The schema version is stored in the database itself, and if it does not match, a migration is triggered: renaming fields, changing types, rebuilding indexes. User data is preserved during this — critical for applications where the local database is the only storage. Migrations are run on a copy of real data before release.

Platform support and access to the database file

A single codebase covers mobile platforms and desktop; the differences concern the file path and access permissions, which are handled by the initialization layer.

For the web, a separate storage mode is used — some native capabilities are unavailable there, and this is taken into account at the schema design stage.

If you still have questions about a specific scenario, describe the task, and we will get back to you with an estimate and a plan.

Let's discuss your local storage task

For the estimate to be accurate rather than "based on gut feeling", we need source data on your project: the data schema and relationships between entities, read and write scenarios, requirements for offline mode and synchronization.

The more specific the inputs, the fewer assumptions in the work plan and the more predictable the timelines.

Describe the task — we'll come back with an estimate and a work plan. In response you will receive:

  • an analysis of the current storage schema and bottlenecks: where queries do a full scan and where indexes are missing;
  • a proposal for the collection structure and the set of indexes for your query scenarios;
  • a reactive subscription scheme: which screens subscribe to what and how to avoid unnecessary UI re-renders;
  • a data migration plan between schema versions without losing user records;
  • a synchronization strategy with the server: conflict resolution, retries, operation during connection loss;
  • a list of checks on test datasets, including edge cases and large volumes;
  • an effort estimate by stage and the team composition for your scope.

If the application is already in production, we will separately discuss how to change the schema on a live database: versioning, backward compatibility, rollback to the previous version of the application. This is the class of tasks where a migration mistake costs more than the development itself.

Send access to the repository or an export of the models and a description of the key queries — that is enough to move from general words to a concrete plan.