I’m working on a Flutter application where I need to fetch and display a list of movies based on user input in a search field. The TextEditingController
should trigger an API call to fetch movie data and update the ListView
with the results. However, the list remains empty even after the API call is made.
Here’s my code:
import 'package:flutter/material.dart';
// Assume these classes are defined elsewhere
import 'api_call.dart';
import 'constants.dart';
import 'movie_model.dart';
import 'search_tile.dart';
class SearchMovies extends StatefulWidget {
const SearchMovies({super.key});
@override
State<SearchMovies> createState() => _SearchMoviesState();
}
class _SearchMoviesState extends State<SearchMovies> {
String text = '';
List<MovieModel> searchMoviesList = [];
final TextEditingController _searchController = TextEditingController();
@override
void initState() {
super.initState();
_searchController.addListener(_onTextChanged);
}
@override
void dispose() {
_searchController.removeListener(_onTextChanged);
_searchController.dispose();
super.dispose();
}
void _onTextChanged() {
fetchMovies(_searchController.text);
}
Future<void> fetchMovies(String query) async {
if (query.isEmpty) {
setState(() {
searchMoviesList.clear();
});
return;
}
try {
print('Fetching movies for query: $query');
var movieResponse = await ApiCall(Constants.search + query).getData();
print('API response: $movieResponse');
setState(() {
searchMoviesList.clear();
List<dynamic> movieResults = movieResponse['results'];
for (var element in movieResults) {
searchMoviesList.add(MovieModel.fromJson(element));
}
print('Updated searchMoviesList: $searchMoviesList');
});
} catch (error) {
print('Error fetching movies: $error');
setState(() {
searchMoviesList.clear();
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Column(
children: [
TextFormField(
controller: _searchController,
onChanged: (value) {
setState(() {
text = value;
});
},
decoration: const InputDecoration(
hintText: 'Search for movies',
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.red),
),
),
),
],
),
),
body: ListView.builder(
itemCount: searchMoviesList.length,
itemBuilder: (BuildContext context, int index) {
return SearchTile(searchMoviesList: searchMoviesList, index: index);
},
),
);
}
}
I have tried debugging the fetchmovies() methods. But 'movieResponse'-used to store API result, is giving empty list. I am expecting that after every character result list to be updated. API used: static String search = 'https://api.themoviedb.org/3/search/movie?api_key=XXXXXXX&query=';
2
Answers
Double-check the structure of the API response. The movieResponse[‘results’] part assumes that the API response has a results key. Print the whole movieResponse to confirm its structure.
I do not know exact problem which causes unwanted behaviour. But I’m gonna provide you some considerations and maybe it will help resolve your issue.
But I strongly believe that these suggestions will make your code more performant and maintainable in any case.
setState
method. Since on every state update widget rebuilds, this computations can cause laggy UI and junk frames. In your particular case, it may be worth it to move everything out ofsetState
inside yourfetchMovies
method, create a new list of items (still outside) and then swap this list tosearchMoviesList
:You have
setState
inside TextFormField’sonChanged
callback, which updatestext
value of state. If you omitted usage of this value – it is OK, but if not – consider to remove this from your code. That how you avoid unnecessary rebuilds sincetext
seems not to be used anywhere.Consider using some debounce technique to delay request until user, at least partially, is done with input. This technique will delay request and even abort it (actually, not even performing it) in case user still typing his query. 300ms is common value to start, but you can play with it.
Consider a little optimisation for checking if results acquired from API are still relevant by comparing actual and last query from user input
Optionally, you can use CancellableOperation instead plain
Future
. This operation can be cancelled and you can find it useful, since it may even reduce cost of your API usage, if it’s pay-per-request basis.So, after all that optimisations I will end up with this code: