[Android/Kotlin] 카카오 로그인 화면 구현하기 - (2)

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

kotlin study

목록 보기
19/19

1편과 이어지는 2편 글임
1편🙊
https://velog.io/@maddie/Android-카카오-로그인-화면-구현하기

이전 글에서 카카오 로그인 화면 구현과 기능에 필요한 기본 설정을 해주었다.

카카오 로그인만 해서 되는게 아니라, 프론트에서 카카오 서버로부터 액세스 토큰을 받으면 이를 백엔드에 전달해줘야 한다.🙈

https://developers.kakao.com/docs

카카오 디벨로퍼 홈페이지를 따라하면 이전 글인 1편에 있는 내용을 모두 구현할 수 있지만,
그 이후의 내용은 잘 안나와있어
몇일을 삽질했다.

우선 백엔드와 http 통신을 해야 하기 때문에,
레트로핏을 사용할거다.
레트로핏 기본 설정과 사용방법은 아래 링크 참고
내가 씀🙊
https://velog.io/@maddie/Android-Retrofit으로-로그인-기능-구현

🐵 1. 인터페이스

import com.google.gson.GsonBuilder
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.Body
import retrofit2.http.Header
import retrofit2.http.POST

interface LoginService {

    @POST("/oauth/kakao/")
    fun postAccessToken(
        //@Header("access_token") token: String
        @Body jsonParams : UserModel

    ): Call<LoginBackendResponse>

    companion object {
        private val gson = GsonBuilder().setLenient().create()
        fun loginRetrofit(): LoginService {
            return Retrofit.Builder()
                .baseUrl("https:BASE URL 주소")
                // .addConverterFactory(ScalarsConverterFactory.create())
                .addConverterFactory(GsonConverterFactory.create(gson))
                .build()
                .create(LoginService::class.java)
        }
    }
}

🐵 2. Object(클라이언트)

import com.google.gson.GsonBuilder
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object RetrofitHelper {

    val BASE_URL: String = "https://8e27-211-106-114-186.jp.ngrok.io/"
    var gson = GsonBuilder().setLenient().create()

    fun getRetrofitInstance(): Retrofit {
        val builder: Retrofit.Builder = Retrofit.Builder()
        val retrofit = builder.baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create(gson))
            .build()

        return retrofit
    }
}

3. 데이터 클래스

==JSON data를 받아올 데이터 클래스


data class UserModel(
    val accessToken : String ?= null
)

data class LoginBackendResponse(
    val email : String,
    //200: 성공, 300,400: 에러
    val accessToken : String,
    val refreshToken : String
)


/* 이렇게도 쓸 수 있음
data class DataclassEx(
    val `data`: List<Data>,
    val message: String,
    val status: Int,
    val isLogin: Boolean
)
{
    data class Data(
        val id: Int,
        val image: String,
        val user_id: Int,
        val user_img: String,
        val user_name: String
    )
}
 */

4. 액티비티


import android.content.ContentValues.TAG
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import com.example.kakaologin5.databinding.ActivityMainBinding
import com.kakao.sdk.auth.model.OAuthToken
import com.kakao.sdk.common.model.AuthErrorCause
import com.kakao.sdk.common.model.ClientError
import com.kakao.sdk.common.model.ClientErrorCause
import com.kakao.sdk.user.UserApiClient
import com.kakao.sdk.user.model.User
import retrofit2.Call
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

