Flutter: One Codebase, Five Platforms
Flutter in 2026 is the most productive way to ship mobile apps. One Dart codebase → iOS, Android, web, macOS, Windows, and Linux. Google's material design components, custom rendering engine, and near-native performance have made Flutter the #1 cross-platform framework for new mobile projects.
For SaaS builders, Flutter's multi-platform output is particularly compelling — ship a native mobile app and a web app from the same code.
Quick Comparison
| Starter | Price | Auth | Billing | Platforms | State Mgmt | Best For |
|---|---|---|---|---|---|---|
| ShipFlutter | $149 | Firebase + Supabase | RevenueCat + Stripe | iOS/Android/Web | Riverpod | Complete mobile SaaS |
| Very Good CLI | Free | ❌ | ❌ | All 6 | Bloc | Production Flutter apps |
| Flutter Firebase | Free | Firebase Auth | ❌ | iOS/Android/Web | Provider | Firebase-backed apps |
| Flutter Supabase | Free | Supabase Auth | ❌ | iOS/Android/Web | Riverpod | Supabase-backed apps |
| mason bricks | Free | Custom | Custom | All 6 | Any | Template generation |
The Starters
ShipFlutter — Best Complete Mobile SaaS
Price: $149 (one-time) | Creator: IndependentDevs
The most complete Flutter SaaS boilerplate. Firebase or Supabase auth (your choice), RevenueCat for in-app purchases, Stripe for web billing, push notifications, analytics, onboarding flows, settings screen, dark mode, localization, and CI/CD config for App Store and Play Store.
Key features:
- Firebase Auth + Firestore or Supabase Auth + PostgreSQL
- RevenueCat integration (in-app purchases, subscriptions)
- Stripe webhook handling for web payments
- Push notifications (FCM)
- Remote config / feature flags
- Analytics (Firebase Analytics / Mixpanel)
- App Store Connect + Play Console release automation
Choose if: You're building a mobile-first SaaS and want everything set up.
Very Good CLI — Best Production Architecture
Price: Free | Creator: Very Good Ventures
Created by Flutter's premier agency (VGV), this CLI generates Flutter apps with their opinionated architecture: Bloc state management, 100% code coverage requirements, internationalization (ARB files), flavors (dev/staging/prod), and GitHub Actions CI.
# Install
dart pub global activate very_good_cli
# Create app
very_good create flutter_app my_app
# Create feature
very_good create flutter_feature auth
Choose if: Code quality, testing, and team scalability matter. VGV's architecture scales to large teams.
Flutter Firebase Starter — Best Free Firebase
Price: Free | Creator: Community
Flutter app connected to Firebase: Firebase Auth (email, Google, Apple), Firestore database, Firebase Storage, Firebase Analytics, and Crashlytics. The fastest way to get a Flutter app to production with Firebase.
Choose if: You're committed to Firebase and want a free, battle-tested starting point.
Flutter Supabase Starter — Best Free Supabase
Price: Free | Creator: Community
Flutter app with Supabase: Supabase Auth, PostgreSQL via supabase_flutter, real-time subscriptions, and Storage. Modern alternative to Firebase with SQL querying and self-hosting options.
Choose if: You prefer PostgreSQL over Firestore or want to avoid Firebase vendor lock-in.
Flutter's Architecture Considerations
State Management
Flutter's biggest architectural decision is state management:
| Package | Approach | Complexity | Best For |
|---|---|---|---|
| Riverpod | Providers | Medium | Solo dev, medium teams |
| Bloc | Stream-based | High | Large teams, testability |
| Provider | InheritedWidget | Low | Small apps |
| GetX | All-in-one | Low | Rapid prototyping |
ShipFlutter uses Riverpod. Very Good CLI uses Bloc. Both are valid — pick based on team preference.
Mobile Billing
Flutter has two billing paths:
-
RevenueCat — Cross-platform in-app purchase management. Handles iOS StoreKit, Android Billing Library, and web Stripe in one SDK. The standard choice for monetized Flutter apps.
-
in_app_purchase — Flutter's official IAP plugin. More control, more code. Good for teams that want to manage subscriptions directly.
Flutter Project Structure
Flutter SaaS apps follow a feature-first directory structure that scales with team size:
lib/
├── core/
│ ├── theme/ # Colors, text styles, theme data
│ ├── router/ # GoRouter configuration
│ ├── di/ # Dependency injection (get_it or Riverpod)
│ └── network/ # HTTP client, interceptors
├── features/
│ ├── auth/
│ │ ├── data/ # Repository implementation
│ │ ├── domain/ # Entities, repository interface
│ │ └── presentation/ # Screens, widgets, state
│ ├── billing/
│ └── dashboard/
└── shared/
├── widgets/ # Reusable UI components
└── utils/ # Helpers, extensions
Very Good CLI generates this structure automatically. ShipFlutter ships a similar layout. For Expo Router Starter users coming from the web, this feature-first approach mirrors modern Next.js App Router organization.
Navigation is typically handled by go_router — the officially recommended Flutter navigation package that supports deep links, URL-based routing (important for Flutter Web), and typed routes:
// core/router/app_router.dart
final router = GoRouter(
redirect: (context, state) {
final isLoggedIn = ref.read(authProvider).isAuthenticated;
if (!isLoggedIn && !state.uri.toString().startsWith('/auth')) {
return '/auth/sign-in';
}
return null;
},
routes: [
GoRoute(path: '/dashboard', builder: (ctx, state) => DashboardPage()),
GoRoute(path: '/auth/sign-in', builder: (ctx, state) => SignInPage()),
],
);
Flutter Performance: Impeller
Flutter's custom rendering engine, Impeller (now the default on iOS since Flutter 3.10 and Android since Flutter 3.16), eliminates shader compilation jank — a long-standing issue that caused visible stutters on first render. Impeller pre-compiles shaders at build time, delivering consistent 60/120fps animations on modern devices.
For SaaS apps with complex dashboards, charts, or animated UIs, Impeller's consistent frame rate is a meaningful UX improvement over older Flutter versions and over React Native's bridge-based rendering.
The practical impact: Flutter apps built on these boilerplates look and feel native in 2026 in a way that earlier Flutter generations didn't. The "this looks like a cross-platform app" criticism no longer holds for well-built Flutter products.
Authentication in Flutter
Flutter auth follows different patterns depending on your backend. With Supabase:
// Using supabase_flutter
import 'package:supabase_flutter/supabase_flutter.dart';
final supabase = Supabase.instance.client;
// Email + password sign in
Future<void> signIn(String email, String password) async {
final response = await supabase.auth.signInWithPassword(
email: email,
password: password,
);
if (response.session != null) {
// User is signed in
Navigator.pushReplacementNamed(context, '/dashboard');
}
}
// Listen to auth state changes
StreamBuilder<AuthState>(
stream: supabase.auth.onAuthStateChange,
builder: (context, snapshot) {
final session = snapshot.data?.session;
return session != null ? DashboardPage() : SignInPage();
},
)
With Firebase Auth:
import 'package:firebase_auth/firebase_auth.dart';
final auth = FirebaseAuth.instance;
// Sign in with Google
Future<UserCredential> signInWithGoogle() async {
final googleUser = await GoogleSignIn().signIn();
final googleAuth = await googleUser!.authentication;
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
return auth.signInWithCredential(credential);
}
// Auth state stream
StreamBuilder<User?>(
stream: auth.authStateChanges(),
builder: (context, snapshot) {
return snapshot.hasData ? DashboardPage() : SignInPage();
},
)
Both patterns use streams to react to authentication state changes — a Flutter idiom that keeps the UI in sync with auth status without manual state management.
RevenueCat Integration for Flutter
RevenueCat is the standard in-app purchase solution for Flutter SaaS. It handles both iOS StoreKit and Google Play Billing behind a unified Dart API:
// pubspec.yaml
dependencies:
purchases_flutter: ^6.0.0
// main.dart
import 'package:purchases_flutter/purchases_flutter.dart';
Future<void> initRevenueCat() async {
await Purchases.setLogLevel(LogLevel.debug);
PurchasesConfiguration config;
if (Platform.isAndroid) {
config = PurchasesConfiguration('YOUR_REVENUECAT_ANDROID_KEY');
} else {
config = PurchasesConfiguration('YOUR_REVENUECAT_IOS_KEY');
}
await Purchases.configure(config);
}
// Purchase a subscription
Future<void> purchasePro() async {
try {
final offerings = await Purchases.getOfferings();
final monthly = offerings.current?.monthly;
if (monthly != null) {
final customerInfo = await Purchases.purchasePackage(monthly);
final isPro = customerInfo.entitlements.active['pro'] != null;
// Update your app state with isPro
}
} on PurchasesErrorCode catch (e) {
if (e != PurchasesErrorCode.purchaseCancelledError) {
// Handle error (show dialog, etc.)
}
}
}
RevenueCat's dashboard provides subscriber analytics, cohort analysis, and webhook integrations — features you'd otherwise build yourself.
Testing Flutter Apps
Very Good CLI enforces 100% code coverage, which is ambitious but reflects Flutter's strong testing ecosystem:
// test/auth_bloc_test.dart
import 'package:bloc_test/bloc_test.dart';
import 'package:test/test.dart';
void main() {
group('AuthBloc', () {
late AuthBloc authBloc;
late MockAuthRepository authRepository;
setUp(() {
authRepository = MockAuthRepository();
authBloc = AuthBloc(authRepository: authRepository);
});
blocTest<AuthBloc, AuthState>(
'emits [authenticated] when sign in succeeds',
build: () {
when(() => authRepository.signIn(any(), any()))
.thenAnswer((_) async => mockUser);
return authBloc;
},
act: (bloc) => bloc.add(AuthSignInRequested('email', 'password')),
expect: () => [AuthAuthenticated(mockUser)],
);
});
}
Flutter's widget testing is similarly strong:
// test/sign_in_widget_test.dart
testWidgets('shows error on empty email submission', (tester) async {
await tester.pumpWidget(MaterialApp(home: SignInPage()));
await tester.tap(find.text('Sign In'));
await tester.pump();
expect(find.text('Email is required'), findsOneWidget);
});
For integration testing on real devices, Flutter's integration_test package runs on physical iOS/Android devices via flutter drive.
CI/CD for Flutter: App Store and Play Store
Deploying Flutter apps to both stores requires Fastlane or GitHub Actions. Very Good CLI includes a GitHub Actions workflow:
# .github/workflows/release.yml
jobs:
build-ios:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
- run: flutter build ipa --release
- run: fastlane ios release
build-android:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
- run: flutter build appbundle --release
- run: fastlane android release
This builds signed release builds and submits to TestFlight and the Play Console automatically on every tag. Setting up the signing certificates and store credentials is a one-time 2-3 hour investment per platform.
When to Choose Flutter
Flutter is the right choice when:
- You need iOS + Android from one codebase with native-like performance
- Your product benefits from a consistent custom UI (Flutter renders identically on all platforms)
- You want to add desktop (macOS, Windows, Linux) or web support from the same codebase
- Your team is willing to learn Dart (most developers are productive in 1-2 weeks)
Consider React Native when:
- Your team is deeply invested in React/JavaScript and npm ecosystem
- You need to reuse existing React components on web and mobile
- Expo's OTA updates (hotfixes without App Store review) are important to your workflow
- Your app needs to use a large number of JavaScript-only third-party libraries
The Flutter vs React Native decision in 2026 is less about performance (both are fast) and more about team background and ecosystem fit. Flutter's Dart learning curve is real but smaller than many developers expect — most JavaScript developers are comfortable in Dart within 1-2 weeks. The payoff is a single codebase that targets more platforms with more consistent behavior than any JavaScript-based alternative. For consumer SaaS with mobile-first ambitions, Flutter's ability to match native performance while sharing code across iOS, Android, and web is increasingly compelling compared to maintaining separate codebases for each platform.
State Management in Flutter SaaS: Riverpod vs Bloc vs Provider
State management is the most debated architectural decision in Flutter development, and the choice you make — or that your boilerplate makes for you — affects how you structure every feature of your application. Understanding the trade-offs before choosing a boilerplate based on its state management approach saves a refactor later.
Riverpod is the dominant choice in new Flutter SaaS boilerplates in 2026. It is the spiritual successor to Provider, created by the same author, with stronger type safety and a cleaner separation between UI and business logic. Riverpod's code generation mode (using @riverpod annotations) generates providers automatically and makes the code highly readable — a service class annotated with @riverpod becomes available throughout the app without manual wiring. The main learning curve is understanding Provider types: Provider for static values, FutureProvider for async data, StreamProvider for real-time streams, and StateNotifierProvider for mutable state.
Bloc (Business Logic Component) is the architectural pattern recommended by the Flutter team for complex applications. It enforces a strict unidirectional data flow: UI dispatches events, Blocs process events into states, UI rebuilds from states. The formalism is valuable for large teams because it makes the application's behavior predictable and testable — every state transition is explicit and unit-testable in isolation. The trade-off is boilerplate: a simple counter with Bloc requires a CounterEvent, a CounterState, a CounterBloc, and the UI wiring. For solo developers or small teams building early-stage products, Bloc's ceremony slows down feature development.
GetX is the speed-of-development option. Minimal boilerplate, simple reactive state, and dependency injection in one package. It is popular for prototypes and small applications. The architectural downsides — implicit state mutations, global state that is hard to trace — make it less suitable for applications you intend to maintain and test seriously.
For SaaS applications with a team, Riverpod is the pragmatic choice in 2026. It provides Bloc-level testability without Bloc's ceremony, and its code generation integration with the broader Flutter ecosystem (Freezed for immutable data classes, json_serializable for API types) is mature and well-documented. The Very Good CLI boilerplate is built around Bloc, reflecting its origin as an enterprise Flutter consultancy's tooling. If your team has Flutter experience and values Bloc's strict separation, Very Good CLI is the right foundation. If your team is newer to Flutter and values moving quickly while maintaining testability, a Riverpod-based starter is the better starting point.
Common Pitfalls When Starting With a Flutter Boilerplate
Flutter boilerplates remove a significant amount of setup work, but several gotchas consistently trip up developers who are new to the ecosystem — knowing them in advance saves days of debugging.
CocoaPods version mismatches. iOS builds fail when the CocoaPods version installed on your machine differs from what the boilerplate's Podfile.lock expects. Run sudo gem install cocoapods to update, then pod install --repo-update inside the ios/ directory. This is responsible for a disproportionate number of "works on Android, fails on iOS" reports in Flutter project issues.
Firebase configuration files. Both google-services.json (Android) and GoogleService-Info.plist (iOS) must be placed in exact locations. google-services.json goes in android/app/; GoogleService-Info.plist goes in ios/Runner/ and must be added through Xcode, not just copied via the file system. Missing the Xcode step causes silent auth failures at runtime.
Flavor configuration for multiple environments. Flutter boilerplates that support dev, staging, and production environments use flavors — a Flutter-native concept with no direct equivalent in React Native's environment variable approach. Each flavor has its own Firebase project, Stripe key, and backend URL. Setting up flavors correctly takes half a day but is essential for a safe release process.
State management version conflicts. The Flutter ecosystem moves fast. A boilerplate that uses Riverpod 2.x and a library that depends on Riverpod 1.x will have version resolution conflicts in pubspec.yaml. Check the pubspec for any dependency_overrides — they indicate places where the boilerplate is working around a conflict rather than resolving it cleanly.
For teams comparing the Flutter and React Native ecosystems more broadly, the best React Native boilerplates guide covers the equivalent starting points on the JavaScript side.
What to Evaluate Before Committing to a Flutter Boilerplate
Not all Flutter boilerplates are maintained with equal rigor. A checklist worth running before committing to a boilerplate for a production app:
Last commit date. Flutter SDK releases every 3 months on stable channel. A boilerplate with no commits in 6 months has likely accumulated breaking changes. Check whether the Flutter version in .fvmrc or pubspec.yaml matches the current stable channel.
Null safety. Any boilerplate that is not fully null-safe (introduced in Dart 2.12) is carrying technical debt from the pre-2021 ecosystem. All current Flutter packages require null safety; a boilerplate mixing null-safe and legacy packages will have confusing compilation errors.
Platform coverage. Check whether the boilerplate has been tested on both iOS and Android simulators, not just one. Some boilerplates develop primarily against Android and have subtle layout issues on iOS (safe area handling, font rendering, keyboard avoidance). Ask in the boilerplate's Discord or GitHub Discussions before purchasing.
License and commercial use. MIT-licensed boilerplates can be used in commercial products without restriction. Some Flutter starters use custom licenses that prohibit commercial use or require attribution — read the license file, not just the README.
The overall Flutter boilerplate market is smaller than the Next.js equivalents, which means finding a boilerplate that exactly matches your stack (specific auth provider, specific backend) is harder. For teams that need flexibility on backend, Very Good CLI's architecture-first approach — where the backend integration is deliberately left as an exercise — gives you a solid foundation regardless of which SaaS boilerplate pattern you follow on the backend side.
Compare all Flutter starters in the StarterPick directory.
See our best React Native boilerplates guide for the JavaScript-first alternative.
Browse our best SaaS boilerplates guide for the complete cross-platform starter comparison.