【Flutter】GitHubリポジトリを検索するサンプルアプリ作った

potatotipsのLT用にサンプルアプリを作ったので紹介です。
この記事で使っている画像は発表スライドから抜粋してます。

環境

  • Flutter: beta-1
  • Dart: 1系

アプリ

入力前 検索結果

コード

コードは https://github.com/rkowase/flutter-sample に置きました。

サンプルアプリを簡単に解説

main.dart

UI部分

サンプルアプリのコンテンツ領域(body部分)の表示要素としてはTextField, Button, ListViewなのでListViewのchildrenにそれぞれのWidgetを配置していきます。
検索ボタン押下時の処理やListViewの各要素の生成処理は後述します。

Screen Shot 2018-03-21 at 17.40.28.png

main.dart
return new Scaffold(
  appBar: new AppBar(
    title: new Text(widget.title),
  ),
  body: new Center(
    child: new ListView(
      padding: const EdgeInsets.symmetric(horizontal: 16.0),
      children: <Widget>[
        new TextField(
          decoration: const InputDecoration(
            hintText: 'Flutter',
            labelText: 'Query',
          ),
          maxLines: 1,
          controller: _controller,
        ),
        new Container(
          padding: const EdgeInsets.all(20.0),
          child: new RaisedButton(
            child: const Text('Search'),
            onPressed: _search,
          ),
        ),
        new Container(
          height: 500.0,
          child: new ListView(
            key: _listViewKey,
            itemExtent: 50.0,
            children: _createWidgets(_items),
          ),
        ),
      ],
    ),
  ),
);

処理部分

以下の_search()メソッドは検索ボタン押下時の処理で、GithubClientから取得してきた結果を_itemに入れてsetState()でUI再描画(再構築)してます。

main.dart
void _search() {
  var client = new GithubClient();
  client.get(_controller.text).then((result) {
    setState(() {
      _items = result;
    });
  });
}

以下の_createWidgets()メソッドはList Widgetの各要素であるListTileを生成しています。
アバター画像に関してはNetworkImageを使って非同期に簡単に取得できます。

Screen Shot 2018-03-21 at 17.41.24.png

main.dart
Iterable<Widget> _createWidgets(List<GithubRepo> items) {
  var ret = new List<Widget>();
  if (items == null) {
    return ret;
  }
  items.forEach((item) {
    ret.add(
        new ListTile(
          leading: new CircleAvatar(
            backgroundImage: new NetworkImage(item.avatarUrl),
          ),
          title: new Text('${item.name} / ⭐ ${item.starCount}'),
        )
    );
  });
  return ret;
}

API Client

async/awaitが使えるのでシュッと書けます。

api/github_api_client.dart
class GithubClient {
  Future<List<GithubRepo>> get(String query) async {
    var url = 'https://api.github.com/search/repositories?sort=stars&q=';
    var httpClient = new HttpClient();

    try {
      var request = await httpClient.getUrl(Uri.parse(url + query));
      var response = await request.close();

      if (response.statusCode != HttpStatus.OK) {
        return null;
      }

      var json = await response.transform(UTF8.decoder).join();
      return GithubRepo.fromJson(json);

    } catch (exception) {
      return null;
    }
  }
}

Model

今回はリポジトリ名、Star数、アバターを表示したかったので以下のようになりました。
fromJson()メソッドでGitHub APIのレスポンスをパースしてます。

model/github_repo.dart
class GithubRepo {
  var name;
  var starCount;
  var avatarUrl;

  static fromJson(json) {
    var data = JSON.decode(json);
    List<Map<String, Object>> itemList = data['items'];

    return itemList.map((item) {
      var repo = new GithubRepo();
      Map<String, Object> owner = item['owner'];
      repo.avatarUrl = owner['avatar_url'];
      repo.name = item['full_name'];
      repo.starCount = item['stargazers_count'];
      return repo;
    }).toList();
  }
}

感想

  • 環境構築は非常に簡単でAndroidStudioをすでに使っていれば数分でHello Worldを動かすことができました。
    • VSCodeでもそんな変わらないと思います。
  • Flutterはドキュメントが非常に充実してるので https://flutter.io/ を見るのが分かりやすいです。
  • 今回作ったサンプルアプリは色々調べながらですが1日くらいで出来ました。
    • Hot Reload最高
  • Androidだと一覧を表示するためにAdapterを用意しないといけなかったりなにかとやることが多いですが、Flutterだと簡単に書けてかなり楽でした。
  • 公式Widgetが充実しているので知れば知るほどできることが増えて早く開発できそうです。
  • Nativeとの連携部分はまだ試せていないのでハードウェア(カメラなど)を使うアプリとかも作ってみたいです。

補足

  • 通信中のインジケータ表示やエラー時(通信失敗など)の処理は入れていません。
  • 固定文字列(URLなど)はconstとかにしたほうがいいですがリファクタリングしてません。
  • Viewはもっとうまく作る方法がある気がします。(こういう書き方のほうがいいとかありましたら教えてください!)
    • (追記)画面内の要素を上から順に配置するために ListView を使っていますが、今回の用途だと Column を使ったほうがよかったです。
    • (追記)あとリポジトリ一覧の ListView の親要素は Container ではなく Expanded にしたほうがよかったです。

おまけ

エラー画面のカラーコード

  • Background: 7C160E
  • Text: FFFE7F Screen Shot 2018-03-21 at 17.42.49.png

Hello Worldを簡単に解説

Hello Worldという文字列を表示するだけでView再描画の必要がないのでStatelessWidgetを継承しています。
画面全体の入れ物にあたるScaffoldの中に画面上部のappBarbodyがあり、それぞれにWidgetを入れていきます。
Screen Shot 2018-03-21 at 17.39.43.png

リンク

potatotipsで「try! Flutter」という発表をしました - Rui Kowase's blog
https://rkowase.hatenablog.com/entry/2018/03/18/154816

try! Flutter // Speaker Deck
https://speakerdeck.com/rkowase/try-flutter

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.