UMC_안드로이드_9주차

Falco·2022년 1월 25일
0

갑자기 9주차로 뛴거 같은건 기분 탓


🌐 Network & API

핵심 키워드 🎯

  • Client, Server 의 관계
  • Http 통신 (Https)
  • API, Restful API
  • JSON, GSON
  • Retrofit

한 것

자동 로그인 API 엮기 및 회원가입 구현

  • 정규식 표현으로 회원가입 로그인, 패스워드 규격 맞추기
  • 이메일, 패스워드 보내서 서버에 저장하고 JWT받아오기.
  • 서버로 JWT 보내서 자동 로그인 가능하게 하기.
  • 앨범인덱스 보내고 앨범수록곡 받아오기
  • 좋아요 눌렀을 때 서버에 좋아요한 노래 추가

영상


포스트맨 사용후기

서버에 응답을 요구하거나, 어떠한 데이터 형식으로 보내거나 받을 때의 서버에서 반응을 확인 할 수 있었다.

POST 형식의 데이터 통신
Json 형식으로 email과 password를 보내고, 그응답으로

{
    "timestamp": "2022-01-25T12:32:33.814+00:00",
    "status": 404,
    "error": "Not Found",
    "message": "",
    "path": "/users/login"
}

를 받은 모습. 물론 서버가 지금은 없기때문에 제대로 된 응답은 나오지 않는다.

GET 데이터 통신으로
헤더에 Token을 넣거나,

URL에 인덱스를 넣어 원하는 값을 도출할 수 있을것이다.

그렇다면 이렇게 서버가 만든 URL과 헤더, 토큰들을 어떻게 사용해야 할까..?


Retrofit2를 사용해 Android 에서 통신하기

  • 로그인 구현
    private fun login(){
        val authService = AuthService()
        authService.setLoginView(this)
        authService.login(getUser())
    }
    
     private fun getUser(): User {
        val email : String = binding.loginEmailEd.text.toString() + "@" + binding.loginEmailadrEd.text.toString()
        val pwd : String = binding.loginPwEd.text.toString()

        return User(email,pwd,"")
    }

액티비티에서 로그인이 진행 될 때 그저 서버에 로그인 이메일과 패스워드를 보내고 OK 로그인!! 하면 좋을 것 같다.

보면은 email과 pwd를 반환하는 getUser함수와 authService를 통해 로그인을 요청하고있다.

아니 authService가 뭐야 그럼??

일단 전체소스

class AuthService {
    private lateinit var loginView: LoginView

   //로그인뷰가 왜필요하지??
    fun setLoginView(loginView: LoginView){
        this.loginView = loginView
    }

    fun login(user: User) {
    //getReposit()함수를 따로 Util에 만들어서 간단하게 사용했다.
//        val retrofit = Retrofit.Builder().baseUrl("URL주소").addConverterFactory(GsonConverterFactory.create()).build()
//        val authService = retrofit.create(AuthRetrofitInterface::class.java)
        val authService = getReposit().create(AuthRetrofitInterface::class.java)
        loginView.onLoginLoading()

        authService.login(user).enqueue(object : Callback<AuthResponse> {
            @SuppressLint("LongLogTag")
            override fun onResponse(call: Call<AuthResponse>, response: Response<AuthResponse>) {
                Log.d("LOGINACT/API-RESPONCE",response.toString())

                val resp = response.body()!!
                Log.d("LOGINACT/API-RESPONCE-FLO",resp.toString())
                when(resp.code){
                    1000 -> loginView.onLoginSuccess(resp.result!!)
                    else -> {
                        loginView.onLoginFailure(resp.code, resp.message)
                    }

                }
            }
            override fun onFailure(call: Call<AuthResponse>, t: Throwable) {
                Log.d("LOGINACT/API-ERROR",t.message.toString())
                loginView.onLoginFailure(400,"네트워크 오류가 발생했습니다.")
            }
        })
    }
    
   }

Retrofit을 사용해 통신을 진행해야 하기 떄문에
서버와 연결한 Retrofit 빌더를 만들어 통신준비를 해야한다.

val retrofit = Retrofit.Builder().baseUrl("URL주소").addConverterFactory(GsonConverterFactory.create()).build()
val authService = retrofit.create(AuthRetrofitInterface::class.java)
        

