State Management
Each platform provides opinionated state management patterns out of the box — you inherit proven approaches instead of debating architecture.
Per-Platform Patterns
| Platform | Primary Pattern | Server State | Reactive Primitives |
|---|---|---|---|
| Flutter | SafeValueNotifier + Stores | Firestore streams | ValueListenableBuilder, SelectValueListenableBuilder |
| Angular | @ngrx/component-store | Firestore services | this.select(), this.effect(), this.updater() |
| React (Web) | TanStack Query + Context | TanStack Query | useQuery, useMutation, useInfiniteQuery |
| React Native | TanStack Query + Context | TanStack Query | Same as web (shared packages) |
Flutter
The Foundation provides SafeValueNotifier — a disposal-safe extension of Flutter’s ValueNotifier that silently drops updates and listener registrations after dispose():
class SafeValueNotifier<T> extends ValueNotifier<T> {
bool isDisposed = false;
@override
set value(T newValue) {
if (isDisposed) return;
super.value = newValue;
}
}State is organized in layers:
| Layer | Role | Example |
|---|---|---|
| Stores | Domain state holders — manage data and expose it via SafeValueNotifier | ConversationStore, JobTemplateStore |
| Controllers | Screen-level orchestrators — combine stores and services for UI coordination | SaveAsTemplateController |
| SDK instances | Feature state — initialized via Sdk.initialize() and resolved from DI | AuthSdk, MessagingSdk |
SelectValueListenableBuilder only rebuilds when a specific slice of state changes:
SelectValueListenableBuilder(
valueListenable: _value,
selector: (v) => v.currentTheme,
builder: (context, currentTheme, child) {
return E11ThemeProvider(data: currentTheme, child: widget.child);
},
)Migration note: The codebase is transitioning from GetX observables (
.obs,Rx,Obx) toSafeValueNotifier+ValueListenableBuilder. New code uses value notifiers exclusively; existing GetX usage is refactored on touch.
Angular
SDK libraries exclusively use @ngrx/component-store — a lightweight, component-scoped reactive state container:
@Injectable({ providedIn: 'root' })
export class CommunityStore extends ComponentStore<CommunityState> {
readonly communities$ = this.select(state => state.communities);
readonly loadCommunities = this.effect<void>(trigger$ =>
trigger$.pipe(
switchMap(() => this.communityService.getAll().pipe(
tapResponse(
communities => this.updateCommunities(communities),
error => this.handleError(error),
),
)),
),
);
readonly updateCommunities = this.updater(
(state, communities: Community[]) => ({ ...state, communities }),
);
}Every SDK provides its own ComponentStore. Product apps inherit the pattern and use EntityAdapter for collection management.
React / React Native
Server state is managed via TanStack Query with custom hooks wrapping the IRepository abstraction:
// Firestore document query (provided by Foundation)
const { data, isLoading } = useDocumentQuery<UserModel>(
userRepository,
userId,
);
// Firestore collection with realtime updates
const { data: messages } = useCollectionQuery<Message>(
messageRepository,
{ where: [['conversationId', '==', id]] },
{ subscribe: true },
);
// Infinite scroll
const { data, fetchNextPage } = useCollectionInfiniteQuery<Job>(
jobRepository,
{ orderBy: ['createdAt', 'desc'], limit: 20 },
);The Foundation provides mutation hooks for add, update, delete, set, transaction, and batch operations — all with automatic cache invalidation.
| State Type | Solution |
|---|---|
| Server / async | TanStack Query (queries, mutations, infinite scroll, realtime subscriptions) |
| Auth | Platform auth context + Firebase auth state listeners |
| Theme | CSS variables + ThemeProvider (web), E11ThemeProvider context (mobile) |
| URL | nuqs for search params (web) |
| Services | DI container via useContainer() |
What You Get
- Pre-configured state management per platform — no architecture debates
- SDK stores for every Foundation domain (auth, messaging, notifications, files, etc.)
- Reactive data layer with caching, invalidation, and realtime support
- Selector patterns that minimize unnecessary re-renders/rebuilds
What You Build on Top
- Product-specific stores/queries for domain data
- Screen-level controllers or hooks that compose Foundation stores
- Custom selectors for product-specific state slices
Next Steps
- Architecture — How state management fits the layered architecture
- Platforms — Platform-specific details