feature image

2018年11月30日 | ブログ記事

Flutterでスマホアプリを作ってみ(た | よう)【アドベントカレンダー2018 37日目】

はじめに

最近、寒くなってきましたね。こうも寒いと、JavaやKotlin、Objective-CやSwiftを書いてモバイルアプリを作るのはしんどいのではないでしょうか。かといって、React NativeやVue Nativeは熱すぎて火傷してしまうし…
そこで白羽の矢が立つのが「Flutter」です。
Flutterでアプリが書ければこの冬は安心ですね!

実のところ、今回の記事はGitHub ActionsでGitHub Pagesをいい感じに自動でやってくれるようにする記事を書くつもりでした。発表されたあと起きてすぐベータに申請したのですが、まだ私は使えないみたいなので代わりにFlutterでアプリを書こうと悪戦苦闘していました。。

Flutter is 何

ここで私が言葉をこねくり回しているのを読むより、公式サイトを呼んだほうが手っ取り早いかも…?
雑に要約するとGoogleが作った Dartって言語でAndroidアプリもiOSアプリもいい感じに書けるやつ ってとこでしょうか。
DartはAngularDartで有名ですね。(たぶん)
似たようなものとしてVue Native、React Native、Xamarinなどがありますが、私はアルファベットの'F'が好きなのでFlutterを選択しました。

使ってみる

導入

導入は公式のガイドに従えば誰でもできるかと思います。非常に簡単でした。
公式から.zipまたは.tar.xzを落として解凍、Windowsの人は中のflutter_console.batを実行、その他の人はpathに中のbinフォルダを追加してからflutter doctorを実行して足りないものを確認して適当にダウンロードしてくるだけです。Android SDKまわりはAndroidStudioがいい感じにやってくれました。
エディタはVisual Studio Code(VSCode)かAndroidStudioが推奨されています。私はPCが非力なのでVSCodeを使いました。

世界に挨拶

お手元のVSCodeを開いてFlutterとDartの拡張機能を入れて、Ctrl(Command)+Shift+Pしてflutterと入力してやればNew Projectからプロジェクトが作成できます。

lib/main.dartを全部消して以下のように書き換えてみましょう

import 'package:flutter/material.dart'; // マテリアルデザインのウィジェットを使うよ

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

class HelloApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'HelloWorld',
        home: Scaffold(
          appBar: AppBar(
            title: Text('HelloWorld'),
          ),
          body: Center(
            child: Text(
              'Hello, world!',
              style: TextStyle(fontSize: 32.0, color: Colors.red),
            ),
          ),
        ));
  }
}

そしたらお手元のAndroid端末とPCをつなぐ、またはエミュレータを起動してエディタからデバッグを開始しましょう。
ビルドにしばらくかかるので「楽しみ~」なんて言いながらそわそわしてると…
ババーン!!!!!!
Screenshot_20181129-225808
できました。
コードはなんとなくJavaに似ているような気がします。ケツカンマが許容されるのは面白いですね。
中身については、説明しなくてもいいのでは?というくらいにそのまんまです。というか、説明すると理解してないのがバレるのでしません。
「これめっちゃマテリアルデザインじゃん、iOSもこれなの?」って思ってる人も多いでしょうが、そうです。
もちろん、iOSっぽくすることもできますよ! デバッグを終了せずに そのままmain.dartを以下のように書き換えて保存してみましょう。FlutterはHot reloadなる機能によって 自動的にコードの変更を反映してくれます 。すごーい!

import 'package:flutter/cupertino.dart';

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

class HelloApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
        title: 'HelloWorld',
        home: CupertinoPageScaffold(
          navigationBar: CupertinoNavigationBar(
            leading: Icon(CupertinoIcons.back),
            middle: Text('HelloWorld'),
            trailing: Icon(CupertinoIcons.share),
          ),
          child: Center(
            child: Text(
              'Hello, iOS!',
              style:
                  TextStyle(fontSize: 32.0, color: CupertinoColors.activeBlue),
            ),
          ),
        ));
  }
}

