Retrofit을 사용한 통신
1. 의존성 추가
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation "com.squareup.okhttp3:logging-interceptor:4.8.1"
참고로 의존성 버전확인은 Project Structure > dependencies에서 확인할 수 있습니다.
의존성 추가 한후 sync now 해줍니다.
2. 인터넷 사용 허용
<uses-permission android:name="android.permission.INTERNET"/
3가지 구성요소
Interface
Retrofit.Builder
DTO (POJO)
4. 전체소스 소스
IRetrofit.kt : Interface에 해당
package com.exmaple.retrofit_test
import com.google.gson.JsonElement
import retrofit2.Call
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.POST
interface IRetrofit {
@FormUrlEncoded
@POST("/login")
fun loginRequest(
@Field("username") username:String,
@Field("password") password:String
): Call<JsonElement>
}
RetrofitClient.kt Retrofit.Builder에 해당
package com.exmaple.retrofit_test
import android.util.Log
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit
object RetrofitClient {
private var retrofitClient:Retrofit? = null
private val TAG = "로그"
fun getClient(baseUrl:String) : Retrofit?{
if(retrofitClient == null){
val client = OkHttpClient.Builder()
val loggingInterceptor =HttpLoggingInterceptor(object:HttpLoggingInterceptor.Logger{
override fun log(message: String) {
Log.d(TAG,"RetrofitClient - log : ${message}")
}
})
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
client.addInterceptor(loggingInterceptor)
client.connectTimeout(10, TimeUnit.SECONDS)
client.readTimeout(10, TimeUnit.SECONDS)
client.writeTimeout(10, TimeUnit.SECONDS)
client.retryOnConnectionFailure(true)
retrofitClient =Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.client(client.build())
.build()
}
return retrofitClient;
}
}
RetrofitManager.kt
package com.exmaple.retrofit_test
import android.util.Log
import com.google.gson.JsonElement
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class RetrofitManager {
private val baseUrl = "http://192.168.21.126:5000"
private val TAG = "로그"
companion object{
val instance = RetrofitManager()
}
private val iRetrofit: IRetrofit? = RetrofitClient.getClient(baseUrl)?.create(IRetrofit::class.java)
fun loging(username:String, password:String, completion:(LoginResponse, String)->Unit){
var call = iRetrofit?.loginRequest(username, password) ?:return
call.enqueue(object: Callback<JsonElement> {
override fun onResponse(call: Call<JsonElement>, response: Response<JsonElement>) {
Log.d(TAG, "RetrofitManager - onResponse body : ${response.body()}")
Log.d(TAG, "RetrofitManager - onResponse code : ${response.code()}")
if(response.code() == 200){
completion(LoginResponse.OK, response.body().toString())
}else{
completion(LoginResponse.FAIL, response.body().toString())
}
}
override fun onFailure(call: Call<JsonElement>, t: Throwable) {
Log.d(TAG, "RetrofitManager - onFailure")
completion(LoginResponse.FAIL, t.toString())
}
})
}
}
MainActivity.kt
package com.exmaple.retrofit_test
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import android.os.Bundle
import android.os.PersistableBundle
import android.util.Log
import androidx.appcompat.app.AlertDialog
import com.exmaple.retrofit_test.databinding.ActivityMainBinding
class MainActivity:AppCompatActivity(){
private lateinit var binding:ActivityMainBinding
private val TAG = "로그"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.user = User("pepper","123123")
binding.loginButton.setOnClickListener{
Log.d(TAG,"MainActivity login button click");
var username = binding.usernameInput.text.toString()
var password = binding.passwordInput.text.toString()
loginRequest(username, password)
}
}
private fun loginRequest(username: String, password: String) {
var dialogBuilder =AlertDialog.Builder(this@MainActivity)
if(username.isEmpty() || password.isEmpty()){
dialogBuilder.setTitle("알림")
dialogBuilder.setMessage("빈 칸을 전부 채워주세요")
dialogBuilder.setPositiveButton("확인",null)
dialogBuilder.show()
}else{
RetrofitManager.instance.loging(
username = username,
password = password,
completion ={loginResponse, response ->
when(loginResponse){
LoginResponse.FAIL->{
dialogBuilder.setTitle("알림")
dialogBuilder.setMessage("로그인 실패")
dialogBuilder.setPositiveButton("확인",null)
dialogBuilder.show()
}
LoginResponse.OK->{
dialogBuilder.setTitle("알림")
dialogBuilder.setMessage("로그인 성공")
dialogBuilder.setPositiveButton("확인",null)
dialogBuilder.show()
}
}
}
)
}
}
}
data class User(val name:String, val pass:String);
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<import type="android.view.View"/>
<variable
name="user"
type="com.exmaple.retrofit_test.User" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<EditText
android:id="@+id/username_input"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_marginTop="200dp"
android:text="@{user.name}"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/password_input"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:text="@{user.pass}"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/username_input" />
<Button
android:id="@+id/login_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="로그인"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/password_input" />
<Button
android:id="@+id/test_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="테스트"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/login_button" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
***
val name: String = nm ?: "" //nm라는 변수가null 이면 name에 ""가 들어감
object
싱글턴 클래스로 만들 때
익명 클래스 객체를 생성할 때
*** 참고 Retrofit API Declaration
API Declaration
Annotations on the interface methods and its parameters indicate how a request will be handled.
REQUEST METHOD
Every method must have an HTTP annotation that provides the request method and relative URL. There are eight built-in annotations: HTTP, GET, POST, PUT, PATCH, DELETE, OPTIONS and HEAD. The relative URL of the resource is specified in the annotation.
@GET("users/list")
You can also specify query parameters in the URL.
@GET("users/list?sort=desc")
URL MANIPULATION
A request URL can be updated dynamically using replacement blocks and parameters on the method. A replacement block is an alphanumeric string surrounded by { and }. A corresponding parameter must be annotated with @Path using the same string.
@GET("group/{id}/users")
Call<List<User>> groupList(@Path("id") int groupId);
Query parameters can also be added.
@GET("group/{id}/users")
Call<List<User>> groupList(@Path("id") int groupId, @Query("sort") String sort);
For complex query parameter combinations a Map can be used.
@GET("group/{id}/users")
Call<List<User>> groupList(@Path("id") int groupId, @QueryMap Map<String, String> options);
REQUEST BODY
An object can be specified for use as an HTTP request body with the @Body annotation.
@POST("users/new")
Call<User> createUser(@Body User user);
The object will also be converted using a converter specified on the Retrofit instance. If no converter is added, only RequestBody can be used.
FORM ENCODED AND MULTIPART
Methods can also be declared to send form-encoded and multipart data.
Form-encoded data is sent when @FormUrlEncoded is present on the method. Each key-value pair is annotated with @Field containing the name and the object providing the value.
@FormUrlEncoded
@POST("user/edit")
Call<User> updateUser(@Field("first_name") String first, @Field("last_name") String last);
Multipart requests are used when @Multipart is present on the method. Parts are declared using the @Part annotation.
@Multipart
@PUT("user/photo")
Call<User> updateUser(@Part("photo") RequestBody photo, @Part("description") RequestBody description);
Multipart parts use one of Retrofit's converters or they can implement RequestBody to handle their own serialization.
HEADER MANIPULATION
You can set static headers for a method using the @Headers annotation.
@Headers("Cache-Control: max-age=640000")
@GET("widget/list")
Call<List<Widget>> widgetList();
@Headers({
"Accept: application/vnd.github.v3.full+json",
"User-Agent: Retrofit-Sample-App"
})
@GET("users/{username}")
Call<User> getUser(@Path("username") String username);
Note that headers do not overwrite each other. All headers with the same name will be included in the request.
A request Header can be updated dynamically using the @Header annotation. A corresponding parameter must be provided to the @Header. If the value is null, the header will be omitted. Otherwise, toString will be called on the value, and the result used.
@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)
Similar to query parameters, for complex header combinations, a Map can be used.
@GET("user")
Call<User> getUser(@HeaderMap Map<String, String> headers)
He