エンジニアHubPowered by エン転職

若手Webエンジニアのための情報メディア

Flutter入門 - 簡単なアプリを作ってUI宣言やホットリロードなど便利機能の使い方を理解しよう

モバイル向けアプリケーションのフレームワーク・Flutterを使って簡単なアプリケーションを作成する基本的な開発について、FlutterのGoogle Developers Expertである上田哲広さんに解説していただきました。

Flutter入門

こんにちは。上田哲広@najeiraです。FlutterのGoogle Developers Expertとして活動しています。

Flutterは、Googleが中心となってGitHub上でオープンソースなプロジェクトとして開発されている、モバイル向けアプリケーションのフレームワークです。AndroidとiOSのアプリを単一のコードベースで開発できます。

GitHub - flutter/flutter: Flutter makes it easy and fast to build beautiful mobile apps.
※Webやデスクトップへのサポートも今後のロードマップに含まれています。

FlutterのアプリはDart言語を使って記述します。モダンでReactiveなUIフレームワークで、美しいUI群が豊富に含まれています。FlutterではUIの各部品のことをWidgetと呼びます。

特長として、宣言的なUI定義やホットリロード(ファイルの変更・保存をするとリロードすることなくリアルタイムでファイルを読み込み直す機能)による高い開発効率、リリース時のネイティブコードへの事前コンパイル・GPUを活用したレンダリングエンジンによる高い実行パフォーマンスなどがあります。

本記事ではFlutterを使った簡単なアプリケーションを作成し、Flutterの基本的な開発を体験・学習することを目的とします。

インストール
Macに必要なツール/Flutterのインストール/flutter doctor/Android Studio/AVDのセットアップ
サンプル・アプリ
サンプル・アプリの起動/サンプル・アプリのソースコード
ホットリロード
自動ホットリロードの設定/ロジックのホットリロード/解説: setStateについて
新たなアプリを作る
アプリを作る準備/HTTP通信/AndroidManifest.xml/JSONのパース/画像を表示する
リリースビルド
インストール/リリース

インストール

まずはFlutterをインストールしましょう。Windows、Mac、Linux、ChromeOSで使用できます。

Flutterの公式サイト右上にある「Get started」のボタンから、公式ガイドでのインストールの手順を参照できます。公式ガイドが詳細かつ最新情報ではありますが、本記事でもMac環境でAndroid Studioを使う場合のインストール・セットアップの手順について解説します。他のOSやIDE/エディタについては公式ガイドを参照してください。

インストール画面

Macに必要なツール

Flutterは以下のコマンドラインツールを利用します。

  • bash
  • curl
  • git 2.x
  • mkdir
  • rm
  • unzip
  • which
    ※これらはMacの環境に標準で入っています。足りないものがあれば別途インストールしてください。

Flutterのインストール

macOS用ダウンロードページ の「flutter_macos_v1.7.8+hotfix.3-stable.zip」のボタンから、zipファイルをダウンロードしてください(バージョンの表記は本記事の執筆時点のものです)

MacOS install - Flutter

ダウンロードしたzipファイルを、インストールしたい場所に展開します。以降、操作はターミナルで行うものとして、コマンド例を示します。 ~/development にインストールしたい場合は以下のようになります。

mkdir ~/development
cd ~/development
unzip ~/Downloads/flutter_macos_v1.7.8+hotfix.3-stable.zip

~/development/flutter というディレクトリが作成され、その下にFlutterのファイルが展開されます。

Flutterのコマンドを簡単に利用できるようにするために、環境変数 PATH にFlutterインストール先を設定しておきましょう。

~/.bash_profile に以下の行を追加します。 [YOUR_NAME] はご自身のユーザー名に置き換えてください。また、インストール先を別の場所にした場合も、適宜パスを置き換えてください。

export PATH="$PATH:/Users/[YOUR_NAME]/development/flutter/bin"

sourceコマンドで、現在のセッションにも反映させます。

source ~/.bash_profile

flutter doctor

Flutterには環境をチェックする doctor というコマンドがあります。このコマンドを実行することで、Flutterが必要とするDart SDKなどがダウンロード・セットアップされます。以下のように実行してください。

flutter doctor

※flutterコマンドが見つからない場合は、前章での flutter/bin にパスを通す設定を見直してください。

以下のように関連するツールの状況が表示され、環境が整っている項目は「 」で表示されます。ない箇所については「 ! 」と表示されます。ここでは少なくとも先頭のFlutterの欄が「 」となっていればOKです。

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, v1.7.8+hotfix.3, on Mac OS X 10.14.4 18E226, locale ja-JP)

[✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3)
[✓] Xcode - develop for iOS and macOS (Xcode 10.2)
[✓] iOS tools - develop for iOS devices
[✓] Android Studio (version 3.3)
[✓] IntelliJ IDEA Ultimate Edition (version 2018.1)
[✓] VS Code (version 1.35.1)
[✓] Connected device (2 available)

この後のAndroid Studioをセットアップ後には、上記の欄のうちAndroid toolchain、Android Studioの欄が「 」となり、開発が進められるようになります。なお、iOSでFlutterアプリを開発したい場合は、別途Xcodeのインストールが必要です。

Android Studio

FlutterにはAndroid Studio、IntelliJ IDEA、Visual Studio Code向けの公式な開発プラグインがあります。また、プラグインを利用せずとも、Flutterのコマンドラインツールも使えるため、お好きなエディタでFlutterでアプリ開発ができます。

本記事ではAndroid Studioを使う場合の手順を紹介します。

Android Studioのインストール

まず、Android Studioの公式サイトから、Android Studioをダウンロードします。

Download Android Studio and SDK tools

Android Studioを起動し、Android Studio Setup Wizardを進めます。これによりAndroid SDK、Android SDK Platform-Tools、Android SDK Build-Toolsといった、Flutterに必要な各ツールがインストールされます。

本記事ではエミュレータを使って開発していきますが、実機デバイスを使った開発においても基本的な手順に違いはありません。

※Android Studioをすでにインストール済みの場合は、バージョンが3.1以上であることを確認してください。
※本記事の執筆時点でのAndroid Studioの最新バージョンは3.4.2ですが、画面のスクリーンショットなどは3.3で撮影しています。

Flutterプラグインのインストール

Android Studio向けのFlutterプラグインをインストールして使います。Android StudioのPreferencesからPluginsを選択し、画面下部のBrowse repositoriesを選んでください。

「flutter」で画面内を検索して表示される「Flutter」を選び、Installボタンでインストールします。

Android Studio向けのFlutterプラグイン

インストール後、Android Studioを再起動してください。再起動後、Android Studioの開始画面に「Start a new Flutter project」のメニューが表示されるようになるので、それを選択します。

Start a new Flutter project

New Flutter Projectの画面では「Flutter Application」を選び「Next」へ進みます。

New Flutter Project

Project nameにはお好きな名前を入れてください。Flutter SDK pathには、インストールしたFlutterのパスが入っています。Project locationには、このアプリを作成する場所を入力します。

New Flutter Apprication

Company domainは、アプリのリリース時にAndroidアプリのパッケージ名(iOSの場合はバンドルID)として使われます。本記事はチュートリアルのため、 example.com のまま進めていきます。

Company domain

さて、アプリの雛形が作成されると、Android Studioのエディタが起動して以下のような状態となります。

Android Studioのエディタ

AVDのセットアップ

アプリをAndroidエミュレータで実行するためには、AVD(Android Virtual Device)の設定が必要です。

Create and manage virtual devices  |  Android Developers

Android StudioのToolsから「AVD Manager」を選択し、AVDの一覧画面が表示されたら、左下の「+Create Virtual Device」を選びます。次の画面では任意のHardwareを選んで「Next」へ進みます。

AVD Manager

次に、System imageを選びます。Recommendedのタブに表示されている中から選べばOKです。現行のMacのCPUはIntelを採用しており、ABI(Application Binary Interface)がx86かx86_64のものでも問題なく高速に動作するでしょう。本稿でも、x86かx86_64を推奨します。ダウンロードが必要なものには「Download」のリンクが表示されているので、必要に応じてダウンロードしてください。

System image

Emulated Performanceの項目で「Hardware - GLES 2.0」を選択してください。

Emulated Performance

作成が完了したら、画面右の再生ボタンでエミュレータを起動します。

エミュレータ

ここまで来たら、FlutterアプリをAndroidエミュレータで実行する準備は完了です。

サンプル・アプリ

サンプル・アプリの起動

Android Studioの「Start a new Flutter project」によってFlutterのサンプル・アプリが雛形として作成されているため、まずはそのアプリを起動してみましょう。

Android Studioの上部にFlutterプラグインのツールバーがあります。「Android SDK build for x86」と表示されているのが、Androidエミュレータです。他に実機デバイスの接続があれば、プルダウンから選択できます。アプリを実行したい環境を選んでください。

