dynamic_backend_bridge 0.0.7
dynamic_backend_bridge: ^0.0.7 copied to clipboard
A dynamic backend switcher for Flutter providing unified, generic Auth and Database bridges supporting Supabase Cloud and Self-Hosted Supabase.
dynamic_backend_bridge #
A Flutter package that provides a dynamic, decoupled interface for switching between Supabase backends (Managed Cloud, Custom Supabase Cloud, and Self-Hosted Docker instances) at runtime. It includes a built-in dark/light themed onboarding wizard UI, a unified Auth layer, a generic map-based Database layer with batch and query filtering support, Riverpod state providers, local & push notifications, and customizable in-app banner alerts.
Features #
- Runtime Backend Switching: Swap between a Managed Supabase Cloud Backend, a Custom
supabase.comproject, or a self-hosted Docker VPS instance dynamically without rebuilds. - Riverpod State Management: Access authentication state, database repository, theme settings, and notification services via first-class Riverpod providers (
core_providers.dart). - Unified Authentication: Perform sign-in, sign-up, sign-out, session restoration, and connection health checks via a single abstract interface (
AuthRepository). - Generic Database Bridge: Read, write, batch upsert/delete, and stream records reactively using a generic, map-based repository interface (
DatabaseRepository). - Type-Safe Collections (
TypedCollection<T>): Wrap the database repository with models to serialize, deserialize, batch process, filter, order, and paginate data. - Push & Local Notifications: Built-in timezone-aware local notification scheduling (
NotificationService) and Firebase Cloud Messaging integration (RemoteNotificationService). - In-App Banner Notifications: Floating overlay banner alerts (
AppBannerService) with auto-dismiss timers, action callbacks, and seamless foreground FCM message display. - Theme & Appearance Service: Persistent theme mode management (
ThemeService,themeModeProvider) and configurable design themes (AppTheme). - Ready-to-use UI Components: Clean, responsive UI components including
HostingWizard,SignInPage,DynamicProfilePage, andAppBannerWidget.
Backend Modes #
dynamic_backend_bridge supports two runtime deployment modes:
BackendType.managed(Our Managed Cloud): Connects to your organization's default managed Supabase instance. The app developer suppliesdefaultSupabaseUrlanddefaultSupabaseAnonKeyprogrammatically (or viaAppEnvironment/--dart-define). End users do not need to configure anything.BackendType.customSupabase(Your Own Supabase Server): Allows end-users or administrators to enter their own Supabase Project URL & Anon Key viaHostingWizard. Works seamlessly for bothsupabase.comCloud projects and private self-hosted Docker VPS servers.
Getting Started #
Add the package to your pubspec.yaml dependencies:
dependencies:
dynamic_backend_bridge: ^0.0.7
Run flutter pub get to install the dependencies.
Usage #
1. Initialize the Bridge #
In your application's main() or bootstrapping sequence, check for any saved configuration and initialize the dynamic backend bridge:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:dynamic_backend_bridge/dynamic_backend_bridge.dart';
// Centralize credentials in a single environment class or --dart-define
class AppEnvironment {
static const String defaultSupabaseUrl = String.fromEnvironment(
'SUPABASE_URL',
defaultValue: 'https://xyzcompany.supabase.co',
);
static const String defaultSupabaseAnonKey = String.fromEnvironment(
'SUPABASE_ANON_KEY',
defaultValue: 'eyJhbGciOiJIUzI1NiIsInR...',
);
}
// Helper function to initialize backend
Future<void> initializeBackend(AppConfig config, ProviderContainer container) async {
await DynamicBackendBridge.initialize(
config: config,
container: container,
defaultSupabaseUrl: AppEnvironment.defaultSupabaseUrl,
defaultSupabaseAnonKey: AppEnvironment.defaultSupabaseAnonKey,
);
}
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final prefs = await SharedPreferences.getInstance();
final configService = ConfigService();
final savedConfig = await configService.getSavedConfig();
final container = ProviderContainer(
overrides: [
sharedPreferencesProvider.overrideWithValue(prefs),
],
);
if (savedConfig != null) {
await initializeBackend(savedConfig, container);
}
runApp(UncontrolledProviderScope(
container: container,
child: MyApp(
configService: configService,
initialConfig: savedConfig,
),
));
}
2. Riverpod State Providers #
Access core backend services anywhere in your widget tree using Riverpod:
// Access database repository
final db = ref.watch(databaseRepositoryProvider);
// Access auth repository
final auth = ref.watch(authRepositoryProvider);
// Listen to auth state stream
final userAsync = ref.watch(currentUserProvider);
// Manage theme mode (System / Light / Dark)
final themeMode = ref.watch(themeModeProvider);
ref.read(themeModeProvider.notifier).setThemeMode(ThemeMode.dark);
// Access notification services
final localNotif = ref.watch(notificationServiceProvider);
final remoteNotif = ref.watch(remoteNotificationServiceProvider);
3. Database Layer & Typed Collections #
Wrap domain models using TypedCollection<T> for type-safe database queries, batch operations, and realtime streaming:
class Task {
final String id;
final String title;
final String userId;
final bool isCompleted;
Task({
required this.id,
required this.title,
required this.userId,
this.isCompleted = false,
});
Map<String, dynamic> toMap() => {
'title': title,
'userId': userId,
'is_completed': isCompleted,
};
static Task fromMap(Map<String, dynamic> map, String id) => Task(
id: id,
title: map['title'] ?? '',
userId: map['userId'] ?? '',
isCompleted: map['is_completed'] ?? false,
);
}
// Instantiate collection wrapper
final taskCollection = TypedCollection<Task>(
repo: ref.read(databaseRepositoryProvider),
collectionName: 'tasks',
toMap: (task) => task.toMap(),
fromMap: (map, id) => Task.fromMap(map, id),
);
// Save single item
await taskCollection.save(Task(id: '', title: 'Buy milk', userId: 'user123'), 'task-id-1');
// Batch save multiple items
await taskCollection.saveBatch([
Task(id: '1', title: 'Task 1', userId: 'user123'),
Task(id: '2', title: 'Task 2', userId: 'user123'),
]);
// Query with filtering, ordering, and pagination
final openTasks = await taskCollection.fetch(
filters: [
QueryFilter.eq('userId', 'user123'),
QueryFilter.eq('is_completed', false),
],
orderBy: 'title',
ascending: true,
limit: 20,
);
// Watch realtime updates
final taskStream = taskCollection.watch(
filters: [QueryFilter.eq('userId', 'user123')],
);
// Batch delete
await taskCollection.deleteBatch(['1', '2']);
4. In-App Banner Notifications #
Display non-blocking in-app alert banners anywhere in your application:
// Success Banner
AppBannerService.showSuccess(context, 'Profile updated successfully!');
// Error Banner
AppBannerService.showError(context, 'Failed to save changes. Please try again.');
// Custom Informational Banner with Tap Handler
AppBannerService.showInfo(
context,
title: 'New Update Available',
body: 'Tap to view release details.',
duration: const Duration(seconds: 5),
onTap: () {
// Navigate to release page
},
);
// Programmatically dismiss current banner
AppBannerService.hideCurrentBanner();
5. Local & Push Notifications #
Schedule local notifications or configure Firebase Cloud Messaging:
final notifService = ref.read(notificationServiceProvider);
// Schedule a notification in 10 minutes
await notifService.scheduleNotification(
id: 101,
title: 'Task Reminder',
body: 'Time to complete your scheduled task!',
duration: const Duration(minutes: 10),
);
// Show immediate notification
await notifService.showNotification(
id: 102,
title: 'Welcome!',
body: 'Thank you for joining our platform.',
);
// Cancel notification
await notifService.cancelNotification(101);
6. Built-in UI Components #
Hosting Wizard (Onboarding & Endpoint Configuration)
HostingWizard(
configService: configService,
onValidate: (AppConfig config) async {
try {
final tempContainer = ProviderContainer();
await initializeBackend(config, tempContainer);
final auth = tempContainer.read(authRepositoryProvider);
return await auth.validateConnection();
} catch (e) {
return e.toString();
}
},
onComplete: (AppConfig config) async {
await DynamicBackendBridge.initialize(
config: config,
ref: ref,
defaultSupabaseUrl: AppEnvironment.defaultSupabaseUrl,
defaultSupabaseAnonKey: AppEnvironment.defaultSupabaseAnonKey,
);
},
)
Dynamic Profile & Appearance Settings Page
DynamicProfilePage(
configService: configService,
onBackendConfigPressed: () {
// Navigate to HostingWizard or config modal
},
)
Additional Information #
For issues, contributions, or configuration details, please refer to the GitHub repository.