package_context 1.0.0
package_context: ^1.0.0 copied to clipboard
Initialize a package's config and dependencies once, and read them from a typed package context.
example/package_context_example.dart
import 'package:package_context/package_context.dart' as package_context;
// #docregion config
/// Values the host passes into the feature package.
final class Config extends package_context.PackageConfig {
/// Catalog API root.
final Uri baseUrl;
/// Whether the catalog feature is on.
final bool isEnabled;
/// Creates a catalog config.
const Config({required this.baseUrl, required this.isEnabled});
@override
List<Object?> get props => [baseUrl, isEnabled];
}
// #enddocregion
/// Host-owned HTTP port.
abstract interface class ApiClient {
/// Fetches bytes from [url].
Future<List<int>> get(Uri url);
}
/// Host-owned session.
abstract interface class Session {
/// Current user id, if any.
String? get userId;
}
// #docregion dependencies
/// Host objects the package must not create.
final class Dependencies extends package_context.PackageDependencies {
/// HTTP client from the host.
final ApiClient apiClient;
/// Session from the host.
final Session session;
/// Creates catalog dependencies.
const Dependencies({required this.apiClient, required this.session});
@override
List<Object?> get props => [apiClient, session];
}
// #enddocregion
// #docregion context
/// Process-wide catalog context. Do not export this from the package barrel.
final packageContext = package_context.PackageContext<Config, Dependencies>();
/// Typed config getter for package code.
Config get config => packageContext.config;
/// Typed dependencies getter for package code.
Dependencies get dependencies => packageContext.dependencies;
// #enddocregion
/// Whether the package DI still holds the catalog facade.
var _isRegistered = false;
// #docregion init_package
/// Initializes the catalog package once per process graph.
Future<void> initPackage({required Config config, required Dependencies dependencies}) async {
if (packageContext.isInitialized && _isRegistered) {
return;
}
if (packageContext.isInitialized) {
packageContext.refresh(config: config, dependencies: dependencies);
} else {
packageContext
..config = config
..dependencies = dependencies;
}
_isRegistered = true;
}
// #enddocregion
/// Reads the holder. Takes no host objects in the constructor.
class CatalogRepository {
/// Loads catalog items for the current session.
Future<List<int>> fetchItems() {
return dependencies.apiClient.get(config.baseUrl.resolve('/items'));
}
/// Creates a catalog repository.
const CatalogRepository();
}
/// In-memory host client. Uses the session as the "token".
class MemoryApiClient implements ApiClient {
/// Session whose user id stands in for an auth token.
final Session session;
@override
Future<List<int>> get(Uri url) async {
return session.userId == 'user-2' ? [2, 2] : [1, 1];
}
/// Creates a client bound to [session].
MemoryApiClient({required this.session});
}
/// Host session.
class AppSession implements Session {
@override
final String userId;
/// Creates a session for [userId].
const AppSession({required this.userId});
}
// #docregion host
void main() async {
const firstSession = AppSession(userId: 'user-1');
await initPackage(
config: Config(baseUrl: Uri.parse('https://api.example.com'), isEnabled: true),
dependencies: Dependencies(
apiClient: MemoryApiClient(session: firstSession),
session: firstSession,
),
);
print('first launch: ${await const CatalogRepository().fetchItems()}');
_isRegistered = false;
const nextSession = AppSession(userId: 'user-2');
await initPackage(
config: Config(baseUrl: Uri.parse('https://api.example.com'), isEnabled: true),
dependencies: Dependencies(
apiClient: MemoryApiClient(session: nextSession),
session: nextSession,
),
);
print('after refresh: ${await const CatalogRepository().fetchItems()}');
}
// #enddocregion