「main.dart」とあるのがアプリのエントリポイントです。この時点では他にファイルがないため、そのままでOKです。その右にある再生ボタンを押してアプリを起動しましょう。

サンプルアプリの起動

※Androidの実機デバイスで開発したい場合は、端末の開発者オプションの「USBデバッグ」を有効にしてください。また、WindowsでAndroidデバイスを使う場合は、USBドライバが必要です。詳しくはユーザーガイドの「Google USB ドライバを入手する」を参照してください。

起動すると以下のようになります。なお、サンプル・アプリの初回起動時はAndroidアプリの通常のコンパイルが行われるため、数十秒から数分、時間がかかることがあります。

サンプル・アプリの起動

画面左に配置されているサンプル・アプリ画面の右下の「+」ボタンを押すと、アプリ画面中央に表示されている数字がカウントアップします。

サンプル・アプリのソースコード

サンプルとして作成されたこのアプリは、以下のコードになっています(コメント部分を除く)。

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

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

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

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>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

わずか60行ほどのコードで、アプリケーションとして動作するものが出来上がっています。

本記事ではFlutterフレームワークの詳細については触れませんが、このコードに登場する要素について、簡単に述べておきます:

  • main 関数から処理が開始され、 runApp にWidgetを渡すことでアプリが表示される
  • MyAppStatelessWidget を継承しており、 Widget である
  • MyHomePageStatefulWidget を継承しており、状態があることを意味する( _MyHomePageState がその状態)
  • WidgetStatebuild メソッドの戻り値が、画面に表示されるUIとなる
  • MaterialApp はマテリアルデザインのアプリを作るために便利なWidget
  • Scaffold は典型的な画面レイアウトを構築する便利なWidget

ホットリロード

では、Flutterの特長のひとつである、ホットリロードを試してみましょう。

アプリは起動したまま、コード内の文字列

'You have pushed the button this many times:'

の中身を、

'ボタンをこの回数も押しました'

に変更してください。

ファイルの変更を保存すると、アプリ内の文字列も即座に変更されます。

ホットリロードで文字列が即座に変更される

これは、Flutterプラグインでファイル保存する際の自動ホットリロードが、デフォルトで有効になっているためです。

自動ホットリロードの設定

Android StudioのPreferenceから「Languages & Frameworks」 > 「Flutter」を選ぶと、Flutterに関するオプションを変更できます。自動ホットリロードをオフにしたい場合は「Perform hot reload on save」のチェックを外します。

ホットリロード

自動ホットリロードがオフの状態でホットリロードする場合は、ツールバーにある稲妻の形のボタンを押します。

ロジックのホットリロード

ホットリロードできるのは文字列といった定数だけではありません。ボタンを押したときの処理を行っている _incrementCounter メソッドの中身を「2ずつ足していく」ように書き換えてみましょう。コードは以下の通りです。

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

ホットリロードして、「+」ボタンを押してみてください。数字が2ずつ増えるようになりました。

解説: setStateについて

_counter の更新は、setState というメソッドに渡した無名関数内で代入(=状態の変更)を行っています。

この setState メソッドの呼び出しによって、Flutterフレームワークに対して「状態が変わった」ことが伝わります。Flutterフレームワークは、このあとで build メソッドを呼び出すことで画面を最新の状態に更新します。

新たなアプリを作る

サンプル・アプリはここまでにして、より実践的なアプリを作りましょう。

世の中の多くのアプリにある「HTTP通信を行う」「リストビューによって多数の項目を表示する」といった機能を実装します。本記事ではGitHubのFlutterリポジトリにあるIssuesを一覧表示するアプリを作っていきます。

アプリを作る準備

まずは MyApp クラスの title の文字列を Flutter Issues に変更し、 _MyHomePageState クラスの build メソッド内を以下のように空っぽに近い状態にして、新たなアプリに備えます。

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Issues',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Issues'),
    );
  }
}
class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Text('空っぽの画面です'),
    );
  }
}
スクリーンショット
空っぽの画面のスクリーンショット
pubspec.yaml

Flutterでは、外部パッケージなどの依存関係やアプリの設定はpubspec.yamlというファイルに記述します。ファイルは雛形とともに作成されています。

pubspec.yaml

アプリの雛形を作成した時点では、pubspec.yamlは以下のようになっています(コメント部分は除く)。

name: flutter_app
description: A new Flutter application.

version: 1.0.0+1

environment:
  sdk: ">=2.1.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^0.1.2

dev_dependencies:
  flutter_test:
    sdk: flutter

flutter:
  uses-material-design: true
dependencies

このファイルの dependencies の項目に、 http を追加します。

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^0.1.2
  http: any
packages get

httpパッケージの記述を追加した後は、エディタ画面上部に表示されるFlutter commandsの「Packages get」を選んで実行してください。これにより、pubspec.yamlに記載された外部パッケージなどが取得され、使えるようになります。

pubspec.yaml

エディタ下部のコンソールに以下のように表示されれば処理は成功です。

コンソール

Tips: pub

FlutterやDartの各種パッケージは、「Dart packages」で探せます。

Dart packages

import

コードでhttpパッケージを利用するためには import が必要です。以下のようにimport文を追加してください。

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

as http とあるのは、 import したパッケージ内の名前にプレフィックスをつけて利用するためです。httpパッケージにはgetやpostといった単純な名前があり、他の識別子との衝突を避けたい場合などにこの機能を使います。

HTTP通信

HTTP通信によって、外部からデータを読み込んでみます。

FlutterリポジトリのIssuesの一覧を取得するAPIのエンドポイントにアクセスして、データを取得します。このURLをブラウザで表示するとAPIの中身を確認できます。

ソースコード

_MyHomePageState を以下のようにします。変更後、Flutterツールバーの再生ボタンを押してホットリスタートを行ってください。ホットリロードでは表示が更新されません。

class _MyHomePageState extends State<MyHomePage> {
  String _data = '';

  @override
  void initState() {
    super.initState();
    _load();
  }

  Future<void> _load() async {
    final res = await http.get('https://api.github.com/repositories/31792824/issues');
    setState(() {
      _data = res.body;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Text(_data),
    );
  }
}
スクリーンショット

ホットリスタート後、以下のように、GitHubのAPIから取得したFlutterリポジトリのIssues APIの中身が、そのまま文字列として表示されます。

FlutterリポジトリのIssues APIの中身
ホットリスタート

Flutterのホットリロードを行うと、 build メソッドが呼び出されて画面が更新されます。今回のコードの変更では新たにhttpパッケージを追加し、さらに initState メソッドで初期化処理を行うようにしましたが、表示されているWidget(State)の initState はホットリロードでは再呼び出しされません。そのため、ホットリスタートでアプリを最初から実行させることで initState (とそこから呼び出される _load メソッド)を実行させ、HTTP通信の結果を画面に表示するようにしました。

Flutterアプリを開発中に、意図した通りに更新が行われなかった場合は、この initState が再呼び出しされなかったことが原因の可能性があります。その際はホットリスタートを試してみてください。

解説

ここでの変更のポイントは以下の通りです。

class _MyHomePageState extends State<MyHomePage> {
  String _data = '';
  • 取得したデータを保持するインスタンス変数 _data を追加します。
  @override
  void initState() {
    super.initState();
    _load();
  }
  • initState メソッドを override します 。initState は、このオブジェクトが画面(Widget tree)に追加された時に呼び出され、初期化処理などを記述できます。ここでは別途実装した _load メソッドを呼び出して、HTTP通信を開始させています。
  Future<void> _load() async {
    final res = await http.get('https://api.github.com/repositories/31792824/issues');
    setState(() {
      _data = res.body;
    });
  }
  • _load メソッドは async キーワードにより、非同期メソッドになっています。 http.get (httpパッケージのget関数)は非同期でHTTP通信を行い Future を返します。その結果を await で待ち受けています。
  • 戻り値を受け取る変数 resfinal で宣言しており、これ以降は再代入できません。また型名の宣言は省略しています(Dartが推論で型を解決しています)。
  • HTTP通信のレスポンスの中身( res.body )をインスタンス変数 _data に代入しています。 setState によって状態が変わったことをFlutterに伝えます。
  • bodyのText widgetはインスタンス変数 _data の中身を表示します。

なお、async/awaitを使わずに _load メソッドを記述すると以下のようになります。 http.get が返すFutureオブジェクトに対して then メソッドでコールバック関数を渡します。処理が完了するとそのコールバック関数が呼び出されます。この方法はasync/awaitがサポートされる以前の方法であり、現時点ではasync/awaitを使う方法が推奨されています

