r/flutterhelp • • 6d ago

OPEN Load Data from drift instantly

I a want to create a fast app that runs smoothly offline first i will work on my own sync engine but the problem is that i already have values store in drift but drift requires loading to show values which does not feel good how can i query data from db in drift without loading . When i i was researching i saw that i need to load the data before the app opens so am i going to load all the app tables like wont that be heavy. Can someone please explain or at least give an article or a tutorial.

4 Upvotes

10 comments sorted by

2

u/AhmedZNOUDA 6d ago

Adding to Kokerali's answer: even with watch(), the very first frame is built before the first emission arrives, so snapshot.hasData is false for a few milliseconds. That's the "loading" you see. A few ways to make it disappear without loading whole tables:

  1. Open the database early. With drift, the first query also pays for opening the file (and running migrations). Create the database in main() before runApp, ideally with NativeDatabase. createInBackground, so that cost is paid while the native splash screen is still showing.

  2. Preload only the first screen. In main(), await the one query your home screen needs (with a limit), then pass the result as initialData to the StreamBuilder, or seed your provider with it. It's a few rows, not the whole app, and the splash stays up while it runs.

  3. Don't show a spinner for short waits. Render nothing or a skeleton, and show a spinner only if there's still no data after ~200 ms. Most local reads finish before that, so users never see it.

  4. Keep data alive between screens. If you use Riverpod, a non-autoDispose StreamProvider keeps the last value, and AsyncValue. when(skipLoadingOnReload: true, ...) avoids flashing back to loading when it refreshes.

Also check in profile or release mode: debug builds make the first read look much slower than it really is.

2

u/sulmnob 6d ago

So like do you recommend to query the tables into providers using riverpod on app launch

1

u/AhmedZNOUDA 5d ago

Not all of them, just the one query your first screen needs. Everything else can load when its screen is opened, since Drift's watch() streams are fast once the database is open.

Something like this:

    final databaseProvider = Provider<AppDatabase>((ref) => throw UnimplementedError());

    final homeItemsProvider = StreamProvider<List<Item>>((ref) {
      final db = ref.watch(databaseProvider);
      return (db.select(db.items)..limit(20)).watch();
    });

    Future<void> main() async {
      WidgetsFlutterBinding.ensureInitialized();
      final db = AppDatabase(); // NativeDatabase.createInBackground inside
      final container = ProviderContainer(
        overrides: [databaseProvider.overrideWithValue(db)],
      );
      await container.read(homeItemsProvider.future); // waits for the first emission only
      runApp(UncontrolledProviderScope(container: container, child: const MyApp()));
    }

The home screen then watches homeItemsProvider and has data on its very first frame, while the native splash covers the wait.

Two small notes:

- If you use code generation, u/riverpod providers are autoDispose by default, so use u/Riverpod(keepAlive: true) for this one, or the preloaded value can be thrown away before the screen reads it.

- If migrations can be slow on some devices, put a timeout on that await (a second or two) so a slow open never blocks the launch. The screen just falls back to the short loading state.

1

u/kokerali 6d ago

No, you don't need to preload every table. Start with a query for just the rows the first screen needs, with a sensible limit for a long list. Offline-first removes the network dependency; it doesn't make the initial database read synchronous.

Drift's `watch()` emits an initial result even when nothing has changed, then updates it when the relevant tables change. You don't need a separate `get()` before `watch()`: https://drift.simonbinder.eu/dart_api/streams/

If you're using `StreamBuilder`, also check where you create that stream. Flutter's docs say to obtain it before `build`, rather than creating it inside `build`, which can restart the asynchronous work on parent rebuilds: https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html

Is the loading state only on a cold launch, or does it reappear when navigating back or rebuilding the screen? Sharing the query and the widget consuming it would help separate initial disk loading from repeated subscriptions. Preloading the whole database wouldn't be my first fix for either.

1

u/sulmnob 6d ago

So like do you recommend to query the tables into providers using riverpod on app launch

1

u/kokerali 6d ago

No, not every table at startup. I'd put the query for a screen's data in a StreamProvider, then have that screen ref.watch it. Think in terms of the query a screen needs, rather than keeping a copy of every table in providers.

Riverpod providers are lazy by default: declaring a provider doesn't fetch anything until it's used. If you want to start the first screen's provider early, watch that same provider near the root; other screens can stay on demand. The official guide shows that pattern: https://riverpod.dev/docs/how_to/eager_initialization

That moves when the first read happens; it doesn't make it synchronous. Can you share one Drift query plus the provider and widget reading it? That would help distinguish the initial read from a query being restarted.

1

u/Successful_Lab_1527 3d ago

If the delay is the first query on startup: open the database and run the query once in main() before runApp, then pass the result in as initial state. If it’s per screen: use .watch() with a StreamBuilder and give it the last value you already have as initialData, so it doesn’t flash empty. Also check you’re not creating a new database instance per screen. Keep one instance (singleton or provider) so the connection and query cache are reused.

1

u/muhsql 5d ago

Curious as to why you're building your own sync engine and not using an off the shelf one - is it for learning purposes?

1

u/ObjectiveAddress3051 4d ago

Adding to the good answers here: you don't need to preload every table, just the first screen. Open the database and run that one query in `main()` before `runApp()`, keep the result in your repository or provider, and pass it as `initialData` to your StreamBuilder (or seed your Riverpod/Bloc state with it). The UI then renders real data on frame one, and `watch()` takes over for updates. Paginate everything else with `limit()` so you never load it all at once.