Cómo hice un interruptor Okhttp personalizado a través de las rutinas de Kotlin

Comencemos con la declaración del problema.

  1. Es necesario enviar el token y la identificación de usuario en cada solicitud en el encabezado
  2. Es necesario extraer de cada respuesta un nuevo token e identificación de usuario de los encabezados
  3. Los datos recibidos deben guardarse.

La biblioteca para la interacción del servidor es Retrofit. Las corutinas son responsables del subprocesamiento múltiple.
La tarea no es difícil, solo necesita agregar el interruptor de cliente Okhttp a cada solicitud. Media hora y todo está listo, todo funciona, todos están felices. Pero me preguntaba, ¿es posible hacer un interruptor sin un cliente Okhttp?

Comencemos a resolver problemas en orden. Si no hay ningún problema al agregar el encabezado (solo necesita agregar @HeaderMap en la solicitud), entonces, ¿cómo obtener los encabezados que vienen en la respuesta? Muy simple, necesitamos ajustar nuestra respuesta en la clase Response, que tiene un método de encabezados ().

Aquí estaba la interfaz 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 

Pero esto se ha convertido en:

 @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> 

Ahora, para cada solicitud, debe agregar el parámetro headersMap. Creemos una clase RestClient separada para el shell de consulta para que el token y la identificación no se extraigan de las preferencias compartidas constantemente en el presentador. Así es como resulta:

 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() ) } } 

Se puede ver que estamos haciendo lo mismo:

  1. Obtenemos algunos parámetros para la solicitud.
  2. Agregar encabezados a la solicitud.
  3. Llama al método.
  4. Obtenemos nuevos valores de los encabezados.
  5. Devolvemos el resultado.

¿Por qué no hacemos una función para todas las solicitudes? Para hacer esto, modifique las consultas. En lugar de variables individuales del tipo @Field, ahora usaremos @FieldMap. Este será el primer parámetro para nuestra función: el percher. El segundo parámetro que tendremos es la solicitud en sí. Aquí usé Kotlin DSL (realmente quería). Creé la clase Solicitud en la que hice la función de envío para llamar a la solicitud.

Así es como se ve la interfaz de consulta:

 @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> 

Y aquí está la clase Solicitud:

 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 ?: " ") } } } } 

Ahora la clase RestClient se ve así:

 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) } 

Y finalmente, así es como llamamos a nuestras solicitudes en el presentador:

 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() } 

Aquí hice un interruptor personalizado con la ayuda de Kotlin.

PD La solución a este problema fue muy emocionante, pero, desafortunadamente, el proyecto usa un interruptor Okhttp.

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


All Articles