보면은 Json으로 값을 불러오고 Gson으로 바꾸기 때문에 GsonConverter도 들어간 모습
authService 객체를 만들어 통신을 이제 좀 할려고하는데
AuthRetrofitInterface 즉 RetrofitInterface가 필요하다!
이건뭘까

interface AuthRetrofitInterface {
    @POST("/users")
    fun signUp(@Body user: User): Call<AuthResponse>
    //함수(바디에보낼것) : Call<결과로 받을 형식>

    @POST("/users/login")// 12.52.215.12/users/login
    fun login(@Body user: User) : Call<AuthResponse>

    @GET("/users/auto-login")
    fun autologin(@Header("X-ACCESS-TOKEN") jwt: String?) : Call<AuthResponse>

}

주의 할점은 Call을 Import할 때 Retrofit2로 Import해야됨

즉 RetrofitInterface는 서버와의 어떠한 형식의 데이터교환을 어떤방식으로 할지 정해놓은 말그대로 인터페이스

@POST("/users")
    fun signUp(@Body user: User): Call<AuthResponse>

POST형식 "baseurl"/users 와 통신을 진행하며
Body에 User형식의 데이터를 보내고 AuthResponse의 형식으로 받아온다.

//유저의 데이터 형식 이메일과 패스워드를 사용할 것

@Entity (tableName = "UserTable")
data class User(
    var email: String,
    var password: String,
    var name: String? = "",
    ){
    @PrimaryKey(autoGenerate = true)
    var id:Int = 0
}

AuthResponse의 데이터 형식
isSuccess, code, message, result등의 데이터를 Json으로 받아와서 프론트에서 처리한다.

data class Auth(
		@SerializedName("userIdx")val userIdx: Int,
                @SerializedName("jwt")val jwt : String
                )


//@SerializedName("isSuccess") Json으로 오는 키값을 지정할수 있다.
//이름은 같아도 타입이 다르면 오류
data class AuthResponse(@SerializedName("isSuccess")val isSuccess: Boolean,
                        @SerializedName("code")val code:Int,
                        @SerializedName("message")val message: String,
                        @SerializedName("result")val result : Auth?
                        )

OK!! Retrofit 빌더 및 Interface 만들고 데이터 보내는 형식 및 받는 형식까지 다 정했어!

        loginView.onLoginLoading()

로그인 뷰는 뭘까요?

interface LoginView {
    fun onLoginLoading()
    fun onLoginSuccess(auth : Auth)
    fun onLoginFailure(code : Int, message : String)
}

프론트에서 로그인이 진행될 때 로그인이 진행중인지, 성공했는지, 실패했는 지 Activity에 알려주는 인터페이스

Activity에 알려준다보다는 Activity에 직접 상속받아 login을 진행하면서 실패하거나 성공할 때 그 함수를 불러줄 것이다.


음.. 알았어!! 로그인이 진행중인지, 성공했는지 View까지 다 만들었으니까 진짜 로그인을 해보자고

authService.login(user).enqueue(object : Callback<AuthResponse> {
            @SuppressLint("LongLogTag")
            override fun onResponse(call: Call<AuthResponse>, response: Response<AuthResponse>) {
                Log.d("LOGINACT/API-RESPONCE",response.toString())

                val resp = response.body()!!
                Log.d("LOGINACT/API-RESPONCE-FLO",resp.toString())
                when(resp.code){
                    1000 -> loginView.onLoginSuccess(resp.result!!)
                    else -> {
                        loginView.onLoginFailure(resp.code, resp.message)
                    }

                }
            }
            override fun onFailure(call: Call<AuthResponse>, t: Throwable) {
                Log.d("LOGINACT/API-ERROR",t.message.toString())
                loginView.onLoginFailure(400,"네트워크 오류가 발생했습니다.")
            }
        })

retrofit.create로 만든 authService객체의 로그인을 진행해보자.

enqueue는 뭐지?

  • enqueue : 비동기 방식
  • execute : 동기 방식
    Retrofit에서는 2가지 통신 메서드를 지원한다! 그러나 여기까지 알아보기엔 내가 너무 똑똑해 질 것같으니 일단 user객체를 보내어 통신을 진행한다고 생각하자.

참고로 Callback도 임포트할 때 retrofit2로 할것을 잊지말자.

