I'm busy making an app that generates random movie options. The information comes from the Movie Database API, but for some reason it only shows movies from page 1. When I make the API call, in the console log it show's the call going out correctly. But in the console log it shows that the results are empty, making it revert back to page 1. I don't know if it is the JSON decoder or something else.
class RandomMovieStore: ObservableObject {
@Published var randomMovie: Movie?
@Published var genres = [Genre]()
@Published var selectedGenres = Set<Int>()
@Published var totalPages: Int = 1
static let shared = RandomMovieStore()
private let apiKey = "API_KEY"
let urlSession = URLSession.shared
let jsonDecoder = Utils.jsonDecoder
func fetchTotalPages(genres: [Int], providers: [Int], completion: @escaping (Result<Int, MovieError>) -> Void) {
let genresString = genres.map { String($0) }.joined(separator: ",")
let providersString = providers.map { String($0) }.joined(separator: ",")
guard let encodedGenres = genresString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
let encodedProviders = providersString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
completion(.failure(.invalidEndpoint))
return
}
let urlString = "https://api.themoviedb.org/3/discover/movie?api_key=\(apiKey)&include_adult=false&page=1&with_genres=\(encodedGenres)&watch_region=NL&vote_average.ite=1&with_watch_providers=\(encodedProviders)"
guard let url = URL(string: urlString) else {
completion(.failure(.invalidEndpoint))
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
do {
let apiResponse = try JSONDecoder().decode(APIResponse.self, from: data)
DispatchQueue.main.async {
self.totalPages = apiResponse.totalPages
completion(.success(apiResponse.totalPages))
print("total pages are: \(apiResponse.totalPages)")
}
} catch {
print("Error decoding API response: \(error)")
completion(.failure(.serializationError))
}
} else if let error = error {
print("Error fetching movies: \(error)")
completion(.failure(.invalidResponse))
}
}.resume()
print(urlString)
}
func discoverMovies(page: Int, genres: String, providers: String, completion: @escaping (Result<MovieResponse, MovieError>) -> ()) {
guard let url = URL(string: "https://api.themoviedb.org/3/discover/movie?api_key=\(apiKey)&include_adult=false&page=\(page)&with_genres=\(genres)&watch_region=NL&vote_average.ite=1&with_watch_providers=\(providers)") else {
completion(.failure(.invalidEndpoint))
return
}
self.loadURLAndDecode(url: url, completion: completion)
print(url)
}
func fetchMovie(id: Int, completion: @escaping (Result<Movie, MovieError>) -> ()) {
guard let url = URL(string: "https://api.themoviedb.org/3/movie/\(id)&include_adult=false&watch_region=NL") else {
completion(.failure(.invalidEndpoint))
return
}
self.loadURLAndDecode(url: url, params: [
"append_to_response": "videos,credits"
], completion: completion)
print(url)
}
private func loadURLAndDecode<D: Decodable>(url: URL, params: [String: String]? = nil, completion: @escaping (Result<D, MovieError>) -> ()) {
guard var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
completion(.failure(.invalidEndpoint))
return
}
var queryItems = [URLQueryItem(name: "api_key", value: apiKey)]
if let params = params {
queryItems.append(contentsOf: params.map { URLQueryItem(name: $0.key, value: $0.value) })
}
urlComponents.queryItems = queryItems
guard let finalURL = urlComponents.url else {
completion(.failure(.invalidEndpoint))
return
}
urlSession.dataTask(with: finalURL) { [weak self] (data, response, error) in
guard let self = self else { return }
if error != nil {
self.executeCompletionHandlerInMainThread(with: .failure(.apiError), completion: completion)
return
}
guard let httpResponse = response as? HTTPURLResponse, 200..<300 ~= httpResponse.statusCode else {
self.executeCompletionHandlerInMainThread(with: .failure(.invalidResponse), completion: completion)
return
}
guard let data = data else {
self.executeCompletionHandlerInMainThread(with: .failure(.noData), completion: completion)
return
}
do {
let decodedResponse = try self.jsonDecoder.decode(D.self, from: data)
self.executeCompletionHandlerInMainThread(with: .success(decodedResponse), completion: completion)
} catch {
self.executeCompletionHandlerInMainThread(with: .failure(.serializationError), completion: completion)
}
}.resume()
}
private func executeCompletionHandlerInMainThread<D: Decodable>(with result: Result<D, MovieError>, completion: @escaping (Result<D, MovieError>) -> ()) {
DispatchQueue.main.async {
completion(result)
}
}
}
struct APIResponse: Codable {
let totalPages: Int
enum CodingKeys: String, CodingKey {
case totalPages = "total_pages"
}
}
The function to call the API:
func performTask(numOption: Int, selectedGenresViewModel: SelectedGenresViewModel, selectedProviderViewModel: SelectedProviderViewModel, completion: @escaping ([Int], Int?) -> Void) {
let genreIDs = selectedGenresViewModel.selectedGenres.map { $0.id }
let providerIDs = Array(selectedProviderViewModel.selectedProvider.map { $0.provider_id }.shuffled().prefix(1))
RandomMovieStore.shared.fetchTotalPages(genres: genreIDs, providers: providerIDs) { result in
switch result {
case .success(let totalPages):
let pageNumbers = getRandomPageNumbers(numOption: numOption, totalPages: totalPages)
applyFiltersAndFindMovieIDs(pages: pageNumbers, genres: genreIDs, providers: providerIDs) { ids in
completion(ids, totalPages)
}
case .failure(let error):
print("Failed to fetch total pages: \(error)")
completion([], nil)
}
}
// kiest een random pagina nummer tussen 1 en de totaal pagina's
func getRandomPageNumbers(numOption: Int, totalPages: Int) -> [Int] {
let limitedTotalPages = min(totalPages, 50) // Limit totalPages to a maximum of 50
let randomNumbers = Array(1...limitedTotalPages).shuffled().prefix(numOption)
print("number of options: \(numOption)")
print(randomNumbers)
return Array(randomNumbers)
}
// Voegt filters toe aan de API call (mocht die er zijn), voegt de pagina in de api call. Bij succes kiest die 1 random film van de resultaten en pakt de film id.
func applyFiltersAndFindMovieIDs(pages: [Int], genres: [Int], providers: [Int], completion: @escaping ([Int]) -> Void) {
var movieIDs = [Int]()
let group = DispatchGroup()
for page in pages {
group.enter()
let genreIDs = genres.map(String.init).joined(separator: ",")
let providerIDs = Array(providers.shuffled().prefix(1))
RandomMovieStore.shared.discoverMovies(page: page, genres: genreIDs, providers: providerIDs.map(String.init).joined()) { result in
switch result {
case .success(let response):
print("this is page", page)
if !response.results.isEmpty { // it appears as empty!!!
print("is the results empty?", !response.results.isEmpty)
let randomIndex = Int.random(in: 1..<response.results.count)
print("this is randomIndex", randomIndex)
let randomMovie = response.results[randomIndex]
let randomMovieID = randomMovie.id
print("Random movie ID:", randomMovieID)
movieIDs.append(randomMovieID)
}
case .failure(let error):
print("API call failed with error: \(error)")
}
group.leave()
}
}
group.notify(queue: .main) {
print("All API calls completed")
completion(movieIDs)
}
}
}