  void _load() {
    http.get('https://api.github.com/repositories/31792824/issues').then((http.Response res) {
      setState(() {
        _data = res.body;
      });
    });
  }
Tips: _で始まる名前

Dartでは、 _ で始まる識別子は、他のファイルからアクセスすることができません。このため、外部に公開する必要のない変数やメソッドを _ から始まる名前にしています。

AndroidManifest.xml

ここで、Androidのパーミッションの設定をしておきましょう。

AndroidManifest.xml

デバッグビルドではパーミッションによるエラーは発生しませんが、リリースビルドした時に権限がないとHTTP通信が失敗します。 android/app/src/main/AndroidManifest.xml に、以下のように uses-permission を追記してください。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.flutter_app">

    <uses-permission android:name="android.permission.INTERNET" />

JSONのパース

GitHubのAPIはJSON形式です。Dartには標準パッケージでJSONの処理を行う関数が用意されており、これを利用してパースを行います。

import

Dartの convert パッケージを import してください。

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

これで、以下のように使うことができます。

final data = json.decode(res.body);
ソースコード

GitHub Issuesのタイトルを List で扱い、リスト形式で表示するよう _MyHomePageState を更新します。今回も initState で呼び出される処理が変わっているので、ホットリスタートで更新してください。

class _MyHomePageState extends State<MyHomePage> {
  List<String> _titles = <String>[];

  @override
  void initState() {
    super.initState();
    _load();
  }

