我使用firebase id令牌作为http请求的授权头。要从android客户端获得这个令牌需要一个方法:user.getIdToken(true)
,这是一个基于异步函数的回调。我还有一个柄模块,它提供了一个改装服务(接口)的实例:
服务接口如下:
interface CountriesService {
@GET("countries")
suspend fun getCountries(): Response<CountriesResponse>
}
下面是提供此服务的Hilt模块:
@Module
@InstallIn(SingletonComponent::class)
object CountriesModule {
private const val CLOUD_RUN_BASE_URL = "https://my-base-url/"
@Provides
fun provideCountriesService(): CountriesService {
return Retrofit.Builder().apply {
baseUrl(CLOUD_RUN_BASE_URL)
addConverterFactory(GsonConverterFactory.create())
}.build().create(CountriesService::class.java)
}
}
现在我需要添加一个OkHttp检测器(简单地将授权头添加到所有请求)到上述模块,如:拦截器:
var client: OkHttpClient = OkHttpClient.Builder().addInterceptor { chain ->
val user = FirebaseAuth.getInstance().currentUser
user?.getIdToken(true)?.addOnCompleteListener { task ->
if(task.isSuccessful){
val token = task.result.token
val newRequest: Request = chain.request().newBuilder()
.addHeader("Authorization", "Bearer $token")
.build()
chain.proceed(newRequest) // return not allowed here
} else{
Log.d("id_token", task.exception?.message.toString())
}
}
}.build()
然后将client
值作为参数传递给下面模块中的client()
方法:
@Module
@InstallIn(SingletonComponent::class)
object CountriesModule {
private const val CLOUD_RUN_BASE_URL = "https://my-base-url/"
@Provides
fun provideCountriesService(): CountriesService {
return Retrofit.Builder().apply {
client(client) // Newly added line
baseUrl(CLOUD_RUN_BASE_URL)
addConverterFactory(GsonConverterFactory.create())
}.build().create(CountriesService::class.java)
}
}.
拦截器代码的问题是不能从addOnCompleteListener{}
块返回值chain.proceed(newRequest)
。
有更好的方法来实现这一点吗?如何使一个基于回调的方法返回一个稍后被另一个方法使用的值?
您必须重新考虑初始化并初始化HTTP客户端,并将依赖于它的所有代码移到应用程序的后面阶段。在Android上,只有一组有限的事情可以在初始化回调中同步进行。
通常情况下,应用程序在初始化阶段启动时只显示一个启动屏幕,此时需要网络。
您需要重新考虑如何进行调用,特别是调用的顺序。可能在拦截器中应该使用缓存的令牌,但在此之前,需要使令牌失效。这将导致你的拦截器总是使用最新的令牌。
或者,你可以坚持firebaseTask
提供结果阻塞,你的HTTP调用总是在后台线程,但我不建议。