Como fiz um disjuntor Okhttp personalizado através das corotinas de Kotlin

Vamos começar com a declaração do problema.

  1. É necessário enviar token e ID do usuário em cada solicitação no cabeçalho
  2. É necessário extrair de cada resposta um novo token e ID de usuário dos cabeçalhos
  3. Os dados recebidos devem ser salvos.

A biblioteca para interação do servidor é Retrofit. As corotinas são responsáveis ​​pelo multithreading.
A tarefa não é difícil, você só precisa adicionar o disjuntor do cliente Okhttp a cada solicitação. Meia hora e tudo está pronto, tudo funciona, todo mundo está feliz. Mas eu queria saber, é possível fazer um disjuntor sem um cliente Okhttp?

Vamos começar a resolver problemas em ordem. Se não houver nenhum problema com a adição de cabeçalho (você só precisa adicionar @HeaderMap na solicitação), como obter os cabeçalhos que vêm na resposta? Muito simples, precisamos agrupar nossa resposta na classe Response, que possui um método headers ().

Aqui estava a interface de consulta:

@FormUrlEncoded @POST("someurl/") suspend fun request1(@Field("idLast") idLastFeed: Long, @Field("autoview") autoView: Boolean, @HeaderMap headers: Map<String, String?>): Answer1 @FormUrlEncoded @POST("someurl/") suspend fun request2(@Field("ransom") ransom: Long, @HeaderMap headers: Map<String, String?>): Answer2 

Mas isso se tornou:

 @FormUrlEncoded @POST("someurl") suspend fun request1(@Field("idLast") idLastFeed: Long, @Field("autoview") autoView: Boolean, @HeaderMap headers: Map<String, String?>?): Response<Answer1> @FormUrlEncoded @POST("someurl") suspend fun request2(@Field("ransom") ransom: Long, @HeaderMap headers: Map<String, String?>?): Response<Answer2> 

Agora, para cada solicitação, você precisa adicionar o parâmetro headersMap. Vamos criar uma classe RestClient separada para o shell de consulta, para que o token e o ID não sejam retirados das sharedPreferences constantemente no apresentador. É assim que acontece:

 class RestClient(private val api: Api, private val prefs: SharedPreferences) { suspend fun request1(last: Long, autoView: Boolean): Answer1 { return api.request1(last, autoView, headers()) } suspend fun request2(id: Long): Answer2 { return api.request2(id, headers()) } private val TOKEN_KEY = "Token" private val ID_KEY = "ID" fun headers(): Map<String, String> { return mapOf( TOKEN_KEY to prefs.getString(Constants.Preferences.SP_TOKEN_KEY, ""), ID_KEY to prefs.getLong(Constants.Preferences.SP_ID, -1).toString() ) } } 

Pode-se ver que estamos fazendo a mesma coisa:

  1. Nós obtemos alguns parâmetros para a solicitação.
  2. Adicione cabeçalhos à solicitação.
  3. Chame o método
  4. Obtemos novos valores dos cabeçalhos.
  5. Retornamos o resultado.

Por que não fazemos uma função para todas as solicitações? Para fazer isso, modifique as consultas. Em vez de variáveis ​​individuais do tipo @Field, agora usaremos @FieldMap. Este será o primeiro parâmetro para nossa função - o percher. O segundo parâmetro que teremos é a própria solicitação. Aqui eu usei o Kotlin DSL (eu realmente queria). Criei a classe Request na qual fiz a função de envio para chamar a solicitação.

É assim que a interface de consulta se parece:

 @FormUrlEncoded @POST("someurl/") suspend fun feedListMap(@FieldMap map: HashMap<String, out Any>?, @HeaderMap headers: Map<String, String?>?): Response<Answer1> @FormUrlEncoded @POST("someurl/") suspend fun feedListMap(@FieldMap map: HashMap<String, out Any>?, @HeaderMap headers: Map<String, String?>?): Response<Answer2> 

E aqui está a classe Request:

 class Request<T>( var fieldHashMap: java.util.HashMap<String, out Any> = hashMapOf(), var headersHashMap: Map<String, String?>? = mapOf(), var req: suspend (HashMap<String, out Any>?, Map<String, String?>?) -> Response<T>? = { _,_ -> null} ){ fun send(): Response<T>? { return runBlocking { try { req.invoke(fieldHashMap, headersHashMap) } catch (e: Exception) { throw Exception(e.message ?: " ") } catch (t: Throwable) { throw Exception(t.message ?: " ") } } } } 

Agora a classe RestClient fica assim:

 class RestClient(private val api: Api, private val prefs: SharedPreferences) { private val TOKEN_KEY = "Token" private val ID_KEY = "ID" fun headers(): Map<String, String> { return mapOf( TOKEN_KEY to prefs.getString(Constants.Preferences.SP_TOKEN_KEY, ""), ID_KEY to prefs.getLong(Constants.Preferences.SP_ID, -1).toString() ) } fun <T> buildRequest(request: Request<T>.() -> Unit): T? { val req = Request<T>() request(req) val res = req.send() val newToken = res?.headers()?.get(TOKEN_KEY) val newID = res?.headers()?.get(ID_KEY)?.toLong() if (newToken.notNull() && newID.notNull()) { prefs.edit() .putString(TOKEN_KEY, newToken) .putLong(ID_KEY, newID) .apply() } return res?.body() } fun fiedsMapForRequest1(last: Long, autoView: Boolean) = hashMapOf("idLast" to last, "autoview" to autoView) fun fiedsMapForRequest2(ransom: Long, autoView: Boolean) = hashMapOf("ransom" to ransom) } 

E, finalmente, é assim que chamamos nossos pedidos no apresentador:

 try { val answer1 = restClient.buildRequest<Answer1> { fieldHashMap = restClient.fiedsMapForRequest1(1, false) headersHashMap = restClient.headers() req = api::request1 } val answer2 = restClient.buildRequest<Answer2> { fieldHashMap = restClient.fiedsMapForRequest2(1234) headersHashMap = restClient.headers() req = api::request2 } // do something with answer } catch (e: Exception) { viewState.showError(e.message.toString()) } finally { viewState.hideProgress() } 

Aqui fiz um disjuntor personalizado com a ajuda de Kotlin.

PS A solução para esse problema foi muito empolgante, mas, infelizmente, o projeto usa um disjuntor Okhttp.

Source: https://habr.com/ru/post/pt465781/


All Articles