👻

モバイルアプリを高速で開発したいならFlutterを使え

2024/08/30に公開

はじめに

SREホールディングス株式会社でエンジニアをしております、加藤です。
最近プライベートでモバイルアプリを開発する機会があり、Flutterを使用してみました。実際に使用してみて最初に知っておきたかったことや、こうしとけばよかった点などをFlutterの概要も併せて紹介させていただきたいと思います。

対象読者

  • モバイルアプリを開発してみたい人
  • モバイルアプリの技術選定で悩んでいる人
  • Flutter初学者

なぜFlutterを選ぶのか?

Flutterを選ぶ理由としては大きく分けて下記に示す4つあると思っています。

  • クロスプラットフォームのフレームワーク
    通常のアプリ開発の場合、OSごとに別の言語を使用して開発する必要があります。それを1つのフレームワークでまとめて開発できるのがクロスプラットフォームと呼ばれ、Flutterもそれに当たります。単純に工数が半分になると考えれば、高速化に関してこのようなフレームワークを選ぶのは必然でしょう。(※実際にはまだ発展途上な所もあるので、たまにOS毎に設定をしなくてはいけない所があります。。。)

  • 学習コストが低い
    高速でアプリケーションを開発するには最も大事な点だと思っています。Flutterで用いられている言語はDartと呼ばれる言語です。この言語はオブジェクト指向言語でJavaやC#と似ているため、そのような言語を使用したことがあれば習得は比較的容易です。
    よく同じクロスプラットフォームであるReact Nativeと比較されますが、Reactの習得難易度を考えるとFlutterに軍配が上がると思います。また余談ですが、Flutterは独自UIなためOSの違いを意識することなく同じデザインになりますが、React Nativeの場合ネイティブUIのため、OSの違いを意識する必要があるといった点もFlutterを使う理由になります。

  • 標準でマテリアルデザインが採用されている
    マテリアルデザインとはGoogleが推奨しているデザインのことで、操作性を重視しており、ユーザーが感覚的に理解することのできるデザインのことを言います。具体的なデザインに関してはGoogleのページを参照してください。
    例えば、+ボタン1つにしてもこのデザインが標準で採用されているため、特に意識することなくモダンなデザインのアプリケーションを開発することができます。影がついていてオシャレ!!!

  • ホットリロードができる
    ホットリロードとは、アプリを起動した状態でソースコードを変更した時に、その変更を即座に反映できる機能のことを言います。この機能があると、実際にUIを確認しながらコードが書けるため非常に捗ります。非常に。

アプリ設計

実際にアプリ開発をしてみて思った、高速で開発するために設計フェーズで意識しておいた方が良い点をいくつか挙げたいと思います。

なるべく既存のウィジェットを使おう

これがすごく大切です。Flutterには簡単に使えるオシャレなウィジェットが沢山あります。例えば

  • AppBar

appBar: AppBar(
    // 背景色
    backgroundColor: Colors.purple[100],
    // 左のアイコン
    leading: const Icon(Icons.arrow_back),
    //中央タイトル
    title: const Text('Test Application'),
    // 右のアイコン
    actions: const [
        Icon(Icons.account_circle, size: 40)
    ],
)
  • Card

const Card(
    // 縦に並べる
    child: Column(
        children: [
            // ListTileウィジェット
            ListTile(
                leading: Icon(Icons.access_alarm),
                title: Text("What's time?"),
                subtitle: Text("Let's work together!!"),
            )
        ]
    )
)
  • BottomNavigatonBar

    bottomNavigationBar: BottomNavigationBar(
        backgroundColor: Colors.purple[100],
        // ここでフッターのアイテムを設定
        items: const [
            // icon=表示アイコン、 label=表示文字
            BottomNavigationBarItem(icon: Icon(Icons.home), label: "ホーム"),
            BottomNavigationBarItem(icon: Icon(Icons.search), label: "検索"),
            BottomNavigationBarItem(icon: Icon(Icons.notification_add), label: "通知"),
            BottomNavigationBarItem(icon: Icon(Icons.account_circle), label: "マイページ")
        ]
    // 下部に固定する
    type: BottomNavigationBarType.fixed,
)

この3つを組み合わせたものがこんな感じで、だいぶ様になっているような気がしますね。

