10. 身份验证您的操作
在本节中,您将预订航班 🚀!预订航班需要向服务器进行身份验证,以确保正确的发送到太空的人!为此,由于 Apollo Kotlin 用来 OkHttp来处理 HTTP 请求,您将使用 OkHttp 拦截器向您的 GraphQL 请求添加头部。
添加拦截器
在Apollo.kt
中添加 AuthorizationInterceptor
类:
app/src/main/kotlin/com/example/rocketreserver/Apollo.kt
private class AuthorizationInterceptor() : Interceptor {override fun intercept(chain: Interceptor.Chain): Response {val request = chain.request().newBuilder().apply {TokenRepository.getToken()?.let { token ->addHeader("Authorization", token)}}.build()return chain.proceed(request)}}
此拦截器若 token 不为 null 则将 "Authorization: $token" HTTP 头部添加到请求中。
使用拦截器
创建一个自定义的 OkHttpClient
,使用此拦截器并将其传递给 ApolloClient
:
app/src/main/kotlin/com/example/rocketreserver/Apollo.kt
val apolloClient = ApolloClient.Builder().serverUrl("https://apollo-fullstack-tutorial.herokuapp.com/graphql").okHttpClient(OkHttpClient.Builder().addInterceptor(AuthorizationInterceptor()).build()).build()
添加 BookTrip 和 CancelTrip 模式
在 schema.graphqls
文件旁边添加一个 BookTrip.graphql
文件:
app/src/main/graphql/BookTrip.graphql
mutation BookTrip($id:ID!) {bookTrips(launchIds: [$id]) {successmessage}}
注意 bookTrips
字段接收一个列表作为 参数,但 mutation 自己只接收单个 变量。
另外,添加一个 CancelTrip.graphql
文件。这个 mutation 没有使用列表:
app/src/main/graphql/CancelTrip.graphql
mutation CancelTrip($id:ID!) {cancelTrip(launchId: $id) {successmessage}}
将mutations与UI连接
回到 LaunchDetails.kt
,并将 TODO
替换为执行适当的 mutation,根据 launch 是否已预订:
app/src/main/java/com/example/rocketreserver/LaunchDetails.kt
private suspend fun onBookButtonClick(launchId: String, isBooked: Boolean, navigateToLogin: () -> Unit): Boolean {if (TokenRepository.getToken() == null) {navigateToLogin()return false}val mutation = if (isBooked) {CancelTripMutation(id = launchId)} else {BookTripMutation(id = launchId)}val response = try {apolloClient.mutation(mutation).execute()} catch (e: ApolloException) {Log.w("LaunchDetails", "Failed to book/cancel trip", e)return false}if (response.hasErrors()) {Log.w("LaunchDetails", "Failed to book/cancel trip: ${response.errors?.get(0)?.message}")return false}return true}
现在回到 LaunchDetails
函数,声明一个协程作用域以便调用 onBookButtonClick
。让我们记住 isBooked
并相应地更改按钮的文本:
app/src/main/java/com/example/rocketreserver/LaunchDetails.kt
// Book buttonval scope = rememberCoroutineScope()var isBooked by remember { mutableStateOf(data.launch?.isBooked == true) }Button(modifier = Modifier.padding(top = 32.dp).fillMaxWidth(),onClick = {scope.launch {val ok = onBookButtonClick(launchId = data.launch?.id ?: "",isBooked = isBooked,navigateToLogin = navigateToLogin)if (ok) {isBooked = !isBooked}}}) {Text(text = if (!isBooked) "Book now" else "Cancel booking")}
还要添加一个加载指示器,并在 mutation 执行时防止按钮被点击:
app/src/main/java/com/example/rocketreserver/LaunchDetails.kt
// Book buttonvar loading by remember { mutableStateOf(false) }val scope = rememberCoroutineScope()var isBooked by remember { mutableStateOf(data.launch?.isBooked == true) }Button(modifier = Modifier.padding(top = 32.dp).fillMaxWidth(),enabled = !loading,onClick = {loading = truescope.launch {val ok = onBookButtonClick(launchId = data.launch?.id ?: "",isBooked = isBooked,navigateToLogin = navigateToLogin)if (ok) {isBooked = !isBooked}loading = false}}) {if (loading) {SmallLoading()} else {Text(text = if (!isBooked) "Book now" else "Cancel booking")}}
预订您的旅行!
编译并运行您的应用程序。您现在可以预订和取消您的旅行!按钮将根据旅行是否已预订而改变。
在下一节中,您将 编写您的第一个订阅并在有人预订航班时实时通知。