Why Flutter for Production Apps
Flutter renders its own widgets using the Skia (now Impeller) graphics engine rather than wrapping native platform components. This means pixel-perfect consistency across iOS and Android without the gaps that React Native's bridge architecture can introduce. The trade-off is a larger app binary and an opinionated UI toolkit that requires learning Flutter's widget system rather than adapting existing web knowledge.
Flutter is particularly strong for: teams that need true cross-platform consistency, apps with custom UI that doesn't map well to platform-standard components, and projects that also target web or desktop from the same codebase.
State Management with Riverpod
Riverpod is the most maintainable state management solution for Flutter production apps. It is compile-time safe (no runtime exceptions from missing providers), supports async state natively, and makes dependencies between providers explicit and testable.
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'user_provider.g.dart';
// Async provider that fetches user data
@riverpod
Future currentUser(CurrentUserRef ref) async {
final authToken = ref.watch(authTokenProvider);
if (authToken == null) throw Exception('Not authenticated');
return ref.watch(apiClientProvider).getUser(authToken);
}
// Notifier for complex state with mutations
@riverpod
class CartNotifier extends _$CartNotifier {
@override
List build() => [];
void addItem(Product product) {
state = [...state, CartItem(product: product, quantity: 1)];
}
void removeItem(String productId) {
state = state.where((i) => i.product.id != productId).toList();
}
}
// In your widget
class CartPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final items = ref.watch(cartNotifierProvider);
final userAsync = ref.watch(currentUserProvider);
return userAsync.when(
loading: () => const CircularProgressIndicator(),
error: (e, _) => ErrorWidget(error: e),
data: (user) => CartView(user: user, items: items),
);
}
}
Platform Channels for Native Features
Flutter's Dart code runs on the Dart VM and communicates with native iOS/Android code through platform channels. Use this for features with no Flutter plugin: biometric auth specifics, background location, NFC, Bluetooth, or proprietary SDKs.
// Dart side
const _channel = MethodChannel('com.company.app/native');
Future getNativeDeviceId() async {
try {
return await _channel.invokeMethod('getDeviceId') ?? '';
} on PlatformException catch (e) {
throw Exception('Failed to get device ID: ${e.message}');
}
}
// iOS side (Swift)
// AppDelegate.swift
FlutterMethodChannel(name: "com.company.app/native",
binaryMessenger: controller.binaryMessenger)
.setMethodCallHandler { call, result in
if call.method == "getDeviceId" {
result(UIDevice.current.identifierForVendor?.uuidString)
}
}
Performance Profiling
Flutter's DevTools suite is the most important tool for identifying performance regressions. Run apps in profile mode (not debug) for accurate performance data.
# Run in profile mode
flutter run --profile
# Key metrics to watch in DevTools:
# - Frame build time: target < 16ms (60fps) or < 8ms (120fps)
# - Jank: frames that miss the deadline
# - Memory: watch for leaks with the Memory tab
Common Flutter performance issues:
- Rebuilding too many widgets: Use
constconstructors on widgets that don't change.constwidgets are not rebuilt when their parent rebuilds. - Heavy computation on the UI thread: Move expensive work to a separate
Isolate— Flutter's equivalent of a background thread. - Unoptimised images: Always use compressed images and cache network images with
cached_network_image. - ListView without item keys: Add
key: ValueKey(item.id)to list items for efficient diffing.
CI/CD with Fastlane and GitHub Actions
# .github/workflows/flutter.yml
jobs:
build-android:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.24.0'
- run: flutter pub get
- run: flutter test
- run: flutter build appbundle --release
- name: Sign and upload to Play Store
run: bundle exec fastlane android deploy
env:
SUPPLY_JSON_KEY_DATA: ${{ secrets.PLAY_STORE_KEY }}
build-ios:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
- run: flutter pub get && flutter build ios --release --no-codesign
- name: Sign and upload to App Store
run: bundle exec fastlane ios deploy
env:
APP_STORE_CONNECT_API_KEY: ${{ secrets.ASC_KEY }}