Kache is a type-safe stale-while-revalidate cache for Dart and Flutter. It can show the last usable value immediately, refresh in the background, preserve data when refresh fails, and expose the complete operation state to any UI or state-management layer.
The core package has no third-party runtime dependency. Persistence, Flutter, and state-management integrations are separate packages.
| Package | Purpose | Runtime boundary |
|---|---|---|
kache |
Cache state machine, concurrency, policies, memory backend | Dart SDK only |
kache_flutter |
Scope, controller, builder, listener, app lifecycle | Flutter + kache |
kache_hive_ce |
Hive CE TypeAdapter/native records, codecs, migrations | Hive CE + kache |
kache_connectivity_plus |
Automatic reconnect revalidation | connectivity_plus + kache |
kache_riverpod |
Provider/family/auto-dispose notifier integration | Riverpod + kache |
kache_bloc |
KacheCubit and composable binding |
Bloc + kache |
kache_provider |
Provider widgets and context helpers | Provider + kache_flutter |
Choose one top-level integration package in application code. Add a lower
level package only when your source imports it directly, such as
kache_hive_ce for persistence or flutter_bloc for Bloc widgets.
flutter pub add kache_flutterDeclare what to fetch, place one client at the app boundary, and render the snapshot. The default policy shows cached data immediately and revalidates it in the background.
import 'package:flutter/material.dart';
import 'package:kache_flutter/kache_flutter.dart';
typedef Profile = ({String name});
Widget createProfileApp({required Future<Profile> Function() fetchProfile}) =>
KacheScope(
client: KacheClient(),
ownership: KacheScopeOwnership.owned,
child: MaterialApp(
home: Scaffold(
body: KacheBuilder<Profile>(
query: KacheQuery<Profile>.memory(
key: KacheKey('profile'),
fetch: (_) => fetchProfile(),
),
builder: (context, snapshot, controller) => snapshot.when(
idle: () => const SizedBox.shrink(),
loading: () => const Center(child: CircularProgressIndicator()),
failed: (_) => Center(
child: FilledButton(
onPressed: controller.load,
child: const Text('Try again'),
),
),
ready: (profile) => _profileList(
profile,
controller,
refreshing: snapshot.isRefreshing,
),
refreshError: (profile, _) =>
_profileList(profile, controller, refreshFailed: true),
),
),
),
),
);
Widget _profileList(
Profile profile,
KacheController<Profile> controller, {
bool refreshing = false,
bool refreshFailed = false,
}) => RefreshIndicator(
onRefresh: () async => controller.refresh(),
child: ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: <Widget>[
ListTile(
title: Text(profile.name),
subtitle: refreshFailed
? const Text('Refresh failed - showing cached data')
: null,
trailing: refreshing
? const CircularProgressIndicator()
: const Icon(Icons.cloud_done),
),
],
),
);KacheBuilder loads automatically. when handles idle, first loading,
no-data failure, ready data, and retained-data refresh failure exhaustively.
Refreshing keeps calling ready by default; use skipLoadingOnRefresh: false
only when a refresh should replace the data UI. Pull to refresh uses the same
query and request deduplication.
KacheQuery.memory keeps data for the lifetime of the client. Use
KacheQuery.persisted with kache_hive_ce when data must survive an app
restart.
Pure Dart applications can depend on the zero-third-party kache package and
use its resource stream directly. See the
core quick start for ownership and
cleanup.
| Requirement | Policy |
|---|---|
| Show cached data and refresh on load/resume | KachePolicy.staleWhileRevalidate() |
| Avoid requests during a fresh window | KachePolicy.cacheFirst(freshFor: ...) |
| Never fetch automatically | KachePolicy.cacheOnly() |
| Always fetch and do not cache | KacheQuery.networkOnly(...) |
staleAfter controls freshness. expireAfter is a hard boundary after which
data is removed instead of emitted. gcAfter controls how long an unreferenced
in-memory entry remains available.
Set refreshInterval to poll only while a resource handle is loaded and
active. Same-key polling remains single-flight. pausePolling() and
resumePolling() control timers without disabling manual cache commands.
KacheQuery.networkOnly accepts the same interval without enabling a cache.
The core accepts any KacheNetwork implementation and remains Dart SDK-only.
When a configured source changes from unavailable to available, active
handles apply their own refreshOnReconnect policy. Reconnect requests are
single-flight and coalesce to at most one trailing pass.
Flutter apps can use kache_connectivity_plus as the official adapter. Network
interface availability is only a retry signal, not proof that the Internet or
an endpoint is reachable. Source errors are reported as connectivity events
without discarding cached data.
KacheClient() is memory-only. To survive restarts, configure a
KachePersistenceBackend and create persisted queries with a binding owned by
that backend. The official implementation is kache_hive_ce.
Serialization is intentionally not part of the core package. A storage
adapter owns codecs, physical records, schema versions, migrations, encryption
configuration, and corruption handling. The core only receives typed values
and cache metadata. kache_hive_ce can reuse an already registered
TypeAdapter<T> with store.bindAdapter<T>(adapter) or use its explicit byte
codec binding for independent schema control.
Implement KachePersistenceBackend for SQLite, Isar, files, secure storage, or
another system. Your backend must:
- return typed
KachePersistenceRead<T>values; - validate that a binding belongs to the backend;
- preserve
KachePersistedMetadata; - implement exact namespace-prefix clearing;
- wrap I/O and codec errors in
KachePersistenceException; - report the actual persistence operation in every wrapped exception;
- define idempotent ownership and
close()behavior.
The core validates the operation field at its boundary. A mismatched backend exception is treated as a backend failure for the operation actually running, while the original exception and stack trace remain available in the failure.
Use MemoryKachePersistence and the contract tests as a reference. Do not add
codec methods to KacheQuery or the core persistence protocol.
Snapshots carry failures as data. Streams do not use addError for expected
fetch or persistence failures. KacheFailure retains the original cause and
stack trace while its toString() stays sanitized. Command-oriented code can
call snapshot.throwIfFailed() or clearResult.throwIfFailed().
Subscribe to KacheClient.events or provide an observer for logging and
telemetry. Events never include payloads or raw key values by default.
cacheHit, cacheMiss, and cacheExpired identify their memory or
persistence layer without changing resource state.
Every client.watch(query) returns an independently disposable
KacheResource. Canceling a stream listener does not dispose the resource.
Release the handle with resource.dispose(), then close clients and owned
backends at the application boundary.
Flutter applications should use KacheScope, which can own the client and
pause polling outside the foreground before revalidating active resources when
the app resumes. It also defers reconnect work outside the foreground and
consumes one pending recovery on resume. State adapters own their resource
handles but never own the supplied client.
Each published package declares and tests its own minimum SDK instead of inheriting the newer toolchain used by this monorepo.
| Package | Dart | Flutter |
|---|---|---|
kache |
>=3.5.0 <4.0.0 | Not required |
kache_flutter |
>=3.5.0 <4.0.0 | >=3.24.0 |
kache_hive_ce |
>=3.5.0 <4.0.0 | Not required |
kache_riverpod |
>=3.7.0 <4.0.0 | Not required |
kache_bloc |
>=3.5.0 <4.0.0 | Not required |
kache_connectivity_plus |
>=3.5.0 <4.0.0 | >=3.24.0 |
kache_provider |
>=3.5.0 <4.0.0 | >=3.24.0 |
Official adapters track Hive CE >=2.19.3 <3.0.0, connectivity_plus
>=7.2.0 <8.0.0, Riverpod >=3.3.2 <4.0.0, Bloc
>=9.2.1 <10.0.0, and Provider >=6.1.5+1 <7.0.0.
The examples/ directory contains runnable Flutter, Riverpod, Bloc/Cubit, and
Provider applications. Each uses the GitHub repository API and Hive CE to
demonstrate cold loading, disk-cache-first restart, refresh, reconnect
revalidation, retained data on failure, and explicit cache clearing.
Kache is available under the MIT License.