コンテンツにスキップ

Flutter Cheatsheet

Flutter - Build Apps for Any Screen

Flutter is Google's UI toolkit for building beautiful, natively compiled applications for mobile, web, and desktop from a single codebase. It is known for its expressive UI, fast development cycle, and native performance.

Table of Contents

Installation

Flutter SDK

# Download Flutter SDK from https://flutter.dev/docs/get-started/install
# Extract the zip file

# Add Flutter to your path (for the current terminal session)
export PATH="$PATH:`pwd`/flutter/bin"

# Add Flutter to your path permanently (macOS/Linux)
echo 'export PATH="$PATH:[PATH_TO_FLUTTER_GIT_DIRECTORY]/flutter/bin"' >> ~/.zshrc
source ~/.zshrc

# Add Flutter to your path permanently (Windows)
# Update your system's environment variables

Platform Setup

Android Setup

# Install Android Studio
# Download from https://developer.android.com/studio

# Set up Android emulator
# Open Android Studio > AVD Manager > Create Virtual Device

# Accept Android licenses
flutter doctor --android-licenses

iOS Setup (macOS only)

# Install Xcode
# Download from Mac App Store

# Install CocoaPods
sudo gem install cocoapods

# Set up iOS simulator
open -a Simulator

Flutter Doctor

# Check your environment and see a report of the status of your Flutter installation
flutter doctor

# Verbose output
flutter doctor -v

Getting Started

Create a New Project

# Create a new Flutter project
flutter create my_app

# Navigate to the project directory
cd my_app

# Create a project with a specific organization
flutter create --org com.example my_app

# Create a project with a specific template
flutter create -t module my_module

Run the App

# Run the app on an available device
flutter run

# Run on a specific device
flutter run -d chrome
flutter run -d 'iPhone 12'

# Run in release mode
flutter run --release

# Run with hot reload
# Press 'r' in the terminal

# Run with hot restart
# Press 'R' in the terminal

Project Structure

my_app/
├── android/          # Android-specific code
├── ios/              # iOS-specific code
├── lib/              # Main Dart code
│   ├── main.dart     # App entry point
│   ├── screens/      # Screen widgets
│   ├── widgets/      # Reusable widgets
│   ├── models/       # Data models
│   ├── services/     # API services
│   └── utils/        # Utility functions
├── test/             # Test files
├── pubspec.yaml      # Project dependencies and metadata
└── README.md         # Project documentation

Core Concepts

Dart Language Basics

// Variables
var name = 'Flutter';
String language = 'Dart';
int year = 2017;
double version = 2.12;
bool isAwesome = true;

// Functions
int add(int a, int b) {
  return a + b;
}

// Arrow syntax
int multiply(int a, int b) => a * b;

// Classes
class Person {
  String name;
  int age;

  Person(this.name, this.age);

  void sayHello() {
    print('Hello, my name is $name and I am $age years old.');
  }
}

// Asynchronous programming
Future<String> fetchData() async {
  await Future.delayed(Duration(seconds: 2));
  return 'Data fetched';
}

void main() async {
  var person = Person('John Doe', 30);
  person.sayHello();

  print('Fetching data...');
  var data = await fetchData();
  print(data);
}

Everything is a Widget

import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('My First Flutter App'),
        ),
        body: Center(
          child: Text('Hello, Flutter!'),
        ),
      ),
    ),
  );
}

BuildContext

// BuildContext provides information about the location of a widget in the widget tree.
// It is used to access theme data, navigation, and other inherited widgets.

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      onPressed: () {
        Scaffold.of(context).showSnackBar(
          SnackBar(content: Text('Button pressed')),
        );
      },
      child: Text('Press Me'),
    );
  }
}

Stateless vs. Stateful Widgets

// StatelessWidget: A widget that does not require mutable state.
class MyStatelessWidget extends StatelessWidget {
  final String title;

  MyStatelessWidget({required this.title});

  @override
  Widget build(BuildContext context) {
    return Text(title);
  }
}