アプリ設計する上で、これらの既存のウィジェットを積極的に使っていくことで、素早くかつモダンなデザインのアプリケーションを作ることができます。ただその際にきちんとそのウィジェットでできること、できないことを事前に調べておくことも大切です。先ほどのCardウィジェットの中で出てきたListTileウィジェットを例にして説明します。基本的に設定できるオプションは下図のようにleading, title, subtitleの3つです。

⭕️できること
このうちleadingがない場合はtitleとsubtitleの領域が左まで拡張されます。

❌できないこと
ですが、titleがない場合だとsubtitleの領域が拡張されることはありません。

そのためtitleがない場合は中央寄せにすることができないので、注意が必要です。

このように、できることとできないことを把握する作業を怠ると、思わぬところで手戻りが発生したりするので、きちんと考えておくことが高速化の近道です。公式のWidget一覧

ウィジェット・画面構成を把握しよう

Flutterではウィジェットや全画面に対して、どこに表示するといったエリアに相当するオプションが設定されています。例えば前述したAppBarでは、左がleading、中央がtitle、右がactions、、、ListTileでは、左がleadingで右上がtitle、右下がsubtitleとなっています。全画面に対しても同様に下記のように設定されています。

この位置にこのウィジェットを当てはめれば、といった風に位置における設定を理解しておくと簡単にUIを作ることができるので、エリアについてはきちんと把握しておくと良いと思います。

基本的なウィジェットの並べ方を理解しよう

ここまで来たらあとは並べるだけです。FlutterのUIはツリー構造になっているため、親ウィジェットに対して子ウィジェットを追加していくように並べていきます。

A: children[
    B: child(D),
    C: children[E, F]
]

ソースコードを見れば分かる通り、小要素が1つの場合はchild、複数の場合はchildrenとして配列で表記します。並べるのに必要な基本的なウィジェットを4つ紹介します。

  • Container

箱のウィジェットで、幅や高さ、背景の色や形を設定することができます。

Container(
    height: 100,
    width: 100,
    // 角を丸くする設定
    decoration: BoxDecoration(
        color: Colors.purple[200],
        borderRadius: BorderRadius.circular(30)
    )
)
  • SizeBox

Containerとほぼ同じで幅と高さの設定できる箱です。Containerとの違いは幅と高さ以外の設定ができないという点にあります。使い方としては子ウィジェットを指定したサイズに納めたい時使うことが多いです。あとは、Containerよりも軽量なので、何も表示させないブロックに使用されたりします。

const SizedBox(
    width: 100,
    height: 100,
    child: Card(
        child: Text("This is Card.")
    )
)
  • Row

ウィジェットを横に並べる際に使用します。先ほどのContainerの形を変えて横に並べてみます。

child: Row(
    children: [
        Container(
            // 上下左右にマージンを設定
            margin: const EdgeInsets.all(5),
            height: 100,
            width: 100,
            decoration: BoxDecoration(
                color: Colors.purple[200],
                borderRadius: BorderRadius.circular(30)
            )
        ),
        Container(
            margin: const EdgeInsets.all(5),
            height: 50,
            width: 100,
            decoration: BoxDecoration(
                color: Colors.purple[200],
                borderRadius: BorderRadius.circular(30)
            )
        ),
        Container(
            margin: const EdgeInsets.all(5),
            height: 100,
            width: 50,
            decoration: BoxDecoration(
                color: Colors.purple[200],
                borderRadius: BorderRadius.circular(30)
            )
        )
    ]
)
  • Column

今度はウィジェットを縦に並べる際に使用します。3つ書くのは冗長なので、三項演算子とfor文を使用して書きました。

Column(
    children: [
        for(int i = 0; i < 3; i++)
            Container(
                margin: const EdgeInsets.all(5),
                height: i == 0 || i == 2 ? 100 : 50,
                width: i == 0 || i == 1 ? 100 : 50,
                decoration: BoxDecoration(
                    color: Colors.purple[200],
                    borderRadius: BorderRadius.circular(30)
                )
            ),
    ]
)

これらを組み合わせて先ほどの図を書いてみるとこのようになります。

