안드로이드에서 기본적으로 데이터 처리를 위해선
이 두가지를 사용한다
또한 여기서 remote DataSource에 해당되는 model은 주로 Retorifit2
를 사용하는데 이는 Http Api를 java interface로 가져와 안드로이드에서 데이터를 요청할 수 있는 구조를 지원한다
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
인터넷 사용 허가를 추가해주고
만약 불러오는 api의 주소가 https가 아닌 http라면
<application
...
android:usesCleartextTraffic="true"
..
</application>
http는 보안 문제로 인해 모든 통신을 허가해준다는 permission을 줘야 한다
dependencies {
//retrofit2
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.google.code.gson:gson:2.8.2'
}
여기서 Gson retrofit2에서 사용하는데 왜 implementation해주냐면 데이터 표준표현인 Json을 java에서 파싱하고 생성하기 위함이다.
좀더 파고들자면 직렬화 역질렬화 개념이 나오는데
Http 통신에선 Request, Response로 원하는 데이터의 요청과 응답을 받게 해준다. 그러나 이 두 과정을 하기 위해선 중간 통신 과정이 필요하다.
http 통신이란?
이 글을 참고하며 Android에서 어떻게 해야할지 알아보자.
-DTO-
Dto란?: Data Transfer Object를 의미하는데 이는 의미 그대로 데이터를 객체화 해주는 것이다!
객체화의 이유는: 데이터를 객체화 시키지 않으면 통신 횟수의 증가, 로직의 비효율성때문 https://kafcamus.tistory.com/13 참고해보면 좋을 것이다
1.Requset구조 짜기 ** Http Header에서 요구하는 값을 넣을 data Class**
data class LoginRequest(
@SerializedName("id") val id : String,
@SerializedName("password") val password : String,
)
1-1.Response 구조짜기 ** Http Body에서 보내준 값을 넣을 받을 dataClass**
data class GameResultData(
@SerializedName("result") val result: List<GameResult>? = null
)
data class GameResult(
@SerializedName("name") val title: String,
@SerializedName("age") val kind: String,
@SerializedName("gender") val category: String
)
-> 내가 받고자하는 Json은 {result : {name:" " , age: " " , gender:" " }} 로 돼 있어서 반환값을 준 것
2.통신 service 구조짜기
interface RequestDto {
companion object {
const val CONTENT_TYPE = "content-type: application/json"
const val LOGIN_API = "공통url뒤의 path부분"
}
@Headers(CONTENT_TYPE)
@POST(LOGIN_API)
suspend fun getLogin(@Body data: LoginRequest): Response<LoginResultData>
}
@GET @POST 말고도 정보를 수정하거나 삭제하는 요청 방식이 있는데, http method(POST, GET, PUT, PATCH, DELETE)를 사용하여 서버에 원하는 작업을 요청할 수 있다.
여기서 주목할 GET방식과 POST 방식은 데이터받는 과정이 다르다는 점
이후 데이터를 Response or Call방식으로 activity에서 .enqueue,execute() 방식으로 쓸지 body()로 쓸지, 구조의 차이로 나아가기에 잘 구분하여 사용해야한다
3. http통신 클라이언트짜기
class Http {
companion object {
private val _LOGIN = "http://testapi.testtest.co.kr"
val clientBuilder = OkHttpClient.Builder()
val loggingInterceptor = HttpLoggingInterceptor()
val retrofit = Retrofit.Builder()
.baseUrl(_LOGIN)
.addConverterFactory(GsonConverterFactory.create())
.client(clientBuilder.build())
.build()
val service = retrofit.create(RequestDto::class.java)
}
}
.baseUrl(_LOGIN) : 고정되는 api의 주소
val service = retrofit.create(RequestDto::class.java): retrofit builder에 넣어 사용할 수 있지만 한 곳에서만 사용 할 것이 아니기에 따로 뺴놨다
clientBuilder: okHttp에서 통신 logger를 보다 쉽게 해줘 넣은 것이라 안 써도된다 -> 사용시엔 종속성 추가해줘야 함
4. activity에서 사용
val intent = Intent(this@LoginActivity, InfoActivity::class.java)
Http.clientBuilder.addInterceptor(Http.loggingInterceptor)
Http.loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
binding.loginTxtView.setOnClickListener {
CoroutineScope(Dispatchers.Main).launch {
//데이터 요청
val resultData = Http.service.getLogin(
data = LoginRequest(
id = binding.idEditTxtView.text.toString(),
password = binding.passwordEditTxtView.text.toString(),
role = "RCT",
token = ""
)
)
try {
// code : 200,300 요청 왔을 때 (성공_)
if (resultData.isSuccessful) {
Timber.e("${resultData.body()}")
Log.e("LoginActivity(Body)", "${resultData.body()}")
val accessToken = resultData.body()?.result?.access
val user = resultData.body()?.result!!.user
//viewModel 사용하지 않고 데이터를 2번째 activity에 넘겨줌
intent.apply {
putExtra( "name", user.name)
putExtra( "age", user.age)
putExtra( "gender", user.birth)
}
startActivity(intent)
finish()
}
throw Exception("ERROR")
// 그외 에러 code나올 때
} catch (e: Exception) {
e.printStackTrace()
Log.e("FAILED : ", "${resultData.code()}")
}
}
}
이렇게 하면 로그는
E/LoginActivity(Body): LoginResultData(result=LoginResult(name = 홍길동,age = 18, gender = man)이런식으로 온다
model에서 추출한 데이터를 안드로이드에서 효율적으로 사용하는 방법엔 MVVM패턴 사용과 SharedPreferences내부 저장소가 있다 이 부분에 대해서도 공부해보자