// StatefulWidget: A widget that has mutable state.
class MyStatefulWidget extends StatefulWidget {
  @override
  _MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Counter: $_counter'),
        RaisedButton(
          onPressed: _incrementCounter,
          child: Text('Increment'),
        ),
      ],
    );
  }
}

Widgets

Basic Widgets

import 'package:flutter/material.dart';

class BasicWidgets extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Basic Widgets')),
      body: SingleChildScrollView(
        padding: EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // Text
            Text('This is a Text widget', style: TextStyle(fontSize: 18)),
            SizedBox(height: 20),

            // Icon
            Icon(Icons.favorite, color: Colors.red, size: 30),
            SizedBox(height: 20),

            // Image
            Image.network('https://flutter.dev/images/flutter-logo-sharing.png'),
            SizedBox(height: 20),

            // Asset Image
            Image.asset('assets/images/logo.png'),
            SizedBox(height: 20),

            // Button
            RaisedButton(
              onPressed: () {},
              child: Text('RaisedButton'),
            ),
            SizedBox(height: 10),
            FlatButton(
              onPressed: () {},
              child: Text('FlatButton'),
            ),
            SizedBox(height: 10),
            IconButton(
              onPressed: () {},
              icon: Icon(Icons.thumb_up),
            ),
            SizedBox(height: 20),

            // TextField
            TextField(
              decoration: InputDecoration(
                labelText: 'Enter your name',
                border: OutlineInputBorder(),
              ),
            ),
            SizedBox(height: 20),

            // Checkbox
            CheckboxListTile(
              title: Text('Accept terms and conditions'),
              value: true,
              onChanged: (value) {},
            ),
            SizedBox(height: 20),

            // Radio
            RadioListTile(
              title: Text('Option 1'),
              value: 1,
              groupValue: 1,
              onChanged: (value) {},
            ),
            SizedBox(height: 20),

            // Switch
            SwitchListTile(
              title: Text('Enable notifications'),
              value: true,
              onChanged: (value) {},
            ),
            SizedBox(height: 20),

            // Slider
            Slider(
              value: 0.5,
              onChanged: (value) {},
            ),
            SizedBox(height: 20),

            // Progress Indicator
            CircularProgressIndicator(),
            SizedBox(height: 10),
            LinearProgressIndicator(),
          ],
        ),
      ),
    );
  }
}

Material Components

import 'package:flutter/material.dart';

class MaterialComponents extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Material Components')),
      drawer: Drawer(
        child: ListView(
          children: [
            DrawerHeader(child: Text('Drawer Header')),
            ListTile(title: Text('Item 1')),
            ListTile(title: Text('Item 2')),
          ],
        ),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // Card
            Card(
              child: Padding(
                padding: EdgeInsets.all(16.0),
                child: Text('This is a Card'),
              ),
            ),
            SizedBox(height: 20),

            // Chip
            Chip(
              avatar: CircleAvatar(child: Text('A')),
              label: Text('Chip'),
            ),
            SizedBox(height: 20),

            // DataTable
            DataTable(
              columns: [
                DataColumn(label: Text('ID')),
                DataColumn(label: Text('Name')),
              ],
              rows: [
                DataRow(cells: [DataCell(Text('1')), DataCell(Text('John'))]),
                DataRow(cells: [DataCell(Text('2')), DataCell(Text('Jane'))]),
              ],
            ),
            SizedBox(height: 20),

            // SnackBar
            RaisedButton(
              onPressed: () {
                Scaffold.of(context).showSnackBar(
                  SnackBar(content: Text('This is a SnackBar')),
                );
              },
              child: Text('Show SnackBar'),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {},
        child: Icon(Icons.add),
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
          BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Search'),
          BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
        ],
      ),
    );
  }
}

Layouts

Single-Child Layout Widgets

import 'package:flutter/material.dart';

