Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions dart_define.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{
"WEBTRIT_APP_FIREBASE_ENABLED": true,
"__WEBTRIT_APP_FIREBASE_ENABLED_DESCRIPTION": "Whether Firebase (app, messaging, local push, Remote Config, Installations, Analytics) is initialised. Default true. Disable when the app is embedded in a host that owns the default Firebase app (e.g. the theme configurator's realtime preview) so the embedded app runs Firebase-free.",
"WEBTRIT_APP_DATABASE_LOG_STATEMENTS": false,
"_WEBTRIT_APP_CORE_URL": "",
"WEBTRIT_APP_DEMO_CORE_URL": "http://192.168.10.100:4000",
Expand Down
2 changes: 2 additions & 0 deletions docs/environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ maintainability and enabling tools to parse configuration reliably.
- `WEBTRIT_APP_DEMO_CORE_URL` – Demo core URL (_default: **http://localhost:4000**_).
- `WEBTRIT_APP_DATABASE_LOG_STATEMENTS` – Enables logging of database queries (
_default: **false**_).
- `WEBTRIT_APP_FIREBASE_ENABLED` – Whether Firebase is initialised (_default: **true**_). Set to
`false` to run embedded in a host that owns the default Firebase app (configurator realtime preview).
- `_WEBTRIT_APP_CORE_URL` – Custom core URL (optional override).
- `_WEBTRIT_APP_CORE_VERSION_CONSTRAINT` – Core compatibility range.
- `_WEBTRIT_APP_ABOUT_URL` – URL for "About" screen content.
Expand Down
2 changes: 1 addition & 1 deletion lib/app/view/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ class _AppState extends State<App> {
deepLinkBuilder: isDeepLinkEnabled ? appRouter.deepLinkBuilder : null,
navigatorObservers: () => [
AppRouterObserver(),
context.read<AppAnalyticsRepository>().createObserver(),
if (EnvironmentConfig.FIREBASE_ENABLED) context.read<AppAnalyticsRepository>().createObserver(),
AutoRouteObserver(),
],
reevaluateListenable: ReevaluateListenable.stream(
Expand Down
36 changes: 29 additions & 7 deletions lib/bootstrap.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,27 @@ IsolateContext? _isolateContext;
Future<InstanceRegistry> bootstrap() async {
final registry = InstanceRegistry();

// External SDKs (Side effects only, don't need registration)
await _initFirebaseApp();
await _initFirebaseMessaging();
await _initLocalPushs();
// External SDKs (Side effects only, don't need registration). Gated by the
// WEBTRIT_APP_FIREBASE_ENABLED config so a host that owns the default Firebase
// app (e.g. the theme configurator's realtime preview) can run this app
// Firebase-free by configuring that flag off.
final firebaseEnabled = EnvironmentConfig.FIREBASE_ENABLED;
if (firebaseEnabled) {
await _initFirebaseApp();
await _initFirebaseMessaging();
await _initLocalPushs();
}

// Initialize Components

// App Info & Device Data

final packageInfo = await PackageInfoFactory.init();
final appInfo = await AppInfo.init(FirebaseAppIdProvider());
// FirebaseAppIdProvider uses Firebase Installations; without Firebase use the
// local shared-preferences id provider instead.
final appInfo = await AppInfo.init(
firebaseEnabled ? FirebaseAppIdProvider() : const SharedPreferencesAppIdProvider(),
);
final deviceInfo = await DeviceInfoFactory.init();

// Storages
Expand Down Expand Up @@ -97,9 +107,21 @@ Future<InstanceRegistry> bootstrap() async {
apiClientFactory: apiClientFactory,
);

// Remote configuration
// Remote configuration. Firebase Remote Config needs the Firebase app, so when
// Firebase is disabled fall back to the local shared-preferences cache
// (DefaultRemoteCacheConfigService also implements RemoteConfigService).
final remoteCacheConfigService = await DefaultRemoteCacheConfigService.init();
final cachedRemoteConfigService = await CachedRemoteConfigService.init(remoteCacheConfigService);
RemoteConfigService cachedRemoteConfigService;
if (firebaseEnabled) {
try {
cachedRemoteConfigService = await CachedRemoteConfigService.init(remoteCacheConfigService);
} catch (e, s) {
Logger('bootstrap').warning('Firebase Remote Config init failed; using local cache fallback', e, s);
cachedRemoteConfigService = remoteCacheConfigService;
}
} else {
cachedRemoteConfigService = remoteCacheConfigService;
}

final featureAccessStreamFactory = FeatureAccessStreamFactory(
appThemes: appThemes,
Expand Down
8 changes: 8 additions & 0 deletions lib/environment_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ class EnvironmentConfig {
return seconds > 0 ? seconds : compileTime;
}

// Whether Firebase (app, messaging, local push, Remote Config, Installations,
// Analytics) is initialised. Disable it when this app is embedded in a host
// that owns the default Firebase app (the theme configurator's realtime
// preview), so the embedded app runs Firebase-free.
static const FIREBASE_ENABLED__NAME = 'WEBTRIT_APP_FIREBASE_ENABLED';
static bool get FIREBASE_ENABLED =>
_env.boolean(FIREBASE_ENABLED__NAME, const bool.fromEnvironment(FIREBASE_ENABLED__NAME, defaultValue: true));

static const DATABASE_LOG_STATEMENTS__NAME = 'WEBTRIT_APP_DATABASE_LOG_STATEMENTS';
static bool get DATABASE_LOG_STATEMENTS => _env.boolean(
DATABASE_LOG_STATEMENTS__NAME,
Expand Down
8 changes: 7 additions & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,13 @@ class RootApp extends StatelessWidget {
create: (_) => instanceRegistry.get(),
dispose: disposeIfDisposable,
),
RepositoryProvider.value(value: AppAnalyticsRepository(instance: FirebaseAnalytics.instance)),
// Lazy so the FirebaseAnalytics.instance handle is created only when the
// analytics observer is actually attached. With WEBTRIT_APP_FIREBASE_ENABLED
// false the observer is skipped (see App), so this provider is never read and
// the embedded app stays Firebase-free.
RepositoryProvider<AppAnalyticsRepository>(
create: (_) => AppAnalyticsRepository(instance: FirebaseAnalytics.instance),
),
RepositoryProvider<RegisterStatusRepository>.value(value: registerStatusRepository),
RepositoryProvider<PresenceSettingsRepository>.value(value: presenceSettingsRepository),
RepositoryProvider<QueuedTerminationRequestsRepository>.value(value: queuedTerminationRequestsRepository),
Expand Down
10 changes: 10 additions & 0 deletions test/environment_config_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,15 @@ void main() {
EnvironmentConfig.applyOverrides({name: '30'});
expect(EnvironmentConfig.USER_REPOSITORY_POLLING_INTERVAL_SECONDS, 30);
});

test('FIREBASE_ENABLED defaults to true and reflects a false override', () {
expect(EnvironmentConfig.FIREBASE_ENABLED, isTrue);

EnvironmentConfig.applyOverrides({EnvironmentConfig.FIREBASE_ENABLED__NAME: 'false'});
expect(EnvironmentConfig.FIREBASE_ENABLED, isFalse);

EnvironmentConfig.clearOverrides();
expect(EnvironmentConfig.FIREBASE_ENABLED, isTrue);
});
});
}
Loading