Overview
Flutter is Google's open-source UI software development kit for building natively compiled applications from a single codebase. Released in 2017, it has revolutionized cross-platform development with its reactive framework, rich widget library, and exceptional performance through direct compilation to native ARM code.
1. Flutter Fundamentals
Why Flutter?
- True Single Codebase: One codebase for iOS, Android, Web, Windows, macOS, Linux
- Native Performance: Compiles to ARM/x64 machine code
- Hot Reload: See changes instantly without losing state
- Rich Widget Library: 100+ customizable widgets
- Reactive Framework: UI rebuilds automatically when state changes
- Strong Ecosystem: Pub.dev with 30,000+ packages
Flutter Architecture
┌─────────────────┐
│ Your App │
├─────────────────┤
│ Flutter SDK │
├─────────────────┤
│ Dart Runtime │
├─────────────────┤
│ Skia Engine │
├─────────────────┤
│Platform Channel │
└─────────────────┘
2. Dart Language Essentials
Variables & Types
// Type inference
var name = 'Flutter';
// Explicit type
String framework = 'Flutter';
// Final (runtime constant)
final currentTime = DateTime.now();
// Const (compile-time constant)
const pi = 3.14;
// Nullable types
String? nullable = null;
// Late initialization
late String lateVar;
Functions
// Named parameters
void greet({required String name, int age = 18}) {
print('Hello $name, age: $age');
}
// Arrow functions
int add(int a, int b) => a + b;
// Anonymous functions
var multiply = (int a, int b) => a * b;
Classes & OOP
class Person {
String name;
int _age; // Private variable
// Constructor
Person(this.name, this._age);
// Named constructor
Person.guest() : name = 'Guest', _age = 0;
// Getter
int get age => _age;
// Method
void introduce() => print('I am $name');
}
// Inheritance
class Student extends Person {
String school;
Student(String name, int age, this.school) : super(name, age);
}
// Mixin
mixin Swimming {
void swim() => print('Swimming');
}
class Athlete extends Person with Swimming {
Athlete(String name, int age) : super(name, age);
}
3. Widgets
Stateless Widget
class MyCard extends StatelessWidget {
final String title;
const MyCard({Key? key, required this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
child: Text(title),
);
}
}
Stateful Widget
class Counter extends StatefulWidget {
@override
_CounterState createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Count: $_count'),
ElevatedButton(
onPressed: _increment,
child: Text('Increment'),
),
],
);
}
}
Widget Lifecycle (Stateful)
- createState() - Called once when widget is inserted
- initState() - Called once after createState
- didChangeDependencies() - Called when dependencies change
- build() - Called whenever widget needs to render
- didUpdateWidget() - Called when widget configuration changes
- setState() - Triggers rebuild
- deactivate() - Called when widget is removed from tree
- dispose() - Called when widget is permanently removed
4. Common Widgets
Layout Widgets
// Column (vertical)
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [Widget1(), Widget2()],
)
// Row (horizontal)
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [Widget1(), Widget2()],
)
// Stack (overlapping)
Stack(
alignment: Alignment.center,
children: [
Background(),
Positioned(top: 10, left: 10, child: Icon()),
],
)
// Container
Container(
padding: EdgeInsets.all(16),
margin: EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(8),
),
child: Text('Hello'),
)
// Expanded & Flexible
Row(
children: [
Expanded(flex: 2, child: Widget1()),
Expanded(flex: 1, child: Widget2()),
],
)
Scrollable Widgets
// ListView
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => ListTile(
title: Text(items[index]),
),
)
// GridView
GridView.count(
crossAxisCount: 2,
children: List.generate(6, (i) => Card(child: Text('$i'))),
)
// SingleChildScrollView
SingleChildScrollView(
child: Column(children: widgets),
)
5. State Management
setState (Local State)
setState(() {
_counter++;
});
InheritedWidget
class MyInheritedWidget extends InheritedWidget {
final int data;
MyInheritedWidget({required this.data, required Widget child})
: super(child: child);
static MyInheritedWidget? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
}
@override
bool updateShouldNotify(MyInheritedWidget old) => data != old.data;
}
Provider Pattern
// Model
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
// Usage
Provider<CounterModel>(
create: (_) => CounterModel(),
child: MyApp(),
)
// Consumer
Consumer<CounterModel>(
builder: (context, counter, child) => Text('${counter.count}'),
)
// Provider.of
final counter = Provider.of<CounterModel>(context);
BLoC Pattern (Business Logic Component)
// Event
abstract class CounterEvent {}
class Increment extends CounterEvent {}
// State
class CounterState {
final int count;
CounterState(this.count);
}
// BLoC
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterState(0));
@override
Stream<CounterState> mapEventToState(CounterEvent event) async* {
if (event is Increment) {
yield CounterState(state.count + 1);
}
}
}
7. Async Programming
Future
Future<String> fetchData() async {
await Future.delayed(Duration(seconds: 2));
return 'Data loaded';
}
// Usage
void loadData() async {
try {
final data = await fetchData();
print(data);
} catch (e) {
print('Error: $e');
}
}
// FutureBuilder
FutureBuilder<String>(
future: fetchData(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
return Text(snapshot.data ?? '');
},
)
Stream
Stream<int> countStream() async* {
for (int i = 0; i < 5; i++) {
await Future.delayed(Duration(seconds: 1));
yield i;
}
}
// StreamBuilder
StreamBuilder<int>(
stream: countStream(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text('Count: ${snapshot.data}');
}
return Text('Waiting...');
},
)
8. HTTP & APIs
Using http package
import 'package:http/http.dart' as http;
import 'dart:convert';
Future<List<User>> fetchUsers() async {
final response = await http.get(
Uri.parse('https://api.example.com/users'),
headers: {'Authorization': 'Bearer token'},
);
if (response.statusCode == 200) {
final List<dynamic> data = json.decode(response.body);
return data.map((json) => User.fromJson(json)).toList();
} else {
throw Exception('Failed to load users');
}
}
// POST request
Future<void> createUser(User user) async {
final response = await http.post(
Uri.parse('https://api.example.com/users'),
headers: {'Content-Type': 'application/json'},
body: json.encode(user.toJson()),
);
}
9. Local Storage
SharedPreferences
// Save
final prefs = await SharedPreferences.getInstance();
await prefs.setString('username', 'John');
await prefs.setInt('age', 25);
await prefs.setBool('isLoggedIn', true);
// Read
final username = prefs.getString('username') ?? '';
final age = prefs.getInt('age') ?? 0;
final isLoggedIn = prefs.getBool('isLoggedIn') ?? false;
// Remove
await prefs.remove('username');
SQLite
// Database helper
class DatabaseHelper {
static Database? _database;
Future<Database> get database async {
if (_database != null) return _database!;
_database = await _initDB();
return _database!;
}
Future<Database> _initDB() async {
String path = join(await getDatabasesPath(), 'app.db');
return await openDatabase(
path,
version: 1,
onCreate: (db, version) async {
await db.execute('''
CREATE TABLE users(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
age INTEGER
)
''');
},
);
}
Future<int> insertUser(Map<String, dynamic> user) async {
Database db = await database;
return await db.insert('users', user);
}
}
10. Forms & Validation
class LoginForm extends StatefulWidget {
@override
_LoginFormState createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _emailController,
decoration: InputDecoration(labelText: 'Email'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter email';
}
if (!value.contains('@')) {
return 'Please enter valid email';
}
return null;
},
),
TextFormField(
controller: _passwordController,
obscureText: true,
decoration: InputDecoration(labelText: 'Password'),
validator: (value) {
if (value == null || value.length < 6) {
return 'Password must be at least 6 characters';
}
return null;
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// Process data
}
},
child: Text('Submit'),
),
],
),
);
}
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
}
11. Animations
Implicit Animations
// AnimatedContainer
AnimatedContainer(
duration: Duration(seconds: 1),
width: _expanded ? 200 : 100,
height: _expanded ? 200 : 100,
color: _expanded ? Colors.blue : Colors.red,
child: Text('Tap me'),
)
// AnimatedOpacity
AnimatedOpacity(
opacity: _visible ? 1.0 : 0.0,
duration: Duration(milliseconds: 500),
child: Text('Fade me'),
)
Explicit Animations
class _MyAnimationState extends State<MyAnimation>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
_animation = Tween<double>(begin: 0, end: 300).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
);
_controller.forward();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Container(
width: _animation.value,
height: _animation.value,
color: Colors.blue,
);
},
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
12. Platform-Specific Code
Platform Channels
// Flutter side
class BatteryLevel {
static const platform = MethodChannel('com.example.app/battery');
Future<int> getBatteryLevel() async {
try {
final int result = await platform.invokeMethod('getBatteryLevel');
return result;
} catch (e) {
return -1;
}
}
}
// Check platform
import 'dart:io';
if (Platform.isAndroid) {
// Android specific code
} else if (Platform.isIOS) {
// iOS specific code
}
13. Testing
Unit Testing
// Test file: test/calculator_test.dart
import 'package:test/test.dart';
void main() {
group('Calculator', () {
test('addition', () {
expect(add(2, 3), 5);
});
test('subtraction', () {
expect(subtract(5, 3), 2);
});
});
}
Widget Testing
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Counter increments', (WidgetTester tester) async {
await tester.pumpWidget(MyApp());
// Find widgets
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap button
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}
14. Performance Best Practices
Keys
// Use keys for widget identity
ListView.builder(
itemBuilder: (context, index) => MyWidget(
key: ValueKey(items[index].id),
data: items[index],
),
)
const Constructors
// Use const for compile-time constants
const Text('Hello World')
const EdgeInsets.all(16.0)
const SizedBox(height: 20)
Build Optimization
// Avoid rebuilding in build method
class BadExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
// DON'T: Create new objects in build
final data = expensiveComputation(); // Bad!
return Text(data);
}
}
class GoodExample extends StatefulWidget {
@override
_GoodExampleState createState() => _GoodExampleState();
}
class _GoodExampleState extends State<GoodExample> {
late String data;
@override
void initState() {
super.initState();
// DO: Initialize expensive operations once
data = expensiveComputation(); // Good!
}
@override
Widget build(BuildContext context) {
return Text(data);
}
}
15. Critical Interview Topics
Widget Types - Deep Understanding
StatelessWidget:
- Immutable configuration
- No internal state management
- Rebuilds only when parent rebuilds
- Performance: More efficient for static content
- Example: Icons, Labels, Containers with fixed content
StatefulWidget:
- Maintains mutable state
- Two classes: Widget (immutable) + State (mutable)
- Can trigger its own rebuilds via setState()
- Lifecycle methods available
- Example: Forms, Animations, Dynamic lists
BuildContext Explained
// BuildContext is your widget's GPS in the widget tree
// It provides:
// 1. Location in tree
// 2. Access to inherited widgets
// 3. Theme and MediaQuery data
// Common uses:
Theme.of(context).primaryColor
MediaQuery.of(context).size
Navigator.of(context).push()
Scaffold.of(context).showSnackBar()
Hot Reload vs Hot Restart vs Full Restart
| Feature | Hot Reload | Hot Restart | Full Restart |
|---|---|---|---|
| State Preservation | Yes | No | No |
| Speed | <1 second | 2-5 seconds | 10-30 seconds |
| Recompiles | Modified files | All Dart code | Everything |
| Use Case | UI tweaks | Logic changes | Native code changes |
| Shortcut | r | R | Stop + Run |
Layout Understanding - Constraints Go Down, Sizes Go Up
Flutter's Golden Rule:
"Constraints go down. Sizes go up. Parent sets position."
MainAxis vs CrossAxis:
// Column: mainAxis = vertical, crossAxis = horizontal
Column(
mainAxisAlignment: MainAxisAlignment.center, // Vertical
crossAxisAlignment: CrossAxisAlignment.start, // Horizontal
children: [...],
)
// Row: mainAxis = horizontal, crossAxis = vertical
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, // Horizontal
crossAxisAlignment: CrossAxisAlignment.center, // Vertical
children: [...],
)
Async Patterns - FutureBuilder vs StreamBuilder
| Aspect | FutureBuilder | StreamBuilder |
|---|---|---|
| Data Type | Future |
Stream |
| Updates | Once | Multiple times |
| Use Cases | HTTP requests, File I/O | Real-time data, WebSockets |
| State Management | ConnectionState | AsyncSnapshot |
| Memory | Disposed after complete | Needs manual disposal |
// FutureBuilder - One-time fetch
FutureBuilder<User>(
future: fetchUser(), // Called once
builder: (context, snapshot) {
if (snapshot.hasData) return UserWidget(snapshot.data!);
if (snapshot.hasError) return ErrorWidget();
return LoadingWidget();
},
)
// StreamBuilder - Continuous updates
StreamBuilder<List<Message>>(
stream: chatStream(), // Continuous emission
builder: (context, snapshot) {
// Rebuilds on every stream event
},
)
Responsive Design Strategies
1. MediaQuery Approach:
class ResponsiveWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
final orientation = MediaQuery.of(context).orientation;
final padding = MediaQuery.of(context).padding;
// Breakpoints
final isMobile = size.width < 600;
final isTablet = size.width >= 600 && size.width < 1200;
final isDesktop = size.width >= 1200;
return Container(
width: isMobile ? double.infinity : 600,
padding: EdgeInsets.all(isMobile ? 16 : 32),
);
}
}
2. LayoutBuilder for Adaptive Layouts:
LayoutBuilder(
builder: (context, constraints) {
// Responsive grid
int crossAxisCount = constraints.maxWidth < 600 ? 2 :
constraints.maxWidth < 900 ? 3 : 4;
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
),
itemBuilder: (context, index) => Card(),
);
},
)
3. Platform-Adaptive Components:
// Adaptive widgets that look native on each platform
import 'package:flutter/cupertino.dart';
Switch.adaptive() // Material on Android, Cupertino on iOS
Slider.adaptive()
CircularProgressIndicator.adaptive()
Keys - The Complete Guide
When to Use Keys:
- Reordering lists with stateful children
- Animating widget additions/removals
- Preserving scroll position
- Maintaining form field state
Types of Keys:
// ValueKey - When you have a unique primitive value
ListTile(key: ValueKey(item.id))
// ObjectKey - For complex objects
Widget(key: ObjectKey(person))
// UniqueKey - New key every time
AnimatedWidget(key: UniqueKey())
// GlobalKey - Access widget state/context from anywhere
final _scaffoldKey = GlobalKey<ScaffoldState>();
_scaffoldKey.currentState?.showSnackBar();
// PageStorageKey - Preserve scroll position
ListView(key: PageStorageKey('list_key'))
Common Mistake:
// WRONG - Key on wrong widget level
Padding(
padding: EdgeInsets.all(8),
child: Container(key: ValueKey(item.id)),
)
// CORRECT - Key at the right level
Padding(
key: ValueKey(item.id),
padding: EdgeInsets.all(8),
child: Container(),
)
Memory Management Best Practices
1. Proper Disposal Pattern:
class _MyWidgetState extends State<MyWidget> {
late AnimationController _animationController;
late TextEditingController _textController;
late ScrollController _scrollController;
StreamSubscription? _subscription;
Timer? _timer;
@override
void initState() {
super.initState();
_animationController = AnimationController(vsync: this);
_textController = TextEditingController();
_scrollController = ScrollController();
_subscription = stream.listen((data) {});
_timer = Timer.periodic(Duration(seconds: 1), (timer) {});
}
@override
void dispose() {
// Dispose in reverse order of initialization
_timer?.cancel();
_subscription?.cancel();
_scrollController.dispose();
_textController.dispose();
_animationController.dispose();
super.dispose(); // Always call super.dispose() last
}
}
2. Common Memory Leak Causes:
- Not disposing controllers
- Keeping references to disposed contexts
- Uncancelled timers/animations
- Stream subscriptions without cancellation
- GlobalKeys holding state references
- Circular references in closures
3. Detection Tools:
- Flutter Inspector's memory profiler
- Dart DevTools memory view
- LeakCanary for platform-specific leaks
16. Common Patterns & Tips
Null Safety
// Null-aware operators
String? name;
print(name ?? 'Default'); // Default if null
print(name?.length); // Null if name is null
name ??= 'John'; // Assign if null
// Late initialization
late String lateString;
Extension Methods
extension StringExtension on String {
String capitalize() {
return '${this[0].toUpperCase()}${substring(1)}';
}
}
// Usage
'hello'.capitalize(); // 'Hello'
Factory Pattern
class Logger {
static final Map<String, Logger> _cache = {};
final String name;
factory Logger(String name) {
return _cache.putIfAbsent(name, () => Logger._internal(name));
}
Logger._internal(this.name);
}
Singleton Pattern
class DatabaseService {
static final DatabaseService _instance = DatabaseService._internal();
factory DatabaseService() => _instance;
DatabaseService._internal();
}
17. Important Packages
- state_management: provider, bloc, riverpod, getx
- navigation: go_router, auto_route
- networking: dio, http, retrofit
- storage: shared_preferences, hive, sqflite
- images: cached_network_image, image_picker
- forms: flutter_form_builder
- animations: lottie, rive
- testing: mockito, flutter_test
18. Debug Tools & Commands
# Create new project
flutter create my_app
# Run app
flutter run
# Build APK
flutter build apk
# Build iOS
flutter build ios
# Run tests
flutter test
# Analyze code
flutter analyze
# Clean project
flutter clean
# Get packages
flutter pub get
# Upgrade packages
flutter pub upgrade
Debug Tools
- Flutter Inspector
- Performance Overlay
- Debug Paint
- Widget Inspector
- Timeline View
Performance Optimization Checklist
Build Performance
- Use const constructors wherever possible
- Split widgets into smaller, reusable components
- Avoid rebuilding expensive widgets unnecessarily
- Use RepaintBoundary for complex graphics
- Implement itemExtent in ListView for better performance
State Management Performance
- Lift state up only as high as necessary
- Use ValueListenableBuilder for single value changes
- Avoid setState() in loops
- Use Selector (Provider) to rebuild only affected widgets
Image & Asset Optimization
// Cache network images
CachedNetworkImage(
imageUrl: url,
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
)
// Preload images
precacheImage(AssetImage('assets/background.png'), context);
// Use appropriate image formats
// WebP for photos (30% smaller)
// PNG for graphics with transparency
// SVG for scalable graphics
State Management Decision Tree
Local State? → setState
Ancestor Access? → InheritedWidget
Simple App? → Provider
Complex App? → BLoC/Riverpod
Performance Checklist
- Use const constructors
- Implement Keys correctly
- Dispose controllers
- Avoid rebuilding expensive widgets
- Use ListView.builder for long lists
- Cache network images
- Minimize widget rebuilds
Common Gotchas
- Don't call setState() in build()
- Don't forget to dispose() controllers
- Use Keys when reordering widgets
- Avoid storing BuildContext
- Handle null safety properly
Interview Success Strategy
Technical Preparation
- Master the fundamentals: Widget lifecycle, BuildContext, Keys
- Practice live coding: Build a TODO app, Weather app, Chat UI
- Know performance optimization: Const widgets, Keys, RepaintBoundary
- Understand state management: setState, Provider, Riverpod, BLoC
- Platform-specific knowledge: Method channels, platform views
Common Code Challenges
- Build a custom widget from scratch
- Implement infinite scrolling list
- Create a form with validation
- Handle API calls with error states
- Implement custom animations
- Build responsive layouts
Red Flags to Avoid
- Not using
constconstructors - Memory leaks from undisposed controllers
- Rebuilding entire tree with setState
- Network calls in build methods
- Ignoring null safety
- Poor error handling
Pro Tip: Flutter rewards composition over inheritance. When asked to solve a problem, think about combining existing widgets rather than creating complex inheritance hierarchies. Show your understanding of Flutter's declarative paradigm and reactive framework.