205 lines
8.3 KiB
Markdown
205 lines
8.3 KiB
Markdown
# Kell Creations — Cross-Platform Shell Composition Strategy
|
|
|
|
## Overview
|
|
|
|
This document describes the shared composition pattern that all Kell Creations
|
|
app targets (`kell_web`, `kell_mobile`, future targets) must follow. The pattern
|
|
lives in the `core` package and provides a consistent way to:
|
|
|
|
1. Read runtime configuration from `--dart-define` values.
|
|
2. Construct environment-appropriate services (fake vs. real backends).
|
|
3. Expose services and configuration to the widget tree.
|
|
|
|
## Architecture
|
|
|
|
```
|
|
┌──────────────────────────────────────────────────────────────┐
|
|
│ core (shared package) │
|
|
│ │
|
|
│ KcAppConfig / KcAppEnvironment │
|
|
│ └─ Runtime config from --dart-define │
|
|
│ │
|
|
│ KcAppServices (abstract) │
|
|
│ └─ Base class for app service containers │
|
|
│ │
|
|
│ KcServiceFactory<T extends KcAppServices> │
|
|
│ └─ Generic factory: createFake() / createWordPress(cfg) │
|
|
│ │
|
|
│ KcBootstrap │
|
|
│ └─ Shared bootstrap: env switch + WP credential fallback │
|
|
│ │
|
|
│ KcAppScope<T extends KcAppServices> │
|
|
│ └─ InheritedWidget exposing T + KcAppConfig to tree │
|
|
└──────────────────────────────────────────────────────────────┘
|
|
▲ ▲
|
|
│ │
|
|
┌────────┴───────────┐ ┌────────────┴──────────────┐
|
|
│ kell_web (app) │ │ kell_mobile (app) │
|
|
│ │ │ │
|
|
│ AppServices │ │ MobileAppServices │
|
|
│ extends │ │ extends │
|
|
│ KcAppServices │ │ KcAppServices │
|
|
│ │ │ │
|
|
│ AppScope │ │ (uses KcAppScope │
|
|
│ extends │ │ <MobileAppServices>) │
|
|
│ KcAppScope │ │ │
|
|
│ <AppServices> │ │ │
|
|
│ │ │ │
|
|
│ Bootstrap │ │ (uses KcBootstrap.run │
|
|
│ delegates to │ │ with own factory) │
|
|
│ KcBootstrap.run │ │ │
|
|
└─────────────────────┘ └────────────────────────────┘
|
|
```
|
|
|
|
## Why abstract services?
|
|
|
|
Concrete service containers hold references to feature-package repository types
|
|
(e.g., `InventoryRepository`, `ProductPublishingRepository`). Moving those
|
|
references into `core` would create **circular dependencies**:
|
|
|
|
```
|
|
core → feature_wordpress → core ← NOT ALLOWED
|
|
```
|
|
|
|
By keeping `KcAppServices` abstract in `core`, the package owns the composition
|
|
**contract** without knowing concrete types. Each app provides its own concrete
|
|
subclass.
|
|
|
|
## Contract for new app targets
|
|
|
|
When creating a new app target (e.g., `kell_mobile`), follow this pattern:
|
|
|
|
### 1. Create a concrete services class
|
|
|
|
```dart
|
|
// apps/kell_mobile/lib/composition/mobile_app_services.dart
|
|
import 'package:core/core.dart';
|
|
import 'package:feature_inventory/feature_inventory.dart';
|
|
import 'package:feature_wordpress/feature_wordpress.dart';
|
|
// ... other feature imports
|
|
|
|
class MobileAppServices extends KcAppServices {
|
|
final InventoryRepository inventoryRepository;
|
|
final ProductPublishingRepository productPublishingRepository;
|
|
// ... other repositories as needed
|
|
|
|
const MobileAppServices({
|
|
required this.inventoryRepository,
|
|
required this.productPublishingRepository,
|
|
});
|
|
|
|
factory MobileAppServices.fake() {
|
|
return MobileAppServices(
|
|
inventoryRepository: FakeInventoryRepository(),
|
|
productPublishingRepository: FakeProductPublishingRepository(),
|
|
);
|
|
}
|
|
|
|
factory MobileAppServices.wordpress({
|
|
required String siteUrl,
|
|
required String consumerKey,
|
|
required String consumerSecret,
|
|
}) {
|
|
final apiClient = WooCommerceApiClient(
|
|
siteUrl: siteUrl,
|
|
consumerKey: consumerKey,
|
|
consumerSecret: consumerSecret,
|
|
);
|
|
return MobileAppServices(
|
|
inventoryRepository: FakeInventoryRepository(),
|
|
productPublishingRepository:
|
|
WordPressProductPublishingRepository(apiClient: apiClient),
|
|
);
|
|
}
|
|
|
|
static KcServiceFactory<MobileAppServices> get serviceFactory {
|
|
return KcServiceFactory<MobileAppServices>(
|
|
createFake: () => MobileAppServices.fake(),
|
|
createWordPress: (config) => MobileAppServices.wordpress(
|
|
siteUrl: config.wcSiteUrl,
|
|
consumerKey: config.wcConsumerKey,
|
|
consumerSecret: config.wcConsumerSecret,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2. Wire up main.dart
|
|
|
|
```dart
|
|
// apps/kell_mobile/lib/main.dart
|
|
import 'package:core/core.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'composition/mobile_app_services.dart';
|
|
|
|
void main() {
|
|
final config = KcAppConfig.fromEnvironment();
|
|
final (:services, config: effectiveConfig) = KcBootstrap.run(
|
|
config,
|
|
MobileAppServices.serviceFactory,
|
|
);
|
|
|
|
runApp(
|
|
KcAppScope<MobileAppServices>(
|
|
services: services,
|
|
config: effectiveConfig,
|
|
child: const KellMobileApp(),
|
|
),
|
|
);
|
|
}
|
|
```
|
|
|
|
### 3. Access services in widgets
|
|
|
|
```dart
|
|
// Anywhere in the widget tree:
|
|
final services = KcAppScope.of<MobileAppServices>(context);
|
|
final config = KcAppScope.configOf<MobileAppServices>(context);
|
|
```
|
|
|
|
## Backward compatibility (kell_web)
|
|
|
|
The `kell_web` app retains its original class names (`AppConfig`, `AppScope`,
|
|
`Bootstrap`, `AppServices`) as thin wrappers/typedefs around the shared `core`
|
|
types. Existing code continues to work without modification:
|
|
|
|
| Original (kell_web) | Shared (core) | Relationship |
|
|
| ------------------- | ------------------ | --------------- |
|
|
| `AppConfig` | `KcAppConfig` | typedef |
|
|
| `AppEnvironment` | `KcAppEnvironment` | typedef |
|
|
| `AppServices` | `KcAppServices` | extends |
|
|
| `AppScope` | `KcAppScope` | extends (typed) |
|
|
| `Bootstrap` | `KcBootstrap` | delegates |
|
|
|
|
New code in `kell_web` may use either the original names or the shared `Kc`-prefixed
|
|
names. The shared names are preferred for clarity and consistency with `kell_mobile`.
|
|
|
|
## Runtime configuration
|
|
|
|
All app targets use the same `--dart-define` keys:
|
|
|
|
| Key | Description | Default |
|
|
| ----------------------- | ------------------------------------ | ------- |
|
|
| `KC_ENV` | `fake` or `wordpress` | `fake` |
|
|
| `KC_WC_SITE_URL` | WordPress site URL | (empty) |
|
|
| `KC_WC_CONSUMER_KEY` | WooCommerce REST API consumer key | (empty) |
|
|
| `KC_WC_CONSUMER_SECRET` | WooCommerce REST API consumer secret | (empty) |
|
|
|
|
When `KC_ENV=wordpress` but credentials are missing, `KcBootstrap` automatically
|
|
falls back to fake mode with a debug-mode warning.
|
|
|
|
## What remains app-specific
|
|
|
|
The following concerns are **not** shared and remain in each app target:
|
|
|
|
- **App shell / navigation** — Web uses `NavigationRail`, mobile will use
|
|
`BottomNavigationBar` or similar.
|
|
- **Routing** — Route definitions and page builders are platform-specific.
|
|
- **Platform-specific presentation** — Layout, responsive breakpoints, etc.
|
|
- **Cross-feature navigation handoffs** — Wired in the app's routing layer.
|
|
|
|
The `design_system` package provides shared visual components (widgets, theme,
|
|
typography, breakpoints) that both web and mobile can use for consistent styling.
|