Beginnen wir mit der Erklärung des Problems.
- Es ist notwendig, Token und Benutzer-ID in jeder Anforderung in den Headern zu senden
- Es ist notwendig, aus jeder Antwort ein neues Token und eine neue Benutzer-ID aus den Headern zu ziehen
- 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:
- Wir erhalten einige Parameter für die Anfrage.
- Fügen Sie der Anforderung Header hinzu.
- Rufen Sie die Methode auf.
- Wir erhalten neue Werte aus Headern.
- 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 }
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.