Refactoring the existing code

The existing code compiles, but it's not optimal yet. The MovieDBHelper doesn't pass the movie's remote ID to its callback, and the movie insertion code doesn't use this remote ID yet. When the user wants to save a new movie, the app still defaults to creating a new movie even though we just wrote our helper method to either find or create a movie to avoid data duplication. We should update our code so the callback is called with the fetched remote ID.

Let's update the MovieDBHelper first. Replace the following lines in the fetchRating(forMovie:callback:) method; changes are highlighted:

typealias MovieDBCallback = (Int?, Double?) -> Void 
let apiKey = "YOUR_API_KEY_HERE"

func fetchRating(forMovie movie: String, callback: MovieDBCallback) {
guard let searchUrl = url(forMovie: movie) else {
callback(nil, nil)
return
}

let task = URLSession.shared().dataTask(with: searchUrl) { data, response, error in
var rating: Double? = nil
var remoteId: Int? = nil

defer {
callback(remoteId, rating)
}

let decoder = JSONDecoder()

guard error == nil, let data = data,
let lookupResponse = try? decoder.decode(MovieDBLookupResponse.self, from: data),
let movie = lookupResponse.results.first
else { return }

rating = movie.popularity
remoteId = movie.id
}

task.resume()
}

These updates change the callback handler so it takes both the remote ID and the rating as parameters. We also add a variable to hold the remote ID, and we incorporate this variable into the callback. With this code, the MovieDBHelper is fully up to date.

You should also update the response struct so the MovieDBMovie struct includes the ID from the API response:

struct MovieDBMovie: Codable {
let popularity: Double?
let id: Int?
}

Let's update the movie creation code to wrap up the refactoring step. Update the following lines in the MoviesViewController's saveMovie(withName:) method; changes are once again highlighted:

moc.persist { 
let movie = Movie.find(byName: name, orCreateIn: moc)
if movie.name == nil || movie.name?.isEmpty == true {
movie.name = name
}

let newFavorites: Set<AnyHashable> = familyMember.favoriteMovies?.adding(movie) ?? [movie]
familyMember.favoriteMovies = NSSet(set: newFavorites)

let helper = MovieDBHelper()
helper.fetchRating(forMovie: name) { remoteId, rating in
guard let rating = rating,
let remoteId = remoteId
else { return }

moc.persist {
movie.popularity = rating
movie.remoteId = Int64(remoteId)
}
}
}

First, the preceding code either fetches an existing movie or creates a new one with the find(byName:orCreateIn:) method we just created. Next, it checks whether or not the returned movie already has a name. If it doesn't have a name yet, we will set it. Also, if it does have a name, we can safely assume we were handed an existing movie object so we don't need to set the name. Next, the rating and ID are fetched and we set the corresponding properties on the movie object to the correct values in the callback.

This is all the code we needed to refactor to prepare our app for background fetch. Let's implement this feature now.