Flutter TMBD APIで映画検索（StreamBuilderで実装）

#こんな感じ
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ローディングウィジェットがずっとグルグルしてたやつ、👇これを参考につけてただけなんで、適当にTextにした<a href="https://t.co/2o8sphSWde">https://t.co/2o8sphSWde</a> <a href="https://t.co/4YiEPTCVE4">pic.twitter.com/4YiEPTCVE4</a></p>&mdash; 高卒プログラマーげんと (@gento34165638) <a href="https://twitter.com/gento34165638/status/1380496184100352007?ref_src=twsrc%5Etfw">April 9, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

#前提
・TMBD APIのAPIキーは取得済み
・こちら👇の続き

https://qiita.com/kokogento/items/d84668c415e51d5b60d3

#はじめに
この動きを再現するのはなかなかの難題でした。。。
そもそも
**「テキスト入力→Query出す→APIからデータ取得→ウィジェットに反映」**

この流れをどうやって実現できるのか？がわからず、色々と迷走しました。。。:sweat_smile:

##状態管理のProvider使う？
まず最初に試したのは、公式が推奨する状態管理のパッケージ？（名称あってる？？w）`Provider`を使うことです。
というのも、下記の記事で同じように検索の処理を行なっていますが、そちらでも`Provider`が使われていました。

https://note.com/macgyverthink/n/nff8d2ee7df71

同じように`Provider`を使おうと色々と[試行錯誤](https://qiita.com/kokogento/questions/f99c8541990a8d4c4bfd)しました。。。
結論として、「型の変換」を解決できずやり方を変えることにしました。

##普通に非同期処理でいけるんじゃね？
タイトルにもある通り、`StreamBuilder`でやってみることにしました。
元からこれでやれば良かったんですが、、

「テキスト入力＝状態の変化＝Provider」かな〜と思ってたのと、もう少し実践的にProviderを使ってみたかったという観点から、後回しにしていました:unamused:

そして`StreamBuilder`でやってみると、なんとか実現できました！

#StreamBuilderを使いAPIで映画検索
`StreamBuilder`といいつつ、実は`StreamController`を使いません。詳細な理由は下記を参照してもらうとして、**要約すると「TabBar使っててTabを切り替えるとエラーが起きるため」です。**

https://qiita.com/tetsukick/items/b51713d637b66ad84510

なので、`StreamController`ではなく`BehaviorSubject`という聴き慣れないものを使いました。
##rxdartを追加
※バージョンか各自最新をチェックしてください

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

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

##メインの検索画面

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

class SearchMovieScreen extends StatelessWidget {
  StreamController _streamController = BehaviorSubject();
  var extractedData = [];

  searchMovie(String text) async {
    if (text.isNotEmpty) {
      var searchUrl =
          '${MOVIE_DB_BASE_URL}/search/movie?api_key=${API_KEY}&language=ja-US&query=${text}&page=1';
      try {
        final response = await http.get(searchUrl);
        extractedData = json.decode(response.body)['results'];
        if (extractedData == null) {
          print('nothing to get');
          return;
        }
        _streamController.add(extractedData);
      } catch (error) {
        print(error);
        throw (error);
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        child: Column(
          children: <Widget>[
            TextField(
              onChanged: (inputText) {
                searchMovie(inputText);
              },
              decoration: InputDecoration(
                  prefixIcon: Icon(
                    Icons.search,
                    color: Colors.grey,
                  ),
                  hintText: 'タイトルを入れてね'),
            ),
            if (_streamController.stream != null)
              StreamBuilder(
                stream: _streamController.stream,
                builder: (BuildContext context, AsyncSnapshot snapshot) {
                  if (snapshot.hasError) {
                    return Center(
                      child: Text(snapshot.error),
                    );
                  }
                  if (snapshot.hasData) {
                    return Expanded(
                      child: SizedBox(
                        height: 200,
                        child: ListView.builder(
                            itemCount: snapshot.data.length ?? 0,
                            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: Text('Googleよりは検索クオリティ低いですよ'));
                },
              ),
           // 一応追加してるけど表示されたことがない。常にstreamは存在する？？？
            if (_streamController.stream == null)
              Center(
                child: Text('空っぽです'),
              )
          ],
        ),
      ),
    );
  }
}
```

##API情報を設定してるconfig.dart

```dart
const API_KEY = "aaaaaaaaaaaaaaaaa";
const MOVIE_DB_BASE_URL = "https://api.themoviedb.org/3";
```

##MovieCard
こちら👇を参考に

https://qiita.com/kokogento/items/d84668c415e51d5b60d3


#最後に
どなたか`Provider`を使ってこれを実現させてください。
そしてどんなコードになったシェアしてください。

冷凍餃子をプレゼントします。