Cross-Platform Mobile App Development with Flutter
Modern methods of developing high-performance, beautiful mobile applications for iOS and Android with a single codebase.

Enes Karakuş
Backend Developer & System Architect
Cross-Platform Mobile App Development with Flutter#
The world of mobile app development has undergone a major transformation in recent years. You no longer need to manage separate codebases for both iOS and Android platforms. Google's Flutter framework allows you to develop applications with native performance and appearance on both platforms using a single codebase. In this article, we'll explore the fundamentals of Flutter and its advantages in the modern mobile app development process.
What is Flutter?#
Flutter is an open-source UI toolkit developed by Google. It allows you to develop applications for iOS, Android, web, and desktop platforms using the Dart programming language. Flutter's most important feature is providing consistent and high-performance user interfaces independent of the platform.
Advantages of Flutter#
- Single Codebase: You can use a single codebase instead of writing separate code for iOS and Android.
- Hot Reload: You can see your changes instantly, speeding up the development process.
- Customizable Widgets: You can achieve the look you want without being limited by platform constraints.
- High Performance: Flutter's rendering engine provides 60 FPS (frames per second) performance.
- Strong Community and Support: A continuously evolving ecosystem with Google's support and a large community.
Flutter Installation and Getting Started#
Installing Flutter is quite simple. After downloading the Flutter SDK and setting up environment variables, you can start developing right away.
# Create a new Flutter project
flutter create my_app
# Go to the project directory
cd my_app
# Run the application
flutter run
Flutter's Building Blocks: Widgets#
In Flutter, everything is a widget. Buttons, text fields, images, and even layout elements are defined as widgets. Widgets are combined to create complex user interfaces.
Stateless and Stateful Widgets#
There are two basic types of widgets in Flutter:
- Stateless Widget: Static widgets whose state does not change.
- Stateful Widget: Dynamic widgets whose state can change over time.
// Stateless Widget example
class WelcomeCard extends StatelessWidget {
final String username;
const WelcomeCard({Key? key, required this.username}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
elevation: 4.0,
margin: const EdgeInsets.all(16.0),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Text(
'Welcome, $username!',
style: const TextStyle(
fontSize: 24.0,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8.0),
const Text(
'You are ready to develop amazing applications with Flutter.',
textAlign: TextAlign.center,
),
],
),
),
);
}
}
// Stateful Widget example
class Counter extends StatefulWidget {
const Counter({Key? key}) : super(key: key);
@override
_CounterState createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Counter: $_count',
style: const TextStyle(fontSize: 20.0),
),
const SizedBox(height: 16.0),
ElevatedButton(
onPressed: _increment,
child: const Text('Increment'),
),
],
);
}
}
Flutter Layout System#
Flutter's layout system provides a flexible way to arrange widgets. The most commonly used layout widgets are:
- Container: A basic structure that wraps widgets with properties like padding, margin, and decoration.
- Row and Column: Allows you to arrange child widgets horizontally (Row) or vertically (Column).
- Stack: Allows you to place widgets on top of each other.
- ListView: Creates a scrollable list.
- GridView: Creates a grid layout.
- Expanded and Flexible: Controls how widgets grow.
// Complex layout example
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter Layout Example')),
body: Column(
children: [
// Top banner area
Container(
height: 200,
width: double.infinity,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blue.shade800, Colors.blue.shade400],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
borderRadius: const BorderRadius.vertical(bottom: Radius.circular(32)),
),
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
CircleAvatar(
radius: 40,
backgroundImage: NetworkImage('https://via.placeholder.com/80'),
),
SizedBox(height: 16),
Text(
'Hello, User',
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
],
),
),
// Content list
Expanded(
child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: 15,
itemBuilder: (context, index) {
return Card(
margin: const EdgeInsets.only(bottom: 16),
child: ListTile(
leading: Icon(Icons.article, color: Colors.blue.shade700),
title: Text('Item ${index + 1}'),
subtitle: Text('This is a description text.'),
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
onTap: () {
// Action to take when the item is tapped
},
),
);
},
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// Action to take when the FAB is tapped
},
child: const Icon(Icons.add),
),
);
}
State Management#
As your application grows, state management becomes more complex. Flutter offers various solutions for state management at different levels:
- setState(): For simple within-widget state changes.
- InheritedWidget/Provider: For sharing state in the widget tree for mid-level applications.
- Riverpod: An advanced version of Provider.
- Bloc/Cubit: Reactive state management for large and complex applications.
- GetX: A lightweight solution for both state management and navigation.
State Management with Provider#
Provider is a popular option for state management in Flutter applications. Here's a simple example:
// Our state class
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners(); // Notify UI about the change
}
}
// Main application
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CounterModel(),
child: const MyApp(),
),
);
}
// Accessing the state
class CounterDisplay extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Listen to the state from the Provider
final counterModel = context.watch<CounterModel>();
return Text(
'Counter: ${counterModel.count}',
style: const TextStyle(fontSize: 20),
);
}
}
class IncrementButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Change the state
return ElevatedButton(
onPressed: () {
context.read<CounterModel>().increment();
},
child: const Text('Increment'),
);
}
}
Navigation and Routing#
In Flutter, Navigator and Route concepts are used to navigate between screens:
// Basic navigation
Navigator.push(
context,
MaterialPageRoute(builder: (context) => DetailScreen()),
);
// Navigation with named routes
Navigator.pushNamed(context, '/details');
// Defining named routes
MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => HomeScreen(),
'/details': (context) => DetailScreen(),
'/profile': (context) => ProfileScreen(),
},
);
For more complex navigation needs, packages like go_router
or auto_route
can be used.
HTTP Requests and API Integration#
Mobile applications typically communicate with servers. In Flutter, the http
package can be used for HTTP requests:
import 'package:http/http.dart' as http;
import 'dart:convert';
Future<List<Product>> fetchProducts() async {
final response = await http.get(Uri.parse('https://api.example.com/products'));
if (response.statusCode == 200) {
final List<dynamic> data = json.decode(response.body);
return data.map((json) => Product.fromJson(json)).toList();
} else {
throw Exception('Failed to load products');
}
}
class Product {
final int id;
final String title;
final double price;
Product({
required this.id,
required this.title,
required this.price
});
factory Product.fromJson(Map<String, dynamic> json) {
return Product(
id: json['id'],
title: json['title'],
price: json['price'].toDouble(),
);
}
}
Local Storage and Databases#
There are different options for local storage in Flutter:
- Shared Preferences: For storing simple key-value pairs.
- SQLite: Full SQL database support with the
sqflite
package. - Hive: Key-value based NoSQL database.
- Isar: High-performance local database.
// Shared Preferences example
import 'package:shared_preferences/shared_preferences.dart';
// Save data
Future<void> saveUsername(String username) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('username', username);
}
// Read data
Future<String?> getUsername() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('username');
}
Firebase Integration#
Flutter's development by Google also makes Firebase integration easier. You can easily add services like Firebase Authentication, Firestore, and Cloud Functions to your application.
// Firebase Authentication example
import 'package:firebase_auth/firebase_auth.dart';
Future<UserCredential> signInWithEmail(String email, String password) async {
return await FirebaseAuth.instance.signInWithEmailAndPassword(
email: email,
password: password,
);
}
// Firestore example
import 'package:cloud_firestore/cloud_firestore.dart';
Future<void> addTask(String userId, String title) async {
await FirebaseFirestore.instance.collection('tasks').add({
'userId': userId,
'title': title,
'completed': false,
'createdAt': FieldValue.serverTimestamp(),
});
}
Stream<List<Task>> getTasks(String userId) {
return FirebaseFirestore.instance
.collection('tasks')
.where('userId', isEqualTo: userId)
.orderBy('createdAt', descending: true)
.snapshots()
.map((snapshot) =>
snapshot.docs.map((doc) => Task.fromFirestore(doc)).toList()
);
}
UI/UX Features and Customizations#
Flutter offers a wide set of tools for advanced UI/UX features:
Themes and Styling#
MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
brightness: Brightness.light,
fontFamily: 'Roboto',
textTheme: const TextTheme(
headline1: TextStyle(fontSize: 72.0, fontWeight: FontWeight.bold),
headline6: TextStyle(fontSize: 36.0, fontStyle: FontStyle.italic),
bodyText2: TextStyle(fontSize: 14.0),
),
),
darkTheme: ThemeData(
primarySwatch: Colors.indigo,
brightness: Brightness.dark,
),
themeMode: ThemeMode.system, // Follow system settings
// ...
)
Animations#
Flutter provides a rich API for creating both simple and complex animations:
class AnimatedLogo extends StatefulWidget {
@override
_AnimatedLogoState createState() => _AnimatedLogoState();
}
class _AnimatedLogoState extends State<AnimatedLogo>
with SingleTickerProviderStateMixin {
late AnimationController controller;
late Animation<double> animation;
@override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
)..repeat(reverse: true);
animation = CurvedAnimation(
parent: controller,
curve: Curves.elasticOut,
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: RotationTransition(
turns: animation,
child: Container(
height: 100,
width: 100,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(10),
),
child: const Icon(
Icons.flutter_dash,
size: 60,
color: Colors.white,
),
),
),
);
}
}
Custom Components#
In Flutter, you can create your own custom components and use them consistently throughout your project:
class CustomButton extends StatelessWidget {
final String text;
final VoidCallback onPressed;
final bool isLoading;
final bool isPrimary;
const CustomButton({
Key? key,
required this.text,
required this.onPressed,
this.isLoading = false,
this.isPrimary = true,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return ElevatedButton(
onPressed: isLoading ? null : onPressed,
style: ElevatedButton.styleFrom(
primary: isPrimary ? theme.primaryColor : Colors.grey.shade200,
onPrimary: isPrimary ? Colors.white : theme.primaryColor,
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
),
child: isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: Text(
text,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
);
}
}
Tests and Quality Assurance#
Flutter provides testing capabilities at three levels:
- Unit Test: To test individual functions and methods.
- Widget Test: To test the behaviors of widgets.
- Integration Test: To test the entire application's operation.
// Widget test example
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build the widget
await tester.pumpWidget(const MyApp());
// Counter is initially 0
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the button and rebuild the framework
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Check if the counter has incremented
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}
Writing Platform-Specific Code#
In some cases, you may need to write platform-specific code. Flutter allows you to do this through Method Channel:
// Dart side
static const platform = MethodChannel('com.example.app/battery');
Future<int> getBatteryLevel() async {
try {
final int result = await platform.invokeMethod('getBatteryLevel');
return result;
} on PlatformException catch (e) {
return -1;
}
}
// Android side (Kotlin)
private fun getBatteryLevel(): Int {
val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
return batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
}
// iOS side (Swift)
private func getBatteryLevel() -> Int {
UIDevice.current.isBatteryMonitoringEnabled = true
return Int(UIDevice.current.batteryLevel * 100)
}
Flutter Web and Desktop Support#
Flutter allows you to develop applications not only for mobile platforms but also for web and desktop (Windows, macOS, Linux):
# Build for web
flutter build web
# Build for Windows
flutter build windows
# Build for macOS
flutter build macos
# Build for Linux
flutter build linux
The Future of Flutter#
The Flutter ecosystem continues to grow and mature rapidly. Thanks to Google's strong support and the large developer community, Flutter will continue to hold an important place in the cross-platform development world in the future.
With Flutter 3.0 and subsequent versions, there are performance improvements, better desktop and web support, and implementation of modern design principles such as Material You.
Conclusion#
Flutter is an excellent choice for modern and efficient cross-platform mobile app development. It offers the ability to develop high-performance, beautiful applications for multiple platforms with a single codebase. Thanks to its hot reload feature, it speeds up the development process and allows you to create rich user interfaces with its extensive widget library.
If you're looking to start mobile app development or want to improve your existing development processes, Flutter is definitely a technology worth considering. Google's continuous support and the growing community indicate that Flutter will be a reliable solution in the long run.