FlutterでAPIをコールしてデータを表示して見た

FlutterでシンプルにAPIから取得したデータを表示してみました。
2018.10.03

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

大阪オフィスの山田です。育てている楓を一度枯らしてしまったのですが、幹から新しい芽が出てきました。今回はFlutterでシンプルにAPIから取得したデータを表示してみます。入力された文字を名前に含むGitHubリポジトリを検索して、リストにして表示するプログラムを実装してみました。ビジネスロジックを分けることもしていないので、大きなWidgetの中でベタに書いています。GitHubのAPIに関するドキュメントはこちら

開発環境

flutter doctor

[✓] Flutter (Channel master, v0.9.5-pre.12, on Mac OS X 10.13.6 17G65, locale ja-JP)
[✓] Android toolchain - develop for Android devices (Android SDK 28.0.1)
[✓] iOS toolchain - develop for iOS devices (Xcode 10.0)
[✓] Android Studio (version 3.1)
    ✗ Flutter plugin not installed; this adds Flutter specific functionality.
    ✗ Dart plugin not installed; this adds Dart specific functionality.
[✓] VS Code (version 1.27.2)
[✓] Connected device (1 available)

Android Studioは確かバージョン3.2が来ていましたね。近いうちにアップデートしよう。詳細はこちら

動作画像

iOS

Android

実装

Model

Githubのリポジトリ情報をModelにします。Named Constructorを追加して、JSONからModelを生成できるようにしています。Named Constructorについてはこちら

class GithubRepository {
  /// Repository full name.
  final String fullName;
  /// Repository description.
  final String description;
  /// Language in use.
  final String language;
  /// Repository html url.
  final String htmlUrl;
  /// Count of stars.
  final int stargazersCount;
  /// Count of watchers.
  final int watchersCount;
  /// Count of forks repository.
  final int forksCount;

  GithubRepository.fromJson(Map<String, dynamic> json) 
    : fullName = json['full_name'],
    description = json['description'],
    language = json['language'],
    htmlUrl = json['html_url'],
    stargazersCount = json['stargazers_count'],
    watchersCount = json['watchers_count'],
    forksCount = json['forks_count'];
}

入力部分

  Widget _buildInput() {
    return Container(
      margin: EdgeInsets.all(16.0),
      child: TextField(
        decoration: InputDecoration(
          prefixIcon: Icon(Icons.search),
          hintText: 'Please enter a search repository name.',
          labelText: "search"
        ),
        onChanged: (inputString) {
          if (inputString.length >= 5) {
            _searchRepositories(inputString).then((repositories) {
              setState(() {
                _repositories = repositories;
              });
            });
          }
        },
      )
    );
  }

TextFieldを置いて、onChangedイベントで入力文字数が5文字以上になったら、APIをコールするメソッドを呼んでいます。取得したGithubRepositoryの一覧を更新して、再描画しています。_repositoriesはState内に定義してある変数です。※エラー処理は入れてません。

APIコール部分

  Future<List<GithubRepository>> _searchRepositories(String searchWord) async {
    final response = await http.get('https://api.github.com/search/repositories?q=' + searchWord + '&sort=stars&order=desc');
    if (response.statusCode == 200) {
      List<GithubRepository> list = [];
      Map<String, dynamic> decoded = json.decode(response.body);
      for (var item in decoded['items']) {
        list.add(GithubRepository.fromJson(item));
      }
      return list;
    } else {
      throw Exception('Fail to search repository');
    }
  }

APIコール部分です。レスポンスが返ってきたらJSONからGithubRepositoryの配列に変換して返却しています。

リスト部分

少し長いですが、ほとんどがデザイン部分です。

  Widget _buildRepositoryList() {
    return ListView.builder(
      itemBuilder: (BuildContext context, int index) {
        final repository = _repositories[index];
        return _buildCard(repository);
      },
      itemCount: _repositories.length,
    );
  }

  Widget _buildCard(GithubRepository repository) {
    return Card(
      margin: EdgeInsets.fromLTRB(16.0, 0.0, 16.0, 16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Padding(
            padding: EdgeInsets.all(12.0), 
            child: Text(
              repository.fullName,
              style: TextStyle(
                fontWeight: FontWeight.bold, fontSize: 16.0
              ),
            ),
          ),
          repository.language != null ? Padding(
            padding: EdgeInsets.fromLTRB(12.0, 0.0, 12.0, 12.0),
            child: Text(
              repository.language,
              style: TextStyle(
                fontWeight: FontWeight.bold, fontSize: 12.0
              ),
            ),
          ) : Container(),
          repository.description != null ? Padding(
            padding: EdgeInsets.fromLTRB(12.0, 0.0, 12.0, 12.0),
            child: Text(
              repository.description,
              style: TextStyle(
                fontWeight: FontWeight.w200,
                color: Colors.grey
              )
            ),
          ) : Container(),
          Row(
            mainAxisAlignment: MainAxisAlignment.end,
            children: <Widget>[
              Icon(Icons.star),
              SizedBox(
                width: 50.0,
                child: Text(repository.stargazersCount.toString()),
              ),
              Icon(Icons.remove_red_eye),
              SizedBox(
                width: 50.0,
                child: Text(repository.watchersCount.toString()),
              ),
              Text("Fork:"),
              SizedBox(
                width: 50.0,
                child: Text(repository.forksCount.toString()),
              ),
            ],
          ),
          SizedBox(height: 16.0,)
        ],
      )
    ,);
  }

リスト部分を作成しています。State内に宣言されているGithubRepositoryの配列をCard Widgetにして表示しています。スター数などのテキストは、SizedBoxで大きさを揃えています。Forkに該当する良いアイコンが見つからなかった...

最後に

いかがだったでしょうか。今回はベタがきでシンプルに実装しました。ビジネスロジックがViewの中に紛れ込んでしまっていますね。現在、BLoCパターンを勉強中ですので、そちらのアーキテクチャパターンを使って実装し直して、ブログにしようと考えています。

参考文献