[Android] Retrofit으로 로그인 기능 구현

Madeline👩🏻‍💻·2023년 1월 12일
0

kotlin study

목록 보기
18/19

🐽 1. Retrofit

Retrofit은 앱 개발 시 서버통신에 사용되는 HTTP API를 자바, 코틀린의 인터페이스 형태로 변환해 안드로이드 개발 시 API를 쉽게 호출할 수 있도록 지원하는 라이브러리입니다.
HTTP API가 존재한다고 가정했을 때 Retrofit 라이브러리를 사용하면 HTTP API를 아래 코드와 같은 인터페이스 형태로 변환할 수 있음.

https://~~/user/{user}/repos 얘를

interface GitHubService {
    @GET("users/{user}/repos")
    fun getRepoList(@Path("user") user: String): Call<List<Repo>>
}

이렇게!

🐶

Retrofit을 이용해 서버를 연동하려면 실제 서버를 연동을 실행하는 Call 객체가 필요하다. 하지만 이 Call 객체는 개발자가 직접 만드는 것이 아니고 개발자는 인터페이스를 통해 설계도만 만들고 Retrofit이 자동으로 만들어 준다.

인터페이스를 이용하여 Service 객체를 획득한 후 네트워킹이 필요할 때 Call 객체를 획득하여 이용하면 되는 구조이다.

대부분의 안드로이드 개발자가 사용하는 Retrofit이 생기기 이전에도 사용하던 라이브러리들이 있었습니다.
바로 HttpClient, HttpUrlConnection, Volley, OKhttp 라이브러리인데요. 현재는 모두 Deprecated 되어 대부분 Retrofit을 사용한다.

🐽 Retrofit 사용

🐽 0. 권한 설정

(gradle)

dependencies {
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation 'com.squareup.okhttp3:okhttp:4.8.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:4.8.0'
} 

🐶

(manifest)

// 권한 설정
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
   
// 안드로이드 보안 설정
android:usesCleartextTraffic="true"
android:hardwareAccelerated="true"

🐽 1. 데이터 클래스

🐶 우선 api가 명세되어 있어야 한다.
=> 이에 맞춰서 data class를 만든다.

🐵 (@SerializedName("status") val status: Int
=> 코드상 변수명과 api에 정의된 이름을 다르게 할 때 사용)

🐸 data class 내의 data class, model 도 가능하다

//백엔드로 전송할 데이터 클래스
data class UserModel(
    var id : String ?= null,
    var pw : String ?= null
)

//백엔드에서 받는 데이터 클래스
data class LoginBackendResponse(
    val code : String,
    //200: 성공, 300,400: 에러
    val message : String,
    val token : String
)

🐽 2. 인터페이스

서버 주소로부터 데이터를 전달하고, 전달 받는다(GET, POST)

@Get("policy"): baseUrl 뒤에 /policy가 붙는다.
@Query("location"): 주소에 들어가는 파라미터인 location을 @Query로 지정한다.
@Header("Authorization"): 서버가 토큰 인증을 사용중이고 헤더에 토큰을 담아 넘겨줘야 하는 경우 @Header("Authorization")을 추가한다.
Call: 서버로부터 데이터를 PolicyResponse 타입으로 받아온다.

baseUrl은 변하지 않는 주소를 지정합니다! ex) http://1.5.7.9:8080

사실 열심히 구글링하다가
인터페이스 내에 companion object를 선언하는 코드와
여기에 하지 않고 다른 파일에 선언하는 코드 둘 다 많이 봤는데,
여기에 해야 액티비티에서 바로 .create()해서 사용할 수 있는 것 같다.
이 부분 밑에 🦁 사자 표시 해놓겠음

import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.annotations.SerializedName
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.Body
import retrofit2.http.Headers
import retrofit2.http.POST
import retrofit2.http.Query

interface Api {
    //@Headers("app/json")
    @POST("/front")
    fun userLogin(
        @Body jsonParams : UserModel,
    ): Call<LoginBackendResponse>


