FoxTalk
Docs/Extension modules

Extension modules

FoxTalk implements every extension capability as a module — the base app compiles without depending on any module code. Modules register routes / message types / component builders into a FeatureRegistry; the base app reaches them through featureById. Removing a module is delete lib/src/<module>/ + comment one line in app_modules.dart — the main flow keeps working.

lib/src/modules/feature_module.dart defines the FeatureModule interface. Each module implements a same-named class and registers its capabilities in the register method.

abstract interface class FeatureModule {
  String get id;
  void register(FeatureRegistry registry);
}

FeatureRegistry exposes many register methods (registerRoute / registerComposerPanelItem / registerMessageContentHandler / registerLifecycle / registerCmdHandler etc.), each taking id + moduleId + value. The base app fetches the value via featureById(id).

Endpoint pattern: reflective lookup

The base app doesn't import module code directly. Instead it defines an interface (e.g. ThirdPartyLoginContract) and declares ID constants centrally in ModuleFeatureIds. The module implements the interface and registers it via registerFeature; the base app fetches the value via featureById and casts:

// Module side
registry.registerFeature(
  id: ModuleFeatureIds.thirdPartyLoginService,
  moduleId: id,
  value: ThirdPartyLoginServiceImpl(),
);

// Base app
final feature = registry.featureById(
  ModuleFeatureIds.thirdPartyLoginService,
);
final service = feature?.value as ThirdPartyLoginContract?;
// null = module not registered; UI hides the entry point

Benefit: removing a module never breaks the base app. A null featureById return triggers UI fallback (button hidden, entry point disappears) — the same one-line-removes-a-module feel as the iOS upstream Podfile.

Five steps to add a module

  • Add ID constants in ExtensionModuleIds + ModuleFeatureIds (if exposing features to the base) in lib/src/modules/module_ids.dart.
  • Create lib/src/<module>/<module>_module.dart implementing FeatureModule. Call registry.includeModule + various registerXxx in register.
  • Add other module source files under lib/src/<module>/ — internal layout is free.
  • Import the module file at the top of lib/src/modules/app_modules.dart and append one const XxxModule().register(registry) call in registerModules.
  • Run flutter analyze + install on a real device — verify the UI entry shows and other modules still work.
last updated · 2026-06