Build iOS/Android/Web App by Flutter

flutteriosandroidwebdart

Flutter is a cross-platform framework by Google. In addition to iOS/Android, Web became stable in version 2.0 which is released in March of this year, and Windows/Mac/Linux is beta. Unlike React Native which uses native UI, Flutter uses its own UI. In addition to Material, iOS-style Cupertino is also available, but unless it branches etc., it become the same looks regardless of the platform.

Build the environment

Build the environment according to the official Get started.

First, install Flutter SDK and add PATH.

$ mv ~/Downloads/flutter ~/
$ echo 'export PATH="$PATH:~/flutter/bin"' >> ~/.bash_profile 
$ source ~/.bash_profile
$ flutter doctor

Android

Install Android Studio and launch it to download dependencies. If cmdline-tools component is missing appears, install it from SDK Manager.

Approve licenses.

$ flutter doctor --android-licenses

Set up an emulator from AVD Manager. Select Hardware - GLES 2.0 as Emulated Performance.

iOS

Install Xcode and CocoaPods, and set up command-line tools.

$ sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
$ sudo xcodebuild -runFirstLaunch
$ sudo gem install cocoapods

VSCode

Install Flutter extension, run Flutter: New Project from Command Palette and select Application, then a project of following structure is created.

$ tree -L 2 .
.
├── README.md
├── analysis_options.yaml
├── android
│   ├── app
│   ├── build.gradle
│   ├── flutter_application_1_android.iml
│   ├── gradle
│   ├── gradle.properties
│   ├── gradlew
│   ├── gradlew.bat
│   ├── local.properties
│   └── settings.gradle
├── flutter_application_1.iml
├── ios
│   ├── Flutter
│   ├── Runner
│   ├── Runner.xcodeproj
│   └── Runner.xcworkspace
├── lib
│   └── main.dart
├── pubspec.lock
├── pubspec.yaml
├── test
│   └── widget_test.dart
└── web
    ├── favicon.png
    ├── icons
    ├── index.html
    └── manifest.json

If Run > Start Debugging in lib/main.dart, the build starts for the selected device. It takes a long time at startup but after that changes is hot reloaded and reflected immediately.

When run on the web, it was rendered by canvas instead of HTML. This behavior can be changed by –web-renderer. Canvas has high performance and can be rendered same as other platform, but the download size is larger.

Look at files

pubspec.yaml has dependencies and flutter’s settings etc. If add a dependency here and run flutter pub get, the pakage is installed.

$ cat pubspec.yaml
name: flutter_application_1
description: A new Flutter project.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev

environment:
  sdk: ">=2.12.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  english_words: ^4.0.0

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^1.0.0

flutter:
  uses-material-design: true
  ...

Looking at the entry point lib/main.dart, StatelessWidget is defined.

$ cat lib/main.dart
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}
...

There is also a StatefulWidget which literally has a State.

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

State returns Widget to render by build().

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
    );
  }
}

For example, if you rewrite the body of Scaffold as follows, the list is rendered.

body: ListView.builder(
  padding: const EdgeInsets.all(16.0),
  itemBuilder: (context, i) {
    return ListTile(title: Text(i.toString()));
  },
  itemCount: 10,
)