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

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

Fourmsushi

はじめに

最近、寒くなってきましたね。こうも寒いと、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や♡を赤くしたりしなかったりするやつで何となくわかっていただけたでしょうか?
それでは、今回私が作ったアプリとそのコードについてちょろっとだけ説明したいと思います。
こちらです。

簡単に言えばエモい画像をランダムに供給するサービスの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

そこの君もtypoしてみないかい…?

この記事をシェア

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

関連する記事

2018年12月19日
ゾーマからの脱出〜後編〜
ran
2018年12月18日
ゾーマからの脱出~前編~
Meffi
2018年12月17日
ワンモデ参加記
nao
2018年12月16日
2018年のクッキー職人が知っておくべきこと
spa
2018年12月16日
ICPCアジア地区横浜大会参加記【アドベントカレンダー2018 52日目】
eiya
2018年12月15日
合宿免許【アドベントカレンダー2018 52日目】
osushi

活動の紹介

カテゴリ

タグ