class MainActivity : AppCompatActivity() {
    private var retrofit: Retrofit = RetrofitHelper.getRetrofitInstance() // RetrofitClient의 instance 불러오기
    private lateinit var binding : ActivityMainBinding
    private var authToken : String ?= null
    var api : LoginService = retrofit.create(LoginService::class.java)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        retrofit = Retrofit.Builder()
            .baseUrl("https://8e27-211-106-114-186.jp.ngrok.io/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()

        //로그인 정보 확인
        UserApiClient.instance.accessTokenInfo{ tokenInfo, error ->
            if(error != null){
                Toast.makeText(this, "토큰 정보 보기 실패", Toast.LENGTH_SHORT).show()
            }
            else if(tokenInfo != null) {
                Toast.makeText(this, "토큰 정보 보기 성공", Toast.LENGTH_SHORT).show()
            }
        }

        /* Click_listener */
        binding.btnLogin.setOnClickListener {
            kakaoLogin() //로그인
        }
        binding.btnLogout.setOnClickListener {
            kakaoLogout() //로그아웃
        }

    }
    private fun kakaoLogin() {

        // 카카오계정으로 로그인 공통 callback 구성
        // 카카오톡으로 로그인 할 수 없어 카카오계정으로 로그인할 경우 사용됨
        val callback: (OAuthToken?, Throwable?) -> Unit = { token, error ->
            if (error != null) {
                when {
                    error.toString() == AuthErrorCause.AccessDenied.toString() -> {
                        Toast.makeText(this, "접근이 거부 됨(동의 취소)", Toast.LENGTH_SHORT).show()
                    }
                    error.toString() == AuthErrorCause.InvalidClient.toString() -> {
                        Toast.makeText(this, "유효하지 않은 앱", Toast.LENGTH_SHORT).show()
                    }
                    error.toString() == AuthErrorCause.InvalidGrant.toString() -> {
                        Toast.makeText(this, "인증 수단이 유효하지 않아 인증할 수 없는 상태", Toast.LENGTH_SHORT).show()
                    }
                    error.toString() == AuthErrorCause.InvalidRequest.toString() -> {
                        Toast.makeText(this, "요청 파라미터 오류", Toast.LENGTH_SHORT).show()
                    }
                    error.toString() == AuthErrorCause.InvalidScope.toString() -> {
                        Toast.makeText(this, "유효하지 않은 scope ID", Toast.LENGTH_SHORT).show()
                    }
                    error.toString() == AuthErrorCause.Misconfigured.toString() -> {
                        Toast.makeText(this, "설정이 올바르지 않음(android key hash)", Toast.LENGTH_SHORT).show()
                    }
                    error.toString() == AuthErrorCause.ServerError.toString() -> {
                        Toast.makeText(this, "서버 내부 에러", Toast.LENGTH_SHORT).show()
                    }
                    error.toString() == AuthErrorCause.Unauthorized.toString() -> {
                        Toast.makeText(this, "앱이 요청 권한이 없음", Toast.LENGTH_SHORT).show()
                    }
                    else -> { // Unknown
                        Toast.makeText(this, "기타 에러", Toast.LENGTH_SHORT).show()
                    }
                }
            } else if (token != null) {
                Log.i(TAG, "카카오계정으로 로그인 성공 ${token.accessToken}")
                binding.tvAccessToken.text = "access token : \n${token.accessToken}\n"
                authToken = token.accessToken
                api.postAccessToken(UserModel(accessToken = authToken)).enqueue(object : retrofit2.Callback<LoginBackendResponse>{
                    override fun onResponse(call: Call<LoginBackendResponse>, response: Response<LoginBackendResponse>) {
                        Log.d("로그인 통신 성공", response.toString())
                        Log.d("로그인 통신 성공", response.body().toString())

                        when (response.code()) {
                            200 -> {
                                Log.d("로그인 성공" , "ggg")
                            }
                            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("통신 로그인..", "전송 실패")
                    }
                })
            }
        }

        // 카카오톡이 설치되어 있으면 카카오톡으로 로그인, 아니면 카카오계정으로 로그인
        if (UserApiClient.instance.isKakaoTalkLoginAvailable(this@MainActivity)) {
            UserApiClient.instance.loginWithKakaoTalk(this@MainActivity) { token, error ->

                if (error != null) {
                    Log.e(TAG, "카카오톡으로 로그인 실패", error)

                    // 사용자가 카카오톡 설치 후 디바이스 권한 요청 화면에서 로그인을 취소한 경우,
                    // 의도적인 로그인 취소로 보고 카카오계정으로 로그인 시도 없이 로그인 취소로 처리 (예: 뒤로 가기)
                    if (error is ClientError && error.reason == ClientErrorCause.Cancelled) {
                        return@loginWithKakaoTalk
                    }

                    // 카카오톡에 연결된 카카오계정이 없는 경우, 카카오계정으로 로그인 시도
                    UserApiClient.instance.loginWithKakaoAccount(this@MainActivity, callback = callback)
                } else if (token != null) {
                    Log.i(TAG, "카카오톡으로 로그인 성공 ${token.accessToken}")

                }
            }
        } else {
            UserApiClient.instance.loginWithKakaoAccount(this@MainActivity, callback = callback)
        }
    }
    private fun kakaoLogout(){
        // 로그아웃
        UserApiClient.instance.logout { error ->
            if (error != null) {
                Toast.makeText(this, "로그아웃 실패 $error", Toast.LENGTH_SHORT).show()
            }else {
                binding.tvIsloginned.text = "로그아웃 되었습니다."
                Toast.makeText(this, "로그아웃 성공", Toast.LENGTH_SHORT).show()
            }
        }
    }
}
profile
Major interest in iOS 🍀 & 🍎

0개의 댓글