I'm using The MovieDatabase API to get movie results. To get a random movie I have a few functions: 1) The task to fetch the total number of pages depending on the filters.
func performTask(numOption: Int, selectedGenresViewModel: SelectedGenresViewModel, selectedProviderViewModel: SelectedProviderViewModel, completion: @escaping ([Int]) -> Void) {
let genreIDs = selectedGenresViewModel.selectedGenres.map { $0.id }
let providerIDs = selectedProviderViewModel.selectedProvider.map { $0.provider_id }
RandomMovieStore.shared.discoverMovies(page: 1, genres: genreIDs.map(String.init).joined(separator: ","), providers: providerIDs.map(String.init).joined(separator: ",")) { 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)
print("total pages are: \(totalPages)")
}
case .failure(let error):
print("API call failed with error: \(error)")
completion([])
}
}
It generates a random page number between 1 and the fetched total page number
func getRandomPageNumbers(numOption: Int, totalPages: Int) -> [Int] { let randomNumbers = Array(1...totalPages).shuffled().prefix(numOption) return Array(randomNumbers) }
It applies everything to the API call
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 = providers.map(String.init).joined(separator: ",") RandomMovieStore.shared.discoverMovies(page: page, genres: genreIDs, providers: providerIDs) { result in switch result { case .success(let (response, _)): if let randomMovie = response.results?.randomElement() { 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) } }
Then we have the button that activates the functions
Button(action: {
Task {
performTask(
numOption: numOption,
selectedGenresViewModel: selectedGenresViewModel,
selectedProviderViewModel: selectedProviderViewModel
) { movieIDs in
var fetchedMovies = [Movie]()
let dispatchGroup = DispatchGroup()
// plaatst de id van de films om de infromatie eruit te halen
for id in movieIDs {
dispatchGroup.enter()
print("Fetching movie with ID: \(id)")
MovieStore.shared.fetchMovie(id: id) { result in
switch result {
case .success(let movie):
fetchedMovies.append(movie)
case .failure(let error):
print(error.localizedDescription)
}
dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: .main) {
self.filteredMovies = fetchedMovies
self.movieTitles = fetchedMovies.map({ $0.title })
print(self.movieTitles)
}
}
}
})
Also here is the API call
func discoverMovies(page: Int, genres: String, providers: String, completion: @escaping (Result<(RandomMovieResponse, Int?), 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) { [weak self] (result: Result<RandomMovieResponse, MovieError>) in
guard let self = self else { return }
switch result {
case .success(let response):
self.totalPages = response.total_pages
completion(.success((response, response.total_pages)))
case .failure(let error):
completion(.failure(error))
}
}
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 RandomMovieResponse: Decodable {
let page: Int?
let total_results: Int?
let total_pages: Int?
let results: [Movie]?
}
For some reason it it not calling the total pages. I'm completely at a los.
I figured it is not possible to fetch the movie ID's and the total_pages in one API call, so I split the two. First it fetches the total number of pages (in the fetchTotalPages func), then it makes a second call (in the discoverMovies func) with a random generated number within the total page number.
But for some reason it keeps failing to fetch the total number of pages. Am I missing something?
Here is the API call
Here is the button action
And finally the func to activate the API call