@kokogento (ここ げんと)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

Flutter type 'List<dynamic>' is not a subtype of type 'List<Movie>'を解決したい

目的

このように映画のAPI(TMDB API)を使って、映画を検索できるようにしたいです。

また、どのように実装すれば良いのか良くわからないので、とりあえずこちらを参考にしてChangeNotifierProviderConsumerを使っています。

発生してるエラー

検索用のTextFieldに文字を入れて、検索をかけるとエラーが発生します。。。

flutter: type 'List<dynamic>' is not a subtype of type 'List<Movie>'

このように型のエラーが発生しています。。

該当のソースコード

※全てのソースは下記にあります。

検索画面

search_movie_screen.dart
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:provider/provider.dart';
import '../config.dart';
import '../widgets/movie_card.dart';
import '../providers/search_movie.dart';
import '../providers/movie.dart';

class SearchMovieScreen extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    final moviesData = Provider.of<SearchMovie>(context);
    final searchedMovies = moviesData.movies;

    return ChangeNotifierProvider<SearchMovie>(
      create: (_) => SearchMovie(),
      child: Consumer<SearchMovie>(
        builder: (context, model, child) {
          return Scaffold(
            body: Container(
                child: Column(
              children: <Widget>[
                TextField(
                  onChanged: (inputText) {
                    print(inputText);
                    model.text = inputText;
                    model.search();
                  },
                  decoration: InputDecoration(
                      prefixIcon: Icon(
                        Icons.search,
                        color: Colors.grey,
                      ),
                      hintText: 'タイトルを入れてね'),
                ),
                Expanded(
                  child: ListView.builder(
                      scrollDirection: Axis.vertical,
                      itemCount: model.movies.length,
                      itemBuilder: (BuildContext context, int index) {
                        return Container(
                          padding: EdgeInsets.all(20),
                          child: Column(
                            children: <Widget>[
                              MovieCard(
                                searchedMovies[index].title,
                                searchedMovies[index].releaseDate,
                                searchedMovies[index].overview,
                                searchedMovies[index].posterPath,
                              ),
                            ],
                          ),
                        );
                      }),
                )
              ],
            )),
          );
        },
      ),
    );
  }
}

型を決めているMovie class

movie.dart
import 'package:flutter/material.dart';

class Movie with ChangeNotifier {
  int voteCount;
  int id;
  bool video;
  num voteAverage;
  String title;
  double popularity;
  String posterPath;
  String originalLanguage;
  String originalTitle;
  String backdropPath;
  bool adult;
  String overview;
  String releaseDate;

  Movie({
    this.voteCount,
    this.id,
    this.video,
    this.voteAverage,
    this.title,
    this.popularity,
    this.posterPath,
    this.originalLanguage,
    this.originalTitle,
    this.backdropPath,
    this.adult,
    this.overview,
    this.releaseDate,
  });

// 参考にしたコードに書いてあったが、どうやって使うのかわからない。。。
  static Movie fromJson(Map<String, dynamic> json) {
    return Movie(
      voteCount: json['vote_count'],
      id: json['id'],
      video: json['video'],
      voteAverage: json['vote_average'],
      title: json['title'],
      popularity: json['popularity'],
      posterPath: json['poster_path'],
      originalLanguage: json['original_language'],
      originalTitle: json['original_title'],
      backdropPath: json['backdrop_path'],
      adult: json['adult'],
      overview: json['overview'],
      releaseDate: json['release_date'],
    );
  }
}

検索した時に値の変更を知らせるSearchMovie class

search_movie.dart
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../config.dart';
import './movie.dart';

class SearchMovie with ChangeNotifier {
  String text = '';

  List<Movie> _movies = [];

  List<Movie> get movies {
    return [..._movies];
  }

  search() async {
    if (this.text.isNotEmpty) {
      var searchUrl =
           '${MOVIE_DB_BASE_URL}/search/movie?api_key=${API_KEY}&language=en-US&query=${text}&page=1';

      try {
        final response = await http.get(searchUrl);
        final List<Movie> extractedData = json.decode(response.body)['results'];
        if (extractedData == null) {
          print('nothing to get');
          return;
        }

        _movies = extractedData;
        notifyListeners();
      } catch (error) {
        print(error);
        throw (error);
      }
    }
  }
}

把握している問題点

search_movie.dart
final List<Movie> extractedData = json.decode(response.body)['results'];

ここでjson.decodeして取得している値がList<dynamic>になっているため、List<Movie>に代入?できない。

ここを解決すると目指している実装ができるかは定かではないですが、とりあえずjson.decodeした物をどうやって別のclassの型に変換するのかがわからないです:cry:

参考情報

Movie class作成で参考にしたコード

APIのレスポンスについては

0 likes

2Answer

dynamic は特定の型と言い切れないものですよね(プログラムから見れば、JSONで定義されている構造は読み込むまでわからないので、コーディング上で特定の型に決まらない。実際には Map<String, dynamic> で返ってくる)

一方 Movie は明確に定義してあるクラスです。構造が違うので List<dynamic> をそのまま List<Movie> に入れることができないわけです。

そのため、明示的な変換が必要になってきます。
Movie クラスにある fromJson メソッドが、その変換をするロジックになります。

おそらく以下のような感じになるのではないでしょうか。
(動作確認はしていないので、参考レベルで見てください)

        final extractedData = json.decode(response.body)['results'];
        if (extractedData == null) {
          print('nothing to get');
          return;
        }

        _movies = List<Movie>.from(extractedData.map((movie) => Movie.fromJson(movie)));

Movie class作成で参考されているリポジトリにも似たようなことをやっている箇所があります。

paginated_similarmovies.dart の 12 行目になります

1Like

Comments

  1. @kokogento

    Questioner

    回答ありがとうございます!!

    `Movie`クラスにある`fromJson`メソッドで確かに変換できそうなのですが、、、
    色々と試してもやはり「型が違う」というエラーに遭遇しました。

    一度考え方を変えて`Provider`を使わない方向で、試行錯誤してみます!

全く別のやり方で実現すること自体はなんとかできました。。。

しかしProviderを活用したかったので、いずれはProviderにリプレイスしてやります!

0Like

Your answer might help someone💌