![見出し画像](https://assets.st-note.com/production/uploads/images/27311266/rectangle_large_type_2_759871782e22584557f8f9ab70ca5a09.png?width=800)
Apollo iOS チュートリアル (6)
今回は、ミューテーションから返された認証トークンを利用して、旅行の予約およびキャンセルを行います。
1. ApolloClient
これまで、ApolloClient はURL指定のみで利用してきました。
・Network.swift
import Apollo
import Foundation
class Network {
static let shared = Network()
private(set) lazy var apollo = ApolloClient(
url: URL(string: "http://localhost:8080/graphql")!)
}
ApolloClient は、内部で NetworkTransport を使用します。デフォルトでは、HTTPNetworkTransport を作成して、サーバとのHTTP通信を処理します。リクエストが送信される前に何か処理する必要がある場合、
HTTPNetworkTransportPreflightDelegate を使います。
2. AppClientへの認証トークンの付加
AppClientに認証トークンを付加するには、Network.swiftを次のように編集します。
・Network.swift
import Apollo
import Foundation
import KeychainSwift
class Network {
static let shared = Network()
private(set) lazy var apollo: ApolloClient = {
let httpNetworkTransport = HTTPNetworkTransport(
url: URL(string: "https://apollo-fullstack-tutorial.herokuapp.com/")!)
httpNetworkTransport.delegate = self // デリゲートの付加
return ApolloClient(networkTransport: httpNetworkTransport)
}()
}
extension Network: HTTPNetworkTransportPreflightDelegate {
// 送信すべきかどうかの取得
func networkTransport(_ networkTransport: HTTPNetworkTransport,
shouldSend request: URLRequest) -> Bool {
return true
}
// 送信前に呼ばれる
func networkTransport(_ networkTransport: HTTPNetworkTransport,
willSend request: inout URLRequest) {
// 認証トークンの付加
let keychain = KeychainSwift()
if let token = keychain.get(LoginViewController.loginKeychainKey) {
request.addValue(token, forHTTPHeaderField: "Authorization")
}
}
}
3. Alertヘルパーメソッドの追加
(1) Xcodeのメニュー「File → New → File → Swift File」で、新規Swiftファイルを作成し、UIViewController+Alert.swiftという名前を指定。
(2) UIViewController+Alert.swift を以下のように編集。
・UIViewController+Alert.swift
import UIKit
import Apollo
extension UIViewController {
// アラートの表示
func showAlert(title: String, message: String) {
let alert = UIAlertController(title: title,
message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alert, animated: true)
}
// エラーアラートの表示
func showAlertForErrors(_ errors: [GraphQLError]) {
let message = errors
.map { $0.localizedDescription }
.joined(separator: "\n")
self.showAlert(title: "GraphQL Error(s)", message: message)
}
}
4. GraphiQLでのBookTripミューテーションの確認
(1) GraphiQLで、[DOCS]タブを開き、bookTripsミューテーションを確認。
このミューテーションを使用して、一度に複数の旅行を予約して元に戻すことができます。
・ success: 予約が成功したかどうか。
・ message: ユーザーに表示する文字列。
・ launches: 現在ユーザーが予約しているランチのリスト。
(2) GraphiQLで、以下のミューテーションを入力。
・(GraphiQL)
mutation BookTrip($id:[ID]!) {
bookTrips(launchIds:$id) {
success
message
}
}
(3) 「QUERY VARIABLES」で、IDの配列を追加。
・(GraphiQL)
{"id": ["25"]}
(4) 「HTTP HEADERS」で、認証ヘッダを追加して、ログイン時に受け取ったトークンを通過させる。
・(GraphiQL)
{"Authorization": "YOUR_TOKEN"}
(5) 再生ボタンをクリック。
予約したばかりの旅行に関する情報が返されます。
【注意】
「Cannot read property 'id' of null」というエラーが表示される場合は、パススルーしたトークンに基づいてユーザーが見つからなかったことを意味します。 認証ヘッダが適切にフォーマットされていることと、実際にログインしていることを確認してください。
このミューテーションを使用すると、同時に必要な旅行をいくつでも予約できます。 ただし今回のアプリでは、一度に1つの旅行しか予約できません。単一のオブジェクトのみを取得できるように、変更します。
(1) GraphiQLで、以下のミューテーションを入力。
・(GraphiQL)
mutation BookTrip($id:ID!) {
bookTrips(launchIds:[$id]) {
success
message
}
}
(2) 「QUERY VARIABLES」で、IDを追加。
・(GraphiQL)
{"id": "25"}
(3) 再生ボタンをクリック。
先ほどと同じ結果が表示されます。
5. XcodeでのBookTripミューテーションの実装
(1) Xcodeの LaunchList.graphql と同じフォルダに新規ファイル BookTrip.graphql を生成し、先程のミューテーションを貼り付ける。
(2) アプリをビルドし、API.swift に新規ミューテーションが追加されていることを確認。
(3) DetailViewControll.swift に予約とキャンセルのメソッドを追加。
キャンセルは、まだprint()のみになります。
・DetailViewController.swift
// 旅行の予約
private func bookTrip(with id: GraphQLID) {
Network.shared.apollo.perform(mutation: BookTripMutation(id: id)) {
[weak self] result in
// selfの取得
guard let self = self else {
return
}
switch result {
// 成功時
case .success(let graphQLResult):
if let bookingResult = graphQLResult.data?.bookTrips {
if bookingResult.success {
self.showAlert(title: "Success!",
message: bookingResult.message ?? "Trip booked successfully")
} else {
self.showAlert(title: "Could not book trip",
message: bookingResult.message ?? "Unknown failure.")
}
}
// エラー表示
if let errors = graphQLResult.errors {
self.showAlertForErrors(errors)
}
// エラー時
case .failure(let error):
// エラー表示
self.showAlert(title: "Network Error",
message: error.localizedDescription)
}
}
}
// 旅行のキャンセル
private func cancelTrip(with id: GraphQLID) {
print("Cancel trip \(id)")
}
(4) bookOrCancelTapped の今までprint()のみだった部分を、予約とキャンセルのメソッドに置き換える。
・DetailViewController.swift
// 旅行の予約およびキャンセル
if launch.isBooked {
self.cancelTrip(with: launch.id)
} else {
self.bookTrip(with: launch.id)
}
(5) 実行。
これで、旅行を予約するためのコードが実装できました。
6. GraphiQLでのCancelTripミューテーションの確認
(1) GraphiQLで、[DOCS]タブを開き、cancelTripsミューテーションを確認。
・ launchId: 予約ID。
(2) GraphiQLで、以下のミューテーションを入力。
・(GraphiQL)
mutation CancelTrip($id:ID!) {
cancelTrip(launchId:$id) {
success
message
}
}
(3) 「QUERY VARIABLES」で、IDの配列を追加。
・(GraphiQL)
{"id": "25"}
(4) 「HTTP HEADERS」で、認証ヘッダを追加して、ログイン時に受け取ったトークンを通過させる。
・(GraphiQL)
{"Authorization": "YOUR_TOKEN"}
(5) 再生ボタンをクリック。
7. XcodeでのCancelTripミューテーションの実装
(1) Xcodeの LaunchList.graphql と同じフォルダに新規ファイル CancelTrip.graphql を生成し、先程のミューテーションを貼り付ける。
(2) アプリをビルドし、API.swift に新規ミューテーションが追加されていることを確認。
(3) DetailViewControll.swift のcancelTrip()を次のように編集。
・DetailViewController.swift // 旅行のキャンセル
private func cancelTrip(with id: GraphQLID) {
Network.shared.apollo.perform(mutation: CancelTripMutation(id: id)) {
[weak self] result in
// selfの取得
guard let self = self else {
return
}
switch result {
// 成功時
case .success(let graphQLResult):
// キャンセル結果の取得
if let cancelResult = graphQLResult.data?.cancelTrip {
if cancelResult.success {
if cancelResult.success {
self.showAlert(title: "Trip cancelled",
message: cancelResult.message ?? "Your trip has been officially cancelled.")
} else {
self.showAlert(title: "Could not cancel trip",
message: cancelResult.message ?? "Unknown failure.")
}
}
}
// エラー表示
if let errors = graphQLResult.errors {
self.showAlertForErrors(errors)
}
// エラー時
case .failure(let error):
// エラー表示
self.showAlert(title: "Network Error",
message: error.localizedDescription)
}
}
}
(4) 実行。
これで、旅行をキャンセルするためのコードが実装できました。
8. キャッシュポリシー
ApolloClientのfetch()は、パラメータの多くはデフォルトが提供されているため、自分で指定する必要があるのはクエリのみです。ただし、パラメータ cachePolicy は注意が必要です。
cachePolicy のデフォルト値は、returnCacheDataElseFetch です。これは、はじめに現在のキャッシュ(デフォルトではメモリ)を検索し、データが存在しない場合はネットワークから取得します。データが存在する場合、デフォルトの動作はローカルコピーを返し、不要な通信を防止します。これは望ましい動作ではない場合があります(特にミューテーションの実行後)。
使用できる cachePolicy はいくつかありますが、ネットワーク更新を強制する最も簡単な方法は、fetchIgnoringCacheData を使用することです。
loadLaunchDetails()での使用例は、次のとおりです。
private func loadLaunchDetails(forceReload: Bool = false) {
guard
let launchID = self.launchID,
(forceReload || launchID != self.launch?.id) else {
return
}
let cachePolicy: CachePolicy
if forceReload {
cachePolicy = .fetchIgnoringCacheCompletely
} else {
cachePolicy = .returnCacheDataElseFetch
}
Network.shared.apollo.fetch(query: LaunchDetailsQuery(id: launchID), cachePolicy: cachePolicy) {
[weak self] result in
<<省略>>
}
}
この記事が気に入ったらサポートをしてみませんか?