Flutter APIからデータを取得して表示させるまで

#こんな感じ
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Flutter TMDB APIからデータ取得ようやくできた。。。 <a href="https://t.co/reofPUVPd3">pic.twitter.com/reofPUVPd3</a></p>&mdash; 高卒プログラマーげんと (@gento34165638) <a href="https://twitter.com/gento34165638/status/1378266409529012225?ref_src=twsrc%5Etfw">April 3, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

#はじめに
Vue.js（Nuxt）で映画のAPI（[TMDB API](https://developers.themoviedb.org/3/getting-started/introduction)）を使ったサービスを作ってるのですが、アプリも欲しいのでFlutterで挑戦中です。

**Web系出身なんでFlutterでUIを作るだけでほんと一苦労です。。。**:sweat:
ウィジェットとか言う奴が、それはもうHTML/CSSとは大違いで。。

そんな感じで手探りで進めていて、ようやく「APIからデータを取得して表示させるまで」が成功したのでここに記録します。



#前提
・TMDB APIのAPIキーは取得済みであること

・ファイルの構成は下記の通り

```
main.dart
widgets
 |_movie_card.dart
screens
 |_top_screen.dart
```



#main.dartの準備

`top_screen.dart`を作成して、`main.dart`のhomeに設定します。

```dart:main.dart
import 'package:flutter/material.dart';
import './screens/top_screen.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '映画App',
      theme: ThemeData(
        primarySwatch: Colors.pink,
        backgroundColor: Colors.pink,
        accentColor: Colors.deepPurple,
        accentColorBrightness: Brightness.dark,
        textTheme: TextTheme(
          headline1: TextStyle(fontSize: 72.0, fontWeight: FontWeight.bold),
          headline6: TextStyle(fontSize: 21.0, fontWeight: FontWeight.bold),
          bodyText1: TextStyle(fontSize: 14.0, fontFamily: 'Hind'),
          bodyText2: TextStyle(fontSize: 14.0, fontFamily: 'Hind'),
        ),
        buttonTheme: ButtonTheme.of(context).copyWith(
            buttonColor: Colors.pink,
            textTheme: ButtonTextTheme.primary,
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(20),
            )),
      ),
      home: TopScreen(),
    );
  }
}
```

#APIからデータを取得
ここから本題！

##httpパッケージを追加
APIからデータを取得するために使うパッケージは色々とあるようですが、今回は公式ドキュメントに書いてあった**「http」**を使います。

https://flutter.dev/docs/cookbook/networking/fetch-data

**※最新のバージョンは下記から**

https://pub.dev/packages/http

```yaml:pubspec.yaml 
environment:
  sdk: ">=2.7.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter
  http: ^0.12.2
```

##Cardを作成
`movie_card.dart`を作成します。

![スクリーンショット 2021-04-03 18.02.59.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167220/cfdf997f-6d46-db77-ee1e-0d766f5e6aab.jpeg)

`top_screen.dart`でimportして、APIから取得したデータを受け取ります。

```dart:movie_card.dart
import 'package:flutter/material.dart';

class MovieCard extends StatelessWidget {
  final String title;
  final String releaseDate;
  final String overview;
  final String posterPath;

  MovieCard(this.title, this.releaseDate, this.overview, this.posterPath);

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Row(
        children: <Widget>[
          Image.network(
            'https://image.tmdb.org/t/p/w154/${posterPath}',
          ),
          Expanded(
            child: Container(
              padding: const EdgeInsets.all(10),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: <Widget>[
                  Container(
                    width: double.infinity,
                    margin: EdgeInsets.only(bottom: 15),
                    // height: 50,
                    child: Text(
                      title,
                      style: Theme.of(context).textTheme.headline6,
                      textAlign: TextAlign.left,
                    ),
                  ),
                  Container(
                    width: double.infinity,
                    margin: EdgeInsets.only(bottom: 15),
                    // height: 20,
                    child: Text(
                      releaseDate,
                      style: TextStyle(
                        color: Colors.grey.shade700,
                      ),
                      textAlign: TextAlign.left,
                    ),
                  ),
                  Container(
                    width: double.infinity,
                    // height: 60,
                    child: Text(
                      overview,
                      overflow: TextOverflow.ellipsis,
                      textAlign: TextAlign.left,
                    ),
                  ),
                ],
              ),
            ),
          )
        ],
      ),
    );
  }
}
```

##TOP画面を作成
`top_screen.dart`を作成します。

```dart:top_screen.dart
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../widgets/movie_card.dart';

class TopScreen extends StatelessWidget {
  static const routeName = '/top';

   getMovies() async {
    final url =
        'https://api.themoviedb.org/3/movie/now_playing?api_key=[YOUR_API_KEY]&language=en-US&page=1';
    try {
      final response = await http.get(url);
      final extractedData = json.decode(response.body)['results'];
      if (extractedData == null) {
        print('nothing to get');
        return;
      }
      return extractedData;
    } catch (error) {
      print(error);
      throw (error);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FutureBuilder(
        future: getMovies(),
        builder: (BuildContext context, AsyncSnapshot snapshot) {
          if (snapshot.hasError) {
            return Center(
              child: Text(snapshot.error),
            );
          }
          if (snapshot.hasData) {
            return ListView.builder(
                itemCount: snapshot.data.length,
                itemBuilder: (BuildContext context, int index) {
                  return Container(
                    padding: EdgeInsets.all(20),
                    child: Column(
                      children: <Widget>[
                        MovieCard(
                          snapshot.data[index]['title'],
                          snapshot.data[index]['release_date'],
                          snapshot.data[index]['overview'],
                          snapshot.data[index]['poster_path'],
                        ),
                      ],
                    ),
                  );
                });
          }
          return Center(child: CircularProgressIndicator());
        },
      ),
    );
  }
}
```

###APIキーについて
[YOUR_API_KEY]の部分はご自身のAPIキーを使ってください。

```dart
final url =
        'https://api.themoviedb.org/3/movie/now_playing?api_key=[YOUR_API_KEY]&language=en-US&page=1';
```

![スクリーンショット 2021-04-03 18.41.08.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167220/8b78c4da-c65c-e528-9855-702df2625d73.jpeg)


###なぜ「results」？

```dart
 final extractedData = json.decode(response.body)['results'];
```

![スクリーンショット 2021-04-03 18.43.16.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167220/33d96e92-da6a-b7db-0a50-be9880a42111.jpeg)

https://developers.themoviedb.org/3/movies/get-now-playing

APIのレスポンスから、欲しいデータである`title`や`overview`などは「results」という括りの中に入ってるからです。



#参考
https://www.youtube.com/watch?v=3ht6O1Ax_G0

APIキーを別ファイルに保存するやり方は下記を参考に

https://qiita.com/kokogento/items/70f00301522cf1d942f0