Wie ich einen benutzerdefinierten Okhttp-Breaker durch Kotlins Coroutinen gemacht habe

Beginnen wir mit der Erklärung des Problems.

  1. Es ist notwendig, Token und Benutzer-ID in jeder Anforderung in den Headern zu senden
  2. Es ist notwendig, aus jeder Antwort ein neues Token und eine neue Benutzer-ID aus den Headern zu ziehen
  3. Empfangene Daten müssen gespeichert werden.

Die Bibliothek für die Serverinteraktion ist Retrofit. Coroutinen sind für Multithreading verantwortlich.
Die Aufgabe ist nicht schwierig. Sie müssen lediglich jeder Anforderung den Okhttp-Client-Breaker hinzufügen. Eine halbe Stunde und alles ist fertig, alles funktioniert, alle sind glücklich. Aber ich habe mich gefragt, ob es möglich ist, einen Breaker ohne einen Okhttp-Client herzustellen.

Beginnen wir mit der Lösung der Probleme in der richtigen Reihenfolge. Wenn es kein Problem beim Hinzufügen von Headern gibt (Sie müssen nur @HeaderMap in der Anforderung hinzufügen), wie erhalten Sie dann die Header, die in der Antwort enthalten sind? Sehr einfach, wir müssen unsere Antwort in die Response-Klasse einschließen, die eine headers () -Methode hat.

Hier war die Abfrageoberfläche:

@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 

Aber das ist geworden:

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

Jetzt müssen Sie für jede Anforderung den Parameter headersMap hinzufügen. Erstellen Sie eine separate RestClient-Klasse für die Abfrageshell, damit das Token und die ID nicht ständig in sharedPreferences im Presenter abgerufen werden. So stellt sich heraus:

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

Es ist zu sehen, dass wir dasselbe tun:

  1. Wir erhalten einige Parameter für die Anfrage.
  2. Fügen Sie der Anforderung Header hinzu.
  3. Rufen Sie die Methode auf.
  4. Wir erhalten neue Werte aus Headern.
  5. Wir geben das Ergebnis zurück.

Warum machen wir nicht eine Funktion für alle Anfragen? Ändern Sie dazu die Abfragen. Anstelle einzelner Variablen vom Typ @Field verwenden wir jetzt @FieldMap. Dies ist der erste Parameter für unsere Funktion - der Percher. Der zweite Parameter, den wir haben werden, ist die Anfrage selbst. Hier habe ich Kotlin DSL verwendet (ich wollte wirklich). Ich habe die Request-Klasse erstellt, in der ich die Sendefunktion zum Aufrufen der Anfrage ausgeführt habe.

So sieht die Abfrageoberfläche aus:

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

Und hier ist die Request-Klasse:

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

Jetzt sieht die RestClient-Klasse folgendermaßen aus:

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

Und schließlich nennen wir unsere Anfragen im Moderator so:

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

Hier habe ich mit Hilfe von Kotlin einen Custom Breaker gemacht.

PS Die Lösung für dieses Problem war sehr aufregend, aber leider verwendet das Projekt einen Okhttp-Breaker.

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


All Articles