    companion object {
        private const val BASE_URL = "https:백엔드 서버 uri/"
        val gson : Gson =   GsonBuilder().setLenient().create();

        fun create() : Api{
          
            return Retrofit.Builder()
                .baseUrl(BASE_URL)
                //.client(client)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .build()
                .create(Api::class.java)
        }
    }
}

3. 🐽클라이언트 - object

🦁 이 부분이 위에 인터페이스에서 companion object 선언 안했을 때 이런 식으로 따로 파일을 만들어서 작동하게 하는 방법!!!!!🦁
=> 레트로핏 객체를 만들었기 때문에 RetrofitBuilder.api로 어디서든 사용할 수 있습니다.
=> 위에서 해줬으면 없어도 된다는 말임

🐶
RetrofitClient는 싱글톤으로 구현하기 위해 object 키워드를 사용한다.
(코틀린에서의 object는 클래스를 싱글톤으로 구현한다.)

싱글톤: 객체의 인스턴스를 1개만 생성하고 계속 재사용하는 패턴

서버 통신할 액티비티나 Fragment에서 retrofitbuilder(client)를 사용해 바로 사용할 수 있게 싱글톤 패턴으로 object 생성

import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object RetrofitClient {
    val BASE_URL = "https://여기에 백엔드 서버 uri"
    fun getApiClient(): Retrofit {

        return Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }
}

🐽 4. 액티비티 (or Fragment)

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import com.example.generallogin.databinding.ActivityMainBinding
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // ==로그인 버튼 클릭 시==
        binding.btnLogin.setOnClickListener {
            val id = binding.editTVId.text.toString().trim()//trim : 문자열 공백제거
            val pw = binding.editTVPw.text.toString().trim()

            saveData(id, pw)//db (shared preference)에 데이터 저장 (자동 로그인 용)

            // == 백엔드 통신 부분 ==
            val api = Api.create()//🦁🦁🦁🦁🦁
            val data = UserModel(id, pw)

            api.userLogin(data).enqueue(object : Callback<LoginBackendResponse> {
                override fun onResponse(
                    call: Call<LoginBackendResponse>,
                    response: Response<LoginBackendResponse>
                ) {
                    Log.d("로그인 통신 성공",response.toString())
                    Log.d("로그인 통신 성공", response.body().toString())

                    when (response.code()) {
                        200 -> {
                            saveData(id, pw)
                        }
                        405 -> Toast.makeText(this@MainActivity, "로그인 실패 : 아이디나 비번이 올바르지 않습니다", Toast.LENGTH_LONG).show()
                        500 -> Toast.makeText(this@MainActivity, "로그인 실패 : 서버 오류", Toast.LENGTH_LONG).show()
                    }
                }

                override fun onFailure(call: Call<LoginBackendResponse>, t: Throwable) {
                    // 실패
                    Log.d("로그인 통신 실패",t.message.toString())
                    Log.d("로그인 통신 실패","fail")
                }
            })
        }
    }
}

val api = Api.create()//🦁🦁🦁🦁🦁
위에서 말한 부분

🐽 5. 데이터 저장하는 함수도 만들자

fun saveData( id : String, pw : String){
        val prefID = getSharedPreferences("userID", MODE_PRIVATE)
        val prefPW = getSharedPreferences("userPW", MODE_PRIVATE)
        val editID = prefID.edit()
        val editPW = prefPW.edit()
        editID.putString("id", id)
        editPW.putString("pw", pw)
        editID.apply()//save
        editPW.apply()//save
        Log.d("로그인 데이터", "saved")
    }

Shared Preference에 아이디와 비밀번호를 저장하는 방법이다.
저장이 잘 되었나? 확인하고 싶으면
안드로이드 스튜디오에 Device File Explorer 에서
data/data/[package 이름]/shared_prefs/
경로로 들어가면 보인다.

요런식으로 잘 저장되었다.

🐽 6. 레이아웃

🐶 reference
https://yang-droid.tistory.com/21
https://stickode.tistory.com/43
https://velog.io/@songa29/Kotlin-Android-retrofit2-GET-서버-연동
https://week-year.tistory.com/146

profile
Major interest in iOS 🍀 & 🍎

0개의 댓글