Retrofit 2.x jest klientem REST dla Javy. W związku z tym, można również używać go do komunikacji z serwerem w Androidzie . Na początek należy dodać odpowiednie zależności do projektu:
dependencies {
...
// retrofit
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
...
}
Następnie trzeba stworzyć:
klasę modelu, która będzie wysyłana na serwer lub zwracana z serwera jako JSON
interfejs, w którym będą zdefiniowane operacje HTTP
obiekt typu Retrofit z podanym url do którego będą wykonywane requesty i wskazać mu interfejs z metodami HTTP
Model
W tym przykładzie posłużę się json-serverem z domyślną konfiguracją podaną w Getting Started jako serwisem do którego będę tworzył zapytania. Mój plik db.json wygląda następująco:
{
"posts": [
{ "id": 1, "title": "json-server", "author": "typicode" }
],
"comments": [
{ "id": 1, "body": "some comment", "postId": 1 }
],
"profile": { "name": "typicode" }
}
Na jego podstawię za pomocą wtyczki https://forum.android.com.pl/topic/353561-json-to-kotlin-class/ tworzę klasy w moim projekcie:
Klasa Post:
data class Post(
val author: String,
val id: Int,
val title: String
)
Klasa Comment:
data class Comment(
val body: String,
val id: Int,
val postId: Int
)
Klasa Profile:
data class Profile(
val name: String
)
Interfejs
W interfejsie definiuje metody HTTP które będą służyć do pobierania informacji z serwera:
interface RestAPI {
//pobranie wszystkich postów podanego autora
@GET("/posts")
fun getPostsByAuthor(@Query("author") author: String): Call<List<Post>>
//pobranie wszystkich komentarzy pod postem
@GET("/comments")
fun getCommentsForPost(@Query("postId") postId: Int): Call<List<Comment>>
//pobranie wszystkich profili
@GET("/profile")
fun getProfileInfo(): Call<List<Profile>>
}
Stworzenie zapytań do serwera
W poniższej Activity pokazuję w jaki sposób wykonać requesty do serwera oraz jak obsłużyć odpowiedzi
class MainActivity : AppCompatActivity() {
private lateinit var restApi: RestAPI
private var profileList = listOf<Profile>()
private var posts = listOf<Post>()
private var comments = listOf<Comment>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//gson służy do zamiany obiektów na json i jsonów na obiekty
val gson = GsonBuilder()
.setLenient()
.create()
// okHttpClient - klient HTTP z którego korzysta retrofit
val okHttpClient = OkHttpClient.Builder().build()
// stworzenie obiektu retrofit z konfiguracją
val retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl("localhost:3000")
.client(okHttpClient)
.build()
// stworzenie implementacji interfejsu RestApi
restApi = retrofit.create(RestAPI::class.java)
// Wykonywanie requestów
restApi.getProfileInfo().enqueue(ProfileInfoResponseHandler())
//uproszczenie - wiem że dla takiego autora istnieją posty
restApi.getPostsByAuthor("typicode").enqueue(PostResponseHandler())
//uproszczenie - wiem że komentarze istnieją dla postu o id 1
restApi.getCommentsForPost(1).enqueue(CommentsResponseHandler())
}
private inner class ProfileInfoResponseHandler : Callback<List<Profile>> {
override fun onFailure(call: Call<List<Profile>>, t: Throwable) {
//co zrobić gdy coś pójdzie nie tak
}
override fun onResponse(call: Call<List<Profile>>, response: Response<List<Profile>>) {
val body = response.body() ?: return
profileList = body
}
}
private inner class PostResponseHandler : Callback<List<Post>> {
override fun onFailure(call: Call<List<Post>>, t: Throwable) {
//co zrobić gdy coś pójdzie nie tak
}
override fun onResponse(call: Call<List<Post>>, response: Response<List<Post>>) {
val body = response.body() ?: return
posts = body
}
}
private inner class CommentsResponseHandler : Callback<List<Comment>> {
override fun onFailure(call: Call<List<Comment>>, t: Throwable) {
//co zrobić gdy coś pójdzie nie tak
}
override fun onResponse(call: Call<List<Comment>>, response: Response<List<Comment>>) {
val body = response.body() ?: return
comments = body
}
}
}
Każde zapytanie wykonuje się asynchronicznie. Dlatego też należy stworzyć Handlery które będą oczekiwać odpowiedzi. Tutaj mamy trzy handlery które robią w sumie to samo. Są to: ProfileInfoResponseHandler, PostResponseHandler oraz CommentsResponseHandler.
Każdy z nich implementuje dwie metody:
onFailure - wykona się gdy wystąpi jakiś błąd np. serwer zwróci kod 500,
onResponse - kiedy wszystko pójdzie zgodnie z planem i serwer zwróci odpowiedni response.