class SingleChildLayouts extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Single-Child Layouts')),
      body: Column(
        children: [
          // Container
          Container(
            color: Colors.blue,
            padding: EdgeInsets.all(16.0),
            margin: EdgeInsets.all(16.0),
            child: Text('Container'),
          ),

          // Center
          Center(
            child: Text('Center'),
          ),

          // Padding
          Padding(
            padding: EdgeInsets.all(16.0),
            child: Text('Padding'),
          ),

          // Align
          Align(
            alignment: Alignment.bottomRight,
            child: Text('Align'),
          ),

          // SizedBox
          SizedBox(
            width: 200,
            height: 100,
            child: Card(child: Center(child: Text('SizedBox'))),
          ),

          // AspectRatio
          AspectRatio(
            aspectRatio: 16 / 9,
            child: Container(color: Colors.green),
          ),

          // ConstrainedBox
          ConstrainedBox(
            constraints: BoxConstraints(maxWidth: 200, maxHeight: 100),
            child: Container(color: Colors.red),
          ),
        ],
      ),
    );
  }
}

Multi-Child Layout Widgets

import 'package:flutter/material.dart';

class MultiChildLayouts extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Multi-Child Layouts')),
      body: Column(
        children: [
          // Row
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [Text('Row 1'), Text('Row 2'), Text('Row 3')],
          ),

          // Column
          Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [Text('Column 1'), Text('Column 2'), Text('Column 3')],
          ),

          // Stack
          Stack(
            children: [
              Container(width: 100, height: 100, color: Colors.red),
              Container(width: 80, height: 80, color: Colors.green),
              Container(width: 60, height: 60, color: Colors.blue),
            ],
          ),

          // ListView
          Expanded(
            child: ListView(
              children: [
                ListTile(title: Text('Item 1')),
                ListTile(title: Text('Item 2')),
                ListTile(title: Text('Item 3')),
              ],
            ),
          ),

          // GridView
          Expanded(
            child: GridView.count(
              crossAxisCount: 2,
              children: List.generate(4, (index) => Center(child: Text('Item $index'))),
            ),
          ),
        ],
      ),
    );
  }
}
// Navigate to a new screen
Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => DetailsScreen()),
);

// Navigate back
Navigator.pop(context);

// Navigate with data
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => DetailsScreen(item: item),
  ),
);

// Receive data from a screen
final result = await Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => SelectionScreen()),
);

// Send data back
Navigator.pop(context, 'Yes');
// pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  go_router: ^3.0.0

// main.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routeInformationParser: _router.routeInformationParser,
      routerDelegate: _router.routerDelegate,
    );
  }

  final _router = GoRouter(
    routes: [
      GoRoute(
        path: '/',
        builder: (context, state) => HomeScreen(),
      ),
      GoRoute(
        path: '/details/:id',
        builder: (context, state) => DetailsScreen(id: state.params['id']!),
      ),
    ],
  );
}

// Navigate with go_router
context.go('/details/123');
context.push('/details/456');

State Management

Provider

// pubspec.yaml
dependencies:
  provider: ^6.0.0

// main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => Counter(),
      child: MyApp(),
    ),
  );
}

class Counter with ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

// Usage in a widget
class CounterScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Provider Example')),
      body: Center(
        child: Text('Count: ${context.watch<Counter>().count}'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.read<Counter>().increment(),
        child: Icon(Icons.add),
      ),
    );
  }
}

BLoC (Business Logic Component)

// pubspec.yaml
dependencies:
  flutter_bloc: ^8.0.0

// counter_event.dart
abstract class CounterEvent {}
class Increment extends CounterEvent {}

// counter_bloc.dart
import 'package:bloc/bloc.dart';
import 'counter_event.dart';

class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(0) {
    on<Increment>((event, emit) => emit(state + 1));
  }
}

// main.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() {
  runApp(
    BlocProvider(
      create: (context) => CounterBloc(),
      child: MyApp(),
    ),
  );
}

