dynamic_backend_bridge 0.0.5
dynamic_backend_bridge: ^0.0.5 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-themed onboarding wizard UI, a unified Auth layer, and a generic map-based Database layer with query filtering support.
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. - 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, and stream records reactively using a generic, map-based repository interface (
DatabaseRepository). - Type-Safe Collections (
TypedCollection<T>): Easily wrap the database repository to serialize and deserialize your custom domain models. - Onboarding/Hosting Wizard: A premium, dark-themed UI configuration screen (
HostingWizard) allowing users or admins to configure and validate backend endpoints.
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.4
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:get_it/get_it.dart';
import 'package:dynamic_backend_bridge/dynamic_backend_bridge.dart';
final getIt = GetIt.instance;
// 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...',
);
}
// Single helper function to initialize backend
Future<void> initializeBackend(AppConfig config) async {
await DynamicBackendBridge.initialize(
config: config,
getIt: getIt,
defaultSupabaseUrl: AppEnvironment.defaultSupabaseUrl,
defaultSupabaseAnonKey: AppEnvironment.defaultSupabaseAnonKey,
);
}
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final configService = ConfigService();
final savedConfig = await configService.getSavedConfig();
if (savedConfig != null) {
await initializeBackend(savedConfig);
}
runApp(MyApp(
configService: configService,
initialConfig: savedConfig,
));
}
2. Integrate the Hosting Wizard Onboarding Screen #
If no configuration exists on startup, present the HostingWizard widget to onboarding the user:
HostingWizard(
configService: widget.configService,
onValidate: (AppConfig config) async {
// Validate configuration health checks (offline test / credentials sanity check)
try {
await initializeBackend(config);
final auth = getIt<AuthRepository>();
return await auth.validateConnection();
} catch (e) {
return e.toString();
}
},
onComplete: (AppConfig config) {
// Navigate or update state to load main application flow
},
)
3. Using the Unified Auth Layer #
Access authentication singletons dynamically from the locator:
final auth = getIt<AuthRepository>();
// Sign In
final user = await auth.signIn('user@example.com', 'password');
// Sign Out
await auth.signOut();
4. Using the Generic Database Layer with TypedCollection #
Define your custom domain models client-side and map them using TypedCollection<T>:
class Task {
final String id;
final String title;
final String userId;
Task({required this.id, required this.title, required this.userId});
Map<String, dynamic> toMap() => {
'title': title,
'userId': userId,
};
static Task fromMap(Map<String, dynamic> map, String id) => Task(
id: id,
title: map['title'] ?? '',
userId: map['userId'] ?? '',
);
}
// Instantiate collection wrapper
final taskCollection = TypedCollection<Task>(
repo: getIt<DatabaseRepository>(),
collectionName: 'tasks',
toMap: (task) => task.toMap(),
fromMap: (map, id) => Task.fromMap(map, id),
);
// Save items
await taskCollection.save(Task(id: '', title: 'Buy milk', userId: 'user123'), 'document-id');
// Watch changes reactively with query filters
final stream = taskCollection.watch(
filters: [QueryFilter.eq('userId', 'user123')],
);
Additional Information #
For issues, contributions, or configuration details, please refer to the package repository.