Screenshot_20181129-233128
こんな感じです。iOS風のデザインパーツはflutter/cupertino.dartをimportして使います。(上にある戻るボタンと共有ボタンはそれっぽいから置いただけで、意味はないです)AndroidでもiOS風に表示されるのが少し不思議ですね。
ちなみに、アップル本社はCupertino(クパチーノ)という都市にあるそうです。

これだけではつまらないので、
今度は動きのあるものを作ってみましょう!♡を赤くしたりしなかったりするアプリ(?)です!

import 'package:flutter/material.dart';

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

class HeartTapApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'HeartTap',
        home: Scaffold(
          appBar: AppBar(
            title: Text('HeartTap'),
          ),
          body: Center(
            child: Heart(),
          ),
        ));
  }
}

class Heart extends StatefulWidget {
  @override
  HeartState createState() => HeartState();
}

class HeartState extends State<Heart> {
  bool _isRed = false; // アンダーバー付きはJavaなどでいうところのprivateです

  @override
  Widget build(BuildContext context) {
    return IconButton(
      icon: Icon(
        isRed ? Icons.favorite : Icons.favorite_border,
        color: isRed ? Colors.red : Colors.grey,
      ),
      iconSize: 64.0,
      onPressed: _toggleColor, // 引数は渡せないので注意しましょう
    );
  }

  void _toggleColor() {
    setState(() => _isRed = !_isRed);
  }
}

動きがある部分はStatelessWidgetではなくStatefulWidgetState<>をそれぞれ継承した2つのクラスから作られます。
状態の変更はsetState()によって反映されますので覚えておきましょう。(忘れてて今時間を無駄に消費しました)
また、Icon()内で使われているhoge ? a : bといった記述は三項演算子というものです。名前はよく聞くのですが、使ったのは初めてでした。

作ったもの

Flutterでどんな感じにアプリを作るかは、上のHelloWorldや♡を赤くしたりしなかったりするやつで何となくわかっていただけたでしょうか?
それでは、今回私が作ったアプリとそのコードについてちょろっとだけ説明したいと思います。
こちらです。

埋め込み用 pic.twitter.com/6qpEKGNxvo

— 紙魚寿司 (@FourmiSushi) 2018年11月29日

簡単に言えばエモい画像をランダムに供給するサービスのLorem Picsumからランダムに画像を表示し、気に入ったらお気に入り登録ができるアプリです。
リポジトリはここにあるので、気になったら見てみるといいでしょう。( JSONをコードに直書きしていたり と滅茶苦茶ガバガバな部分が多いので、アドバイスがあったらどんどん言ってくれるとありがたいです)

機能一覧

あれ…思ってたよりしょぼい…

こだわったところ

画像をカード形の四角形に表示して、それに半透明の♡と撮影者名を表示するところ

これがやりたくてこのアプリを作ったともいえます。
画像をカード形に表示して、その下に影を置くの滅茶苦茶かっこよくないですか?かっこいいですよね!
Flutterでの実装はだいたいこんな感じになります。

import 'package:flutter/material.dart';

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

class EmoImageCardApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'emoi card',
      home: Scaffold(
        appBar: AppBar(
          title: Text('エモい感じで画像表示するやつ'),
        ),
        body: Padding(
          padding: EdgeInsets.only(top: 64.0, bottom: 64.0),
          child: PageView(
            controller: PageController(viewportFraction: 0.85),
            children: List.generate(5, (i) => _emoImage(i)),
          ),
        ),
      ),
    );
  }

  Padding _emoImage(page) {
    bool favorited = page % 2 == 0 ? true : false;
    return Padding(
      padding: EdgeInsets.all(16.0),
      child: Container(
        decoration: BoxDecoration(
            shape: BoxShape.rectangle,
            borderRadius: BorderRadius.circular(16.0),
            boxShadow: <BoxShadow>[
              BoxShadow(
                color: Colors.black45,
                blurRadius: 4.0,
                spreadRadius: 2.0,
                offset: Offset(0.0, 4.0),
              ),
            ],
            image: DecorationImage(
                image: NetworkImage('http://picsum.photos/800/1400'),
                fit: BoxFit.fitWidth)),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: <Widget>[
            IconButton(
              icon: Icon(
                favorited ? Icons.favorite : Icons.favorite_border,
                color: favorited ? Colors.red : Colors.white54,
              ),
              iconSize: 48.0,
              padding: EdgeInsets.all(16.0),
              onPressed: null,
            ),
            Padding(
              padding: EdgeInsets.all(16.0),
              child: Text(
                'satsueisha',
                style: TextStyle(color: Colors.white54, fontSize: 32.0),
              ),
            )
          ],
        ),
      ),
    );
  }
}