  Future<void> _load() async {
    final res = await http.get('https://api.github.com/repositories/31792824/issues');
    final data = json.decode(res.body);
    setState(() {
      final issues = data as List;
      issues.forEach((dynamic element) {
        final issue = element as Map;
        _titles.add(issue['title'] as String);
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: ListView.builder(
        itemBuilder: (BuildContext context, int index) {
          if (index >= _titles.length) {
            return null;
          }

          return ListTile(
            title: Text(_titles[index]),
          );
        },
      ),
    );
  }
}
スクリーンショット

表示されたリストをスクロールしてみてください。スクロールや端に到達した際など、ネイティブアプリと変わらない挙動になっていることが分かると思います。

AndroidManifest.xml

なお、iOSで実行した場合は端でバウンスし、Androidは終端のエフェクトが表示されます。これはFlutterの内部で、スクロールの挙動がOSに応じたものになるように実装しているためです。

解説
class _MyHomePageState extends State<MyHomePage> {
  List<String> _titles = <String>[];
  • インスタンス変数を List<String> に変更し、複数のIssuesのタイトルを扱えるようにしています。
  Future<void> _load() async {
    final res = await http.get('https://api.github.com/repositories/31792824/issues');
    final data = json.decode(res.body);
    setState(() {
      final issues = data as List;
      issues.forEach((dynamic element) {
        final issue = element as Map;
        _titles.add(issue['title'] as String);
      });
    });
  }
  • json.decode した結果からIssuesのタイトルを取り出しています。API全体は List 、各要素は Map 、その中の title がタイトルです。json.decodeの戻り値は型が dynamic と定まっていない(JSONの形式は不定なため)ので、 as キーワードによって ListMap にキャストしています。
  body: ListView.builder(
    itemBuilder: (BuildContext context, int index) {
      if (index >= _titles.length) {
        return null;
      }

      return ListTile(
        title: Text(_titles[index]),
      );
    },
  ),
  • リストの表示には ListView というWidgetを使います。 ListView.builder は、 ListView のクラスの名前付きコンストラクタです。
  • itemBuilder にリストの各要素をビルドするための関数を渡しています。 ListView クラスが要素の位置を index として渡し、呼び出してくるので、その位置に応じたWidgetを返します。 null を返すとリストは終端となるので、 _titles の件数を超えたところで null を返しています。
  • ListTile は、典型的なリスト要素のレイアウトを実現してくれるWidgetです。その title にText widgetを設定しています。 ListTile だけでなく、任意のWidgetを要素に使うこともできます。

画像を表示する

タイトルだけでは寂しいので、Issueを登録した人のアバター画像も一緒に表示しましょう。

ソースコード

GitHub Issuesのタイトルとユーザーのアバター画像を List で扱えるよう、 _MyHomePageState を更新します。今回も initState で呼び出される処理が変わっているので、ホットリスタートで更新してください。

class Issue {
  Issue({
    this.title,
    this.avatarUrl,
  });

  final String title;
  final String avatarUrl;
}

class _MyHomePageState extends State<MyHomePage> {
  List<Issue> _issues = <Issue>[];

  @override
  void initState() {
    super.initState();
    _load();
  }

  Future<void> _load() async {
    final res = await http.get('https://api.github.com/repositories/31792824/issues');
    final data = json.decode(res.body);
    setState(() {
      final issues = data as List;
      issues.forEach((dynamic element) {
        final issue = element as Map;
        _issues.add(Issue(
          title: issue['title'] as String,
          avatarUrl: issue['user']['avatar_url'] as String,
        ));
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: ListView.builder(
        itemBuilder: (BuildContext context, int index) {
          if (index >= _issues.length) {
            return null;
          }

          final issue = _issues[index];
          return ListTile(
            leading: ClipOval(
              child: Image.network(issue.avatarUrl),
            ),
            title: Text(issue.title),
          );
        },
      ),
    );
  }
}
スクリーンショット
アバター
解説
class Issue {
  Issue({
    this.title,
    this.avatarUrl,
  });

  final String title;
  final String avatarUrl;
}
  • Issueごとのデータを表す Issue クラスを定義しました。
class _MyHomePageState extends State<MyHomePage> {
  List<Issue> _issues = <Issue>[];
  • インスタンス変数を List<Issue> に変更して、Issue オブジェクトのリストとしています。
  Future<void> _load() async {
    final res = await http.get('https://api.github.com/repositories/31792824/issues');
    final data = json.decode(res.body);
    setState(() {
      final issues = data as List;
      issues.forEach((dynamic element) {
        final issue = element as Map;
        _issues.add(Issue(
          title: issue['title'] as String,
          avatarUrl: issue['user']['avatar_url'] as String,
        ));
      });
    });
  }
  • APIレスポンスをパースした結果リストの各要素を、 Issue クラスのオブジェクトを生成してから _issuesadd しています。
  body: ListView.builder(
    itemBuilder: (BuildContext context, int index) {
      if (index >= _issues.length) {
        return null;
      }

      final issue = _issues[index];
      return ListTile(
        leading: ClipOval(
          child: Image.network(issue.avatarUrl),
        ),
        title: Text(issue.title),
      );
    },
  ),
  • ListTileleading にユーザーのアバター画像を追加しました。
  • Image.network で、ネットワーク上の画像を表示するImage widgetのオブジェクトを生成しています。
  • ClipOvalchild を円形にクリップして表示するwidgetで、これにより画像を丸く切り取って表示しています。

リリースビルド

さて、本記事でのアプリはこれで完成です。

インストール

実機のAndroidデバイスをお持ちの方は、リリースビルドした.apkファイルを、実機にインストールしてみましょう。 flutter install というコマンドが用意されています。

まずは、実機デバイスをUSBケーブルでマシンに接続し、 flutter devices コマンドを使ってみます。

flutter devices

以下のように接続されたデバイスが表示されます。以下の例であれば実機のデバイスIDは「LPF0000000000000」です。

HW 01K                    • LPF0000000000000 • android-arm64  • Android 8.1.0 (API 27)
Android SDK built for x86 • emulator-5554    • android-x86    • Android 8.1.0 (API 27) (emulator)
macOS                     • macOS            • darwin-x64     • Mac OS X 10.14.4 18E226
web                       • web              • web-javascript • Google Chrome 75.0.3770.142

このデバイスIDを install コマンドで以下のように指定します。デバイスIDは、ご自身のものに置き換えてください。

flutter install -d LPF0000000000000
Initializing gradle...                                              5.7s
Resolving dependencies...                                           2.1s
Installing app.apk to HW 01K...
Installing build/app/outputs/apk/app.apk...                         2.6s

実機にflutter_appというアプリがインストールされているはずです。

リリースビルドでは、開発中に表示されていた画面右上の「DEBUG」の帯がなくなっています。また、動作もより軽快になっていることが確認できると思います。

リリース

リリースビルドしたものをGoogle Play Storeに公開するのであれば、署名などの準備が必要です。詳細は以下を参照してください。

まとめ

AndroidとiOSのアプリを単一のコードベースで開発できるFlutterについて、実際のアプリを例にまとめました。この記事がFlutterを理解するためのガイドになればと思います。

Flutter関連リンク

上田哲広(うえだ・てつひろ) najeira najeira

上田哲広
マイケル株式会社CTO。携帯電話ソフトウェアの研究開発に携わったのち、2007年頃からは主にWebサービスを開発しはじめる。2013年に動画アプリ「MixChannel」を立ち上げ、開発全般を担当。2017年、マイケル株式会社に参画しCTOに就任、車好きのコミュニティ「CARTUNE」を開発。

2012年からGoogle App EngineのGoogle Developer Expertとして、2017年からはFlutterも加えて活動中。