// Usage in a widget
class CounterScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('BLoC Example')),
      body: Center(
        child: BlocBuilder<CounterBloc, int>(
          builder: (context, count) => Text('Count: $count'),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.read<CounterBloc>().add(Increment()),
        child: Icon(Icons.add),
      ),
    );
  }
}

Riverpod

// pubspec.yaml
dependencies:
  flutter_riverpod: ^1.0.0

// main.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

final counterProvider = StateProvider((ref) => 0);

void main() {
  runApp(ProviderScope(child: MyApp()));
}

// Usage in a widget
class CounterScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);

    return Scaffold(
      appBar: AppBar(title: Text('Riverpod Example')),
      body: Center(
        child: Text('Count: $count'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => ref.read(counterProvider.state).state++,
        child: Icon(Icons.add),
      ),
    );
  }
}

Networking

http Package

// pubspec.yaml
dependencies:
  http: ^0.13.0

// services/api_service.dart
import 'package:http/http.dart' as http;
import 'dart:convert';

class ApiService {
  Future<Map<String, dynamic>> fetchData() async {
    final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts/1'));
    if (response.statusCode == 200) {
      return json.decode(response.body);
    } else {
      throw Exception('Failed to load data');
    }
  }
}

dio Package

// pubspec.yaml
dependencies:
  dio: ^4.0.0

// services/api_service.dart
import 'package:dio/dio.dart';

class ApiService {
  final dio = Dio();

  Future<Map<String, dynamic>> fetchData() async {
    try {
      final response = await dio.get('https://jsonplaceholder.typicode.com/posts/1');
      return response.data;
    } catch (e) {
      throw Exception('Failed to load data');
    }
  }
}

Persistence

shared_preferences

// pubspec.yaml
dependencies:
  shared_preferences: ^2.0.0

// utils/storage.dart
import 'package:shared_preferences/shared_preferences.dart';

class Storage {
  Future<void> saveUsername(String username) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('username', username);
  }

  Future<String?> getUsername() async {
    final prefs = await SharedPreferences.getInstance();
    return prefs.getString('username');
  }
}

sqflite

// pubspec.yaml
dependencies:
  sqflite: ^2.0.0
  path: ^1.8.0

// services/database_service.dart
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';

class DatabaseService {
  late Database _database;

  Future<void> initDatabase() async {
    _database = await openDatabase(
      join(await getDatabasesPath(), 'my_database.db'),
      onCreate: (db, version) {
        return db.execute(
          'CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)',
        );
      },
      version: 1,
    );
  }

  Future<void> insertUser(Map<String, dynamic> user) async {
    await _database.insert('users', user, conflictAlgorithm: ConflictAlgorithm.replace);
  }

  Future<List<Map<String, dynamic>>> getUsers() async {
    return await _database.query('users');
  }
}

Animations

Implicit Animations

class ImplicitAnimation extends StatefulWidget {
  @override
  _ImplicitAnimationState createState() => _ImplicitAnimationState();
}

class _ImplicitAnimationState extends State<ImplicitAnimation> {
  bool _isBig = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => setState(() => _isBig = !_isBig),
      child: AnimatedContainer(
        duration: Duration(seconds: 1),
        width: _isBig ? 200 : 100,
        height: _isBig ? 200 : 100,
        color: _isBig ? Colors.blue : Colors.red,
      ),
    );
  }
}

Explicit Animations

class ExplicitAnimation extends StatefulWidget {
  @override
  _ExplicitAnimationState createState() => _ExplicitAnimationState();
}

class _ExplicitAnimationState extends State<ExplicitAnimation> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this, duration: Duration(seconds: 2));
    _animation = Tween<double>(begin: 0, end: 300).animate(_controller)
      ..addListener(() => setState(() {}));
    _controller.forward();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      width: _animation.value,
      height: _animation.value,
      color: Colors.green,
    );
  }
}

Platform Integration

MethodChannel

// Dart side
import 'package:flutter/services.dart';

const platform = MethodChannel('com.example.myapp/channel');