override fun onResponse(call: Call<AuthResponse>, response: Response<AuthResponse>) {
                Log.d("LOGINACT/API-RESPONCE",response.toString())

                val resp = response.body()!!
                Log.d("LOGINACT/API-RESPONCE-FLO",resp.toString())
                when(resp.code){
                    1000 -> loginView.onLoginSuccess(resp.result!!)
                    else -> {
                        loginView.onLoginFailure(resp.code, resp.message)
                    }

                }
            }

onResponse로 결과 값을 받아오고 서버에서 결과를 보내주는데

body에 오는 결과 값 중 request.code가 1000이면 성공이고, 나머지는 다 실패하는 결과라서
1000일때는 loginView의 onLoginSucces를 불러주고,
실패할 때는 onLoginFailure을 실행시켰다.

이는 서버에서 전송해주는 데이터에 따라 다를 수도 있는점을 알고가자.

override fun onFailure(call: Call<AuthResponse>, t: Throwable) {
                Log.d("LOGINACT/API-ERROR",t.message.toString())
                loginView.onLoginFailure(400,"네트워크 오류가 발생했습니다.")
            }

반응이 없고 오류가 나면 onFailure이 실행되며 이때도 loginView의 onLoginFailure을 실행하여 activity에 실패했다는 사실을 알려주자.


OK 액티비티 너는 뭐해 그럼?

class LoginActivity : AppCompatActivity(), LoginView 

일단 진행하기 전에 LoginView를 상속받는다.

override fun onLoginLoading() {
    }

    override fun onLoginSuccess(auth: Auth) {
        //saveJwt(auth.jwt)
        saveUserIdx(this, auth.userIdx)
        savejwt(this, auth.jwt)

        startMainActivity()
        setResult(Activity.RESULT_OK, intent)
        finish()
        return
    }

    override fun onLoginFailure(code: Int, message: String) {
        when(code){
            2015, 2019, 3014->{
                binding.loginErrorTv.visibility = View.VISIBLE
                binding.loginErrorTv.text = message
            }
        }
    }

그리고 상속받은 함수들을 override하여 구현하면 된다.

  • onLoginLoading은 로딩 중 화면에 로딩사진을 로드하거나, 잠시 딜레이를 주든가 중간에 실행될 액션을 넣으면 된다.

  • onLoginSuccess으로 성공했을 때는 결과값으로 Auth를 받아왔고, 데이터를 맘껏 처리하면 됨

  • onLoginFailure 실패했을 때는 실패코드가 여러개 있을 것이고, 그에따른 액션을 취하면 된다.


엄마 나 서버 무서워

Retrofit2 라이브러리를 사용하여 간단하게 만든 로그인이 이정도면 라이브러리 없는 로그인은 생각하기도 무섭다.

정리하자면

  1. 실직적인 로그인을 진행할 Service 클래스를 만든다.
    ex) authService

  2. authService내에서 activity와 이어줄 loginView 인터페이스를 만들어 activity와 차분히 소통한다.
    activity는 loginView를 상속받아 사용

  3. login 함수를 만들고 그 안에 레트로핏 객체 만들기

    	```
    	val retrofit = Retrofit.Builder().baseUrl("http:주소주소").addConverterFactory(GsonConverterFactory.create()).build()
    	 val authService = retrofit.create(AuthRetrofitInterface::class.java)
    	```
    	레트로핏 빌더객체를 만들고 authService객체를 만들어 통신을 준비한다.

    retrofit.create()는 구글링하면 더 자세히 알아 볼 수 있을 것

    @POST("/users/login")// 11.23.23.122/users/login
     fun login(@Body user: User) : Call<AuthResponse>
	인터페이스 내에서 어떠한 URL형식인지, 무엇을보낼지
    @Body, @Query, @Header 등등 자세한 내용은 서버와의 원만한 협의로 진행하기
  1. 로그인 진행하고 onResponce 및 onFailure일때 loginView함수 실행시켜서 메인액티비티에 성공, 실패 결과 값 보내기

  2. 메인액티비티에서는 loginView 함수 상속받아서 데이터 처리하기

정도 인것 같다

더 알아가기
*
Http, Https 차이

  • Restful API 란?
profile
강단있는 개발자가 되기위하여

1개의 댓글

comment-user-thumbnail
2023년 12월 19일

안녕하세요 저도 umc 안드로이드 공부하고 있는 학생입니다...! 혹시 서버로 JWT 보내서 자동 로그인 가능하게 하기 이 부분을 어떤식으로 구현하셨는지 조언 얻을 수 있을까요?

답글 달기