ここに書くにはちょっと長すぎましたね。少しでもHTML+CSS+JSや、モバイルアプリ開発などに触ったことのある人なら、ここに書いてあることはだいたい理解できるのではないでしょうか?
実際、私はモバイルアプリ開発もCSSもJSもほんの少ししかかじっていなかったのですが、書いてあることはなんとなくわかりました。

お気に入り機能

画像を表示するだけではただの自己満足に終わってしまうからと考えて、無理やり実用性のような何かを持たせるために実装した機能ではありますが…
外部ライブラリのshared_preferencesを使ってアプリを閉じてもお気に入りが消えないようにして、url_launcherからその作品ページに飛べるようにしました。
外部ライブラリはpubspec.yamlのdependenciesに書いてあげるとあとはエディタの拡張機能とFlutterがいい感じにダウンロードしてくれます。このあたりの仕組みもよくある感じでとっつきやすいなあと感じました。

不満に感じた点

括弧が多くなりがち(書き手に依存している?)

アプリの構造をDartで書くので、ある程度は仕方ないのでしょうか、括弧が多くなりがちでした。
普段PythonやNimしか書かないからそう感じるだけなのでしょうか…

日本語の資料が少ない

日本語の資料は少ないので、公式のドキュメント(英語)を頼りにすることになります。
英語を読むのはやっぱり少し面倒でした。
日本語の資料はあったとしても、求めている情報を得られたことはあまりありませんでした。。。

書くことがなくなってきたのでまとめにしようかなと思う

いかがだったでしょうか?
私のプログラミング能力と文章力でFlutterの魅力が伝えられたかということについては疑問が残りますが、頑張って魅力を感じ取ってほしいと思います。
何年か前にもAndroidアプリを作ろうとしたことがあるのですが、その時に比べてずいぶんと簡単に書けるようになったなあと感じました。
Flutterに限らず、アプリを簡単に作る手段が一昔前よりもだいぶ増えてきたのではないでしょうか?

この記事をきっかけに何らかの形で創作意欲を刺激された人がいたらいいなあと思います。

おわり

明日、12月1日の担当はkk_markさんとDouble_oxygeNさんです!
ところで、アドベントカレンダーって普通は12月1日からで、25日に終わるので25日目までしかないんですよね…37日目…?おかしいな…?

Fourmsushi icon
この記事を書いた人
Fourmsushi

水とか飲んでます

この記事をシェア

このエントリーをはてなブックマークに追加
共有

関連する記事

ERC20トークンを用いた宝探しゲーム(真)の提案【アドベントカレンダー2018 10日目】 feature image
2018年11月3日
ERC20トークンを用いた宝探しゲーム(真)の提案【アドベントカレンダー2018 10日目】
Azon icon Azon
2021年4月2日
DXライブラリで重力パズルゲームを作る
Macky1_2 icon Macky1_2
2024年3月29日
ClimblocK
Facish icon Facish
2018年12月23日
LogicProXでのサラウンド設定,オーケストラ用テンプレ作成,その他の小ネタ
SolunaEureka icon SolunaEureka
2018年12月16日
ICPCアジア地区横浜大会参加記【アドベントカレンダー2018 52日目】
eiya icon eiya
2022年12月12日
Unityで†リアルタイムな†AR文字認識ゲームが作れなかった話(tesseractを使用)
Hinaruhi icon Hinaruhi
記事一覧 タグ一覧 Google アナリティクスについて 特定商取引法に基づく表記