Future<String> getBatteryLevel() async {
  try {
    final int result = await platform.invokeMethod('getBatteryLevel');
    return 'Battery level at $result % .';
  } on PlatformException catch (e) {
    return "Failed to get battery level: '${e.message}'.";
  }
}

// Android side (MainActivity.java)
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.plugin.common.MethodChannel;

public class MainActivity extends FlutterActivity {
  private static final String CHANNEL = "com.example.myapp/channel";

  @Override
  public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
    super.configureFlutterEngine(flutterEngine);
    new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
        .setMethodCallHandler(
          (call, result) -> {
            if (call.method.equals("getBatteryLevel")) {
              // ... get battery level
              result.success(batteryLevel);
            }
          }
        );
  }
}

Testing

Unit Testing

// test/counter_test.dart
import 'package:test/test.dart';
import 'package:my_app/counter.dart';

void main() {
  test('Counter value should be incremented', () {
    final counter = Counter();
    counter.increment();
    expect(counter.value, 1);
  });
}

Widget Testing

// test/widget_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/main.dart';

void main() {
  testWidgets('Counter increments smoke test', (WidgetTester tester) async {
    await tester.pumpWidget(MyApp());

    expect(find.text('0'), findsOneWidget);
    expect(find.text('1'), findsNothing);

    await tester.tap(find.byIcon(Icons.add));
    await tester.pump();

    expect(find.text('0'), findsNothing);
    expect(find.text('1'), findsOneWidget);
  });
}

Integration Testing

// test_driver/app_test.dart
import 'package:flutter_driver/flutter_driver.dart';
import 'package:test/test.dart';

void main() {
  group('Counter App', () {
    FlutterDriver driver;

    setUpAll(() async {
      driver = await FlutterDriver.connect();
    });

    tearDownAll(() async {
      if (driver != null) {
        driver.close();
      }
    });

    test('increments the counter', () async {
      final counterTextFinder = find.byValueKey('counter');
      final buttonFinder = find.byValueKey('increment');

      expect(await driver.getText(counterTextFinder), '0');

      await driver.tap(buttonFinder);

      expect(await driver.getText(counterTextFinder), '1');
    });
  });
}

Debugging

DevTools

# Open DevTools from the terminal
flutter pub global activate devtools
flutter pub global run devtools

# Or from VS Code / Android Studio

Logging

import 'dart:developer' as developer;

void myFunction() {
  developer.log('This is a log message', name: 'my.app.category');
}

Deployment

Android

# Build an APK
flutter build apk

# Build an App Bundle
flutter build appbundle

iOS

# Build an IPA
flutter build ipa

Web

# Build for web
flutter build web

Best Practices

  • Follow Effective Dart guidelines: Write clean, maintainable Dart code.
  • Organize your project structure: Keep your code organized and easy to navigate.
  • Use a state management solution: Choose a state management solution that fits your app's complexity.
  • Write tests: Write unit, widget, and integration tests to ensure your app is working correctly.
  • Optimize performance: Use DevTools to identify and fix performance bottlenecks.
  • Handle errors gracefully: Use try-catch blocks and error widgets to handle errors.

Troubleshooting

Common Issues

  • Platform-specific issues: Check your platform setup (Android Studio, Xcode).
  • Dependency conflicts: Run flutter pub deps to check for dependency conflicts.
  • Build errors: Run flutter clean and then flutter pub get to clean up your build files.

Summary

Flutter is a powerful and flexible UI toolkit for building high-quality, cross-platform applications. Its key features include:

  • Fast Development: Hot reload allows you to see changes instantly.
  • Expressive UI: Build beautiful and custom UIs with a rich set of widgets.
  • Native Performance: Flutter apps are compiled to native code, providing excellent performance.
  • Single Codebase: Write once, run on mobile, web, and desktop.
  • Growing Community: A large and active community provides support and a rich ecosystem of packages.

Flutter is an excellent choice for developers who want to build beautiful, high-performance applications for multiple platforms with a single codebase.