Container(
    height: 200,
    width: 300,
    decoration: BoxDecoration(
        color: Colors.purple[200], 
        borderRadius: BorderRadius.circular(30)
    ),
    child: Row(
        // 横の並べ方を指定する
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
                Container(
                    margin: const EdgeInsets.all(5),
                    height: 100,
                    width: 120,
                    alignment: Alignment.bottomCenter,
                    decoration: BoxDecoration(
                        color: Colors.purple[300],
                        borderRadius: BorderRadius.circular(20)
                    ),
                    child: Container(
                        margin: const EdgeInsets.all(5),
                        height: 40,
                        width: 110,
                        decoration: BoxDecoration(
                            color: Colors.purple[400],
                            borderRadius: BorderRadius.circular(10)
                        ),
                    ),
                ),
                Container(
                    margin: const EdgeInsets.all(5),
                    height: 100,
                    width: 120,
                    decoration: BoxDecoration(
                        color: Colors.purple[300],
                        borderRadius: BorderRadius.circular(20)
                    ),
                    child: Row(
                        // 縦の並べ方を指定する
                        crossAxisAlignment: CrossAxisAlignment.end,
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        children: [
                            for(int i = 0; i < 2; i++)
                                Container(
                                    margin: const EdgeInsets.all(5),
                                    height: 40,
                                    width: 45,
                                    decoration: BoxDecoration(
                                        color: Colors.purple[400],
                                        borderRadius: BorderRadius.circular(10)
                                    ),
                                ),
                        ],
                    ),
                ),
        ],
    ),
)

このようにして基本ウィジェットの4つを理解していれば、複雑なUIも作ることができます。前述したListTileウィジェットについて中央揃えしたい場合は、ListTileウィジェットのようなものを作って、三項演算子でデータの有無を判定し切り替える方法でも実現できます。

その他の機能

これまでの内容を理解していれば、大体のUIは実現できます。その他にも様々な機能が用意されているので、頻出する3つを紹介させていただきます。

  • スタイルを統一する

統一感のあるレイアウトはアプリの品質を大きくあげてくれるため、文字の色やサイズ、フォントを先に定義しておくことをオススメします。そこで使用するのはThemeです。事前にThemeを設定しておき、文字等を使用する際にその設定を参照します。
【設定方法】

Theme(
    data: ThemeData(
        textTheme: const TextTheme(
          headlineLarge: TextStyle(fontSize: 50, color: Colors.black),
          headlineSmall: TextStyle(fontSize: 20, color: Colors.blueGrey)
        )
    )
)

【使用方法】

Text('テキストです', style: Theme.of(context).textTheme.headlineSmall)
  • 画面遷移をする

Navigatorを使用すれば、簡単に画面遷移も実装できます。ButtonなどのonPressedオプション(押下時の動作を設定できるオプション)に以下を記述すればOKです。

Navigator.push(context, MaterialPageRoute(builder: (context) => 【遷移したいページ】));

更にこのNavigatorには簡単に前のページに戻る機能も備わっています。

Navigator.push(context);
  • 状態を管理する

最後に紹介することになってしまいましたが、実はとても大切な内容です。Flutterのウィジェットには大きく分けると状態を持つウィジェットとそうでないウィジェットに二分されます。下右図はユーザーが+ボタンを押した回数を表示させるような画面です。この画面のcountのように、表示内容を変更しうる変数を持ったウィジェットを、状態を持つウィジェットと言います。FlutterではこれをStatefulWidgetと呼び、setStateメソッドを呼ぶことで状態を更新し、画面を再描画します。(反対に状態を持たないウィジェットをStatelessWidgetと呼びます)

ここで一つ問題なのが、StatefulWidgetは自身の持つ状態に関しては把握することはできますが、それ以外の状態を把握することはできないという点です。下図を例にするとStatfulWidgetはpriceを持っていないため、その状態(変更)を把握することは出来ません。こんなお悩みを解決してくれるのがProviderです。Providerは監視する状態(変数)を決め、状態が変化した際にその変更を指定したウィジェットに通知し、再描画することができます。このようにFlutterの状態管理方法は種類があるため、使い分けることで無駄な描画が無くサクサク動くクオリティの高いアプリケーションを作ることができます。

まとめ

ここまで読んでいただきありがとうございます。いかがだったでしょうか?何か自分でもアプリが作れる気がすると思っていただけたら幸いです。世の中の変化は目まぐるしいもので、アウトプットへの速度をより求められるような時代になってきました。特にFlutterは個人開発やスタートアップといった速度を重要するような場面では、大きな力になると信じています。ぜひ自分の思いを形にしていただければと思います。 善は急げ、思い立ったら吉日、旨い物は宵に食え。 さあアプリ開発を始めてみませんか?

SRE Holdings 株式会社

Discussion