# fbae_core

Flutter Base App Engine — pluggable infrastructure package for Flutter apps.

## Overview

`fbae_core` provides production-ready infrastructure out of the box: HTTP client with auth, navigation shell, theming, state management boilerplate, env config, and logging. Designed to be used as a dependency — consumer apps call `FbaeCore.init()` once in `main()` and get everything wired.

## Bootstrap

```dart
import 'package:fbae_core/fbae_core.dart';

await FbaeCore.init(
  config: FbaeConfig.dev(baseUrl: 'https://api.example.com'),
  tokenProvider: () async => await storage.read('accessToken'),
  onRefreshToken: () async { /* refresh and return new token */ },
);
runApp(const MyApp());
```

Bootstrap order is guaranteed: logger → DI (get_it) → HTTP → BlocObserver → plugins.

## Key Classes

### FbaeCore
- `FbaeCore.init({required FbaeConfig config, AppLogger? logger, TokenProvider? tokenProvider, RefreshTokenCallback? onRefreshToken, List<FbaePlugin> plugins})` — bootstrap entry point, call once in main()
- `FbaeCore.instance` — singleton accessor after init
- `FbaeCore.instance.httpClient` — returns the configured FbaeHttpClient
- `FbaeCore.instance.logger` — returns the active AppLogger

### FbaeConfig
- `FbaeConfig.dev(baseUrl:)` — development config, LogLevel.debug
- `FbaeConfig.staging(baseUrl:)` — staging config, LogLevel.info
- `FbaeConfig.production(baseUrl:)` — production config, LogLevel.none
- Fields: `baseUrl`, `envName`, `timeout`, `logLevel`, `featureFlags`, `errorMessageKey`, `errorCodeKey`

### FbaeHttpClient (interface)
- `get<T>(path, {queryParams, fromJson})` — GET request, throws ApiError on non-2xx
- `post<T>(path, {body, fromJson})` — POST request
- `put<T>(path, {body, fromJson})` — PUT request
- `delete<T>(path, {fromJson})` — DELETE request
- Bearer token auto-injected when `tokenProvider` is passed to `FbaeCore.init()`
- Token refresh on 401 via `QueuedInterceptor` (no duplicate refresh calls on concurrent requests)

### ApiError
- `statusCode` — HTTP status code (0 for network errors)
- `message` — parsed from response body using `FbaeConfig.errorMessageKey` (default: `'message'`)
- `code` — parsed from response body using `FbaeConfig.errorCodeKey` (default: `'code'`)
- `rawBody` — raw response string when JSON parsing fails

### TokenProvider / RefreshTokenCallback
- `TokenProvider = Future<String?> Function()` — returns current access token or null
- `RefreshTokenCallback = Future<String> Function()` — performs refresh, returns new access token

### AppLogger (interface)
- `debug(message)`, `info(message)`, `warning(message)`, `error(message, {error, stackTrace})`
- Default implementation: `TalkerAppLogger` (Talker-backed, respects LogLevel)
- Inject custom: `FbaeCore.init(logger: MyCrashlyticsLogger())`

### AsyncState<T> (sealed)
Four variants — Dart compiler enforces exhaustive switch:
- `AsyncInitial<T>` — not yet started
- `AsyncLoading<T>` — operation in progress
- `AsyncData<T>(value: T)` — success
- `AsyncError<T>(error: Object, stackTrace: StackTrace)` — failure

```dart
switch (state) {
  case AsyncInitial(): return placeholder;
  case AsyncLoading(): return CircularProgressIndicator();
  case AsyncData(:final value): return Text('$value');
  case AsyncError(:final error): return Text('Error: $error');
}
```

### BaseCubit<T>
- Extends `Cubit<AsyncState<T>>`
- `run(Future<T> Function() fn, {bool silent = false})` — handles AsyncLoading → AsyncData/AsyncError lifecycle, guards against closed cubit
- `silent: true` skips emitting AsyncLoading (background reload)

```dart
class UserCubit extends BaseCubit<User> {
  Future<void> load(String id) => run(() => api.getUser(id));
}
```

### BaseBloc<E, S>
- Extends `Bloc<E, S>`
- Only adds: `onError` logs via AppLogger then calls `super.onError`

### FbaeBlocObserver
- Extends `BlocObserver`, auto-wired by `FbaeCore.init()`
- Logs onCreate, onChange, onClose, onError via Talker
- `FbaeBlocObserver({required Talker talker, LogLevel level = LogLevel.debug})`

### FbaeColors (ThemeExtension)
13 color slots + brightness field:
`primary`, `onPrimary`, `secondary`, `onSecondary`, `background`, `onBackground`, `surface`, `onSurface`, `error`, `onError`, `textPrimary`, `textSecondary`, `textDisabled`, `brightness`

- Has sensible Material 3 defaults — override via constructor or `copyWith()`
- `lerp()` fully implemented for smooth animated transitions
- Access in widgets: `Theme.of(context).extension<FbaeColors>()!`

### FbaeTheme
- `FbaeTheme.build({required FbaeColors colors})` → `ThemeData` (Material 3, useMaterial3: true)

### ThemeCubit
- State: `ThemeMode`
- `toggle()` — light↔dark (system→light)
- `setMode(ThemeMode mode)`

### FbaeThemeProvider
- `FbaeThemeProvider({ThemeMode initialMode, required Widget child})` — BlocProvider wrapper
- Descendants access via `context.read<ThemeCubit>()` / `context.watch<ThemeCubit>()`

### FbaeShell
- `FbaeShell({required StatefulNavigationShell navigationShell, required ShellNavigationConfig config})`
- Renders `NavigationBar` for `TabConfig`, `Drawer` for `DrawerConfig`
- Stateless — consumer owns GoRouter

### FbaeRouter
- `FbaeRouter.buildRoutes({required ShellNavigationConfig config, required List<StatefulShellBranch> branches})` → `List<RouteBase>`
- Consumer creates and owns `GoRouter(routes: FbaeRouter.buildRoutes(...))`

### ShellNavigationConfig (sealed)
- `TabConfig(List<TabItemConfig> items)` — bottom navigation bar
- `DrawerConfig({Widget? header, required List<DrawerItemConfig> items})` — side drawer
- `TabItemConfig({required Widget icon, Widget? activeIcon, required String label, required String initialLocation})`
- `DrawerItemConfig({required Widget icon, required String label, required String location})`

### FbaePlugin (interface)
- `configure(FbaePluginContext context)` — called during bootstrap
- `FbaePluginContext` provides: `config`, `logger`, `registerCrashReporter()`, `addLogObserver()`

## Barrel Exports

Single import: `import 'package:fbae_core/fbae_core.dart';`

Test utilities (MockHttpClientAdapter): `import 'package:fbae_core/fbae_core_testing.dart';`

## Dependencies

- `flutter_bloc ^9.1.1` — BlocObserver, Cubit, Bloc
- `go_router ^17.2.0` — navigation
- `dio ^5.9.2` — HTTP client
- `get_it ^9.2.1` — service locator (internal, not exposed to consumers)
- `talker ^5.1.16` + `talker_flutter` + `talker_dio_logger` — logging

## Repository

https://github.com/imthanhhai217/fbae
