Dependency Inversion is the practice of programming to an interface rather than a concrete implementation. In a standard approach, your business logic depends directly on specific tools or SDKs, making the code rigid and difficult to test. With Dependency Inversion, you define a "contract" (an interface) that describes what a service should do, and your application only interacts with that contract.
More in Github.
In this example, we will walk through a common real-world scenario: Analytics. You’ll see how to decouple your Flutter app from specific vendors like Firebase or Facebook, allowing you to swap services, run mock versions in tests, or use a simple console logger during development—all without changing a single line of your feature logic.
The Problem
- Rigid Coupling: Your code is smeared with third-party SDKs. Swapping Firebase for Amplitude or Mixpanel requires editing every file that touches analytics.
- Untestable Logic: Unit tests fail because they require a real Firebase instance to be initialized. You end up skipping tests or wasting hours on complex mocks.
-
No Substitutability: You can’t easily provide "Console Logging" for development and "Real SDKs" for production without
if (kDebugMode)checks everywhere.
The Pattern — SOLID's "D"
High-level modules (your business logic) should not depend on low-level modules (the SDKs). Both should depend on abstractions (interfaces).
- Domain Layer: Owns the
interface(the "contract"). - Infrastructure Layer: Implements the contract (the "details").
- Application Layer: Wires it all together (via Riverpod in this example).
Implementation Guide
1. Define the Contract (Domain)
We use abstract interface class so consumers can only implements the contract — they can't extends it and inherit partial behavior. This keeps the contract pure: every method must be explicitly fulfilled.
Use Domain Primitives: Never leak third-party types (like a RevenueCat
Package) through your interface. Usedouble,String, or your own domain entities.
abstract interface class IAnalytics {
Future<void> logEvent({
required String name,
Map<String, Object>? properties,
});
Future<void> logPurchase({
required String productId,
required double amount,
required String currencyCode,
});
}
2. Create the Strategies (Infrastructure)
Create a Production version that talks to real SDKs and a Development version that simply logs to the console.
// Production: Wires up Firebase + Facebook
class ProductionAnalytics implements IAnalytics {
final FirebaseAnalytics _firebase;
ProductionAnalytics({FirebaseAnalytics? firebase})
: _firebase = firebase ?? FirebaseAnalytics.instance;
@override
Future<void> logEvent({required String name, Map<String, Object>? properties}) =>
_firebase.logEvent(name: name, parameters: properties);
// ... other methods
}
// Development: Zero SDK dependencies, prints to console
class DevelopmentAnalytics implements IAnalytics {
@override
Future<void> logEvent({required String name, Map<String, Object>? properties}) async =>
print('[Analytics] $name: $properties');
}
3. The Composition Root (Riverpod)
The provider acts as the single decision point for which implementation to use.
final analyticsProvider = Provider<IAnalytics>((ref) {
// kDebugMode is a compile-time constant.
// The unused branch is physically tree-shaken from your release binary!
if (kDebugMode) {
return DevelopmentAnalytics();
}
return ProductionAnalytics();
});
Why This Matters
-
Tree-Shaking: Because
kDebugModeis a constant,DevelopmentAnalyticsdoesn't even exist in your production APK/IPA. -
Instant Mocking: In your tests, simply override the provider with a
TestAnalyticsspy to verify logic without network calls. - Clean Boundaries: Your feature developers can track events without ever knowing (or caring) that Firebase is under the hood.
United States
NORTH AMERICA
Related News
How Braze’s CTO is rethinking engineering for the agentic area
10h ago
Amazon Employees Are 'Tokenmaxxing' Due To Pressure To Use AI Tools
21h ago

Implementing Multicloud Data Sharding with Hexagonal Storage Adapters
15h ago

DeepMind’s CEO Says AGI May Be ~4 Years Away. The Last Three Missing Pieces Are Not What Most People Think.
15h ago

CCSnapshot - A Claude Code Configs Transfer Tool
21h ago