[안드로이드 + 코틀린] 코틀린으로 로그인 기능 만들기~!

     

들어가기 전에

드디어 코틀린을 시작해볼 거예요. 이전에 안드로이드는 java로만 만들었었는데, 코틀린이 나오고 나서 다들 코틀린 좋다 코틀린 좋다 그러길래, 도대체 뭐가 좋은 거지?!? 많이 생각했었는데 ㅋㅋㅋ 이제 시작하겠습니다. 

일단 예전에 만들었던 로그인 기능을 코틀린으로 만들어보겠습니다. 들어가기 전에 저의 코틀린 이해도를 설명해 드려야 하는데, 한마디로 1도 모릅니다. ㅋㅋㅋㅋㅋ 정말 코틀린이 어떻게 생겨먹은지 하나도 몰라요. 이래서 할 수 있을지 모르겠어요. 하지만~ 뭐 모든 코딩 언어는 하나로 통하지 않겠습니까?(뭔 소리?) 일단 시작했으니 저는 코틀린에 대해 반이나 알았습니다. (여러분도요)

 

자세한 설명을 원하시면 아래 강의 Youtube영상을 시청해주세요

 

개발환경 및 사용 툴

예전에 안드로이드로 로그인기능 만들기 글을 올릴 때는, java를 활용한 안드로이드 + php와 mysql을 이용한 서버를 구현했습니다. 

이번에는 코틀린을 이용한 안드로이드 + 장고(python) restfulAPI 서버를 이용한 서버를 구현해보도록하겠습니다. 서버 구현은 앞에 글들을 참고하시면 됩니다~!!

이전 글
2019/10/14 - [Study/python] - dJango로 restful API 서버만들기 [1] - django 서버 생성
2019/10/17 - [Study/python] - dJango로 restful API 서버만들기 [2] - rest framework 적용
2019/11/10 - [Study/python] - dJango로 restful API 서버만들기 [3] - 웹에서 로그인 화면 만들어서 날려보기


 

시작하기 전에 서버에 작업하나..

일단 이전에 Django로 만든 API 서버에는 로그인을 콜 하는 게 있긴 합니다. 하지만  Django에 만든 웹에서 바로 호출하는 메소드였기 때문에 응답이 HttpResponse형태로 200, 401의 상태 값만 리턴해주는 간단한 로그인이었습니다. 실제 서비스되는 로그인 서비스들은 상당~~~히 많은 양의 정보가 응답메시지에 담겨있습니다. 예를 들어 로그인의 성공했을 경우, 해당 사용자의 공인인증서 정보나 회원 등급, 회원의 관심목록 등 다양한 정보들이 로그인을 통해 클라이언트로 전달됩니다. 따라서 단순 Http 상태 값만 주는 것이 아니라 더 많은 정보를 주기 위해 Json형태로 데이터를 넘겨줄 필요가 있습니다. 

여기서는 기존 로그인 기능에 응답메시지만 Json형태로 Code값과 Msg값 두 개를 리턴하도록 변경해봅시다. 

#addresses/views.py
@csrf_exempt
def app_login(request):

    if request.method == 'POST':
        print("리퀘스트 로그" + str(request.body))
        id = request.POST.get('userid', '')
        pw = request.POST.get('userpw', '')
        print("id = " + id + " pw = " + pw)

        result = authenticate(username=id, password=pw)

        if result:
            print("로그인 성공!")
            return JsonResponse({'code': '0000', 'msg': '로그인성공입니다.'}, status=200)
        else:
            print("실패")
            return JsonResponse({'code': '1001', 'msg': '로그인실패입니다.'}, status=200)
#restfulapiserver/urls.py
urlpatterns = [
	-
    path('app_login/', views.app_login),
	-
]

views.py에 위와 같이 app_login이라는 메소드를 하나 만들어주고 urls.py에서 연결합니다. 이제 ~/app_login을 호출하면 app_login 메소드를 탈 수 있고, 성공 실패 여부에 따라 code값과 msg가 json형태로 리턴됩니다.

 

안드로이드 프로젝트를 만들어 본다

사실 코틀린을 어떻게 공부할까 고민을 했는데.. 역시 제스타일은 만들면서 배우는 방법입니다. 뭐든 질러보고 안되면 구글링 해가면서 이해하는 것이죠. 약간 고등학교 때 답부터 보고 그 답으로 문제를 이해하는 스타일? ㅋㅋㅋ 

일단 프로젝트를 만들어봅시다. 최신 안드로이드 스튜디오에서는 언어를 코틀린으로 선택하여 만들 수 있습니다. 만약 언어 선택 화면이 나오지 않는다면 안드로이드를 최신 버전으로 업데이트해보세요~!

 

Retrofit2 사용하기

5~6년 전에 안드로이드에서 RESTAPI호출할 때는 AsyncTask를 만들어서 httprequest객체 만들어서 json형태로 통신했던 거 같은데, 최근 안드로이드 개발자 친구에게 물어보니 정석(?)이 있다고 하더라구요. 바로 Retrofit2 라이브러리를 사용하는 겁니다. Retrofit2이란 RESTAPI 통신을 아주 쉽게 구현 가능하게 도와주는 라이브러리인데요, 아주 대중적이고 오픈소스여서 많은 사람들이 사용하고 있네요. 그래서 이번에는 Retrofit2이란 놈을 사용해보려고 합니다.

공식 홈페이지 - https://square.github.io/retrofit/

 

Retrofit

A type-safe HTTP client for Android and Java

square.github.io

 

일단 안드로이드에서 사용하기 위해서는 gradle에 추가하면 되는데요. dependencies에 아래 문장을 추가하면 됩니다.

// Retrofit
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
implementation 'com.squareup.retrofit2:converter-gson:2.6.0'

app단위 gradle에 적으시면 됩니다.

 

그리고 이제 바로 사용하면 되는데요~ 일단은 로그인 화면이니까 ID와 PW를 입력하는 창이 있어야겠죠? 코틀린이라고 해서 layout그리는것은 기존 java와 다르지 않습니다. res/layout 경로에 가서 activity_main.xml을 열어보면 기존과 똑같습니다. 여기에 EDITTEXT위젯을 두 개 넣고 버튼 하나를 넣어볼게요. 보통 로그인하는 화면처럼... 아시죠? ㅋㅋㅋ

 

대충 뭐 이런 느낌으로 그려봅시다.

ConstraintLayout이기 때문에 박스 좌우를 화면 끝과 잘 붙여서 모양을 만들어 줍니다. 사실 기능 구현이 목적이기 때문에 레이아웃은 발로 그려도 상관없습니다. ㅋㅋㅋ 대신 각 위젯의 이름은 확실히 기억해 줍시다. 왜냐면 코드에서 써야 되거든요. 저는 ID를 입력하는 박스가 editText이고 비밀번호가 editText2, 로그인 버튼은 button입니다.

예전에는 findviewbyid를 사용해야 했는데 kotlin에서는 자동으로 id만 쓰면 인식하는 것을 지원해주더라구요? 그냥 editText를 쓰고 자동 import를 하거나 아래 패키지를 import하면 됩니다.

import kotlinx.android.synthetic.main.activity_main.*

왕편함! 물론 java로 할 때도 외부 라이브러리를 통해 쉽게 가져오는 방법들이 있긴 했는데, 이게 더 편한 것 같네요ㅋㅋㅋ

아무튼 editText와 editText2, button을 활용해 버튼을 누르면 id를 팝업으로 띄우도록 간단하게 만들어보겠습니다.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        button.setOnClickListener{
            var text1 = editText.text.toString()
            var text2 = editText2.text.toString()

            var dialog = AlertDialog.Builder(this@MainActivity)
            dialog.setTitle("알람")
            dialog.setMessage("id : " + text1)
            dialog.show()
        }
    }
}

코드 설명은 필요 없을 것 같아요. 신기한 건 아까 말한 것처럼 Button button = (Button)findViewById(R.id.button) 이런 거 안 쓰고 바로 button.setOnClickListerner를 사용했다는 점입니다. 버튼을 누르면 text1과 text2에 id와 password가 들어가고 dialog에서 id를 출력해주며 끝나게 됩니다. 아주 간단하죠? 

간단 명료 심플 단순 이지

자 이제 여기다가 api서버에 있는 applogin을 호출하는 코드를 넣을 건데.. retrofit2를 사용한다고 했습니다. retrofit으로 통신하는 방법은 특정 서비스를 인터페이스로 구현해놓고 INPUT값과 OUTPUT값을 정의하고 retrofit.build를 이용해 retrofit객체를 만든 다음에  거기에 서비스를 얹어서 사용하는 방식입니다. 말로 설명하니까 참 어렵죠..?  ㅋㅋㅋ 일단 순서대로 해볼게요. 물론 제가 정의한 순서이기 때문에 정답은 아닙니다. ㅋㅋ 하지만 이 순서대로 해야 오류가 제일 없을 것 같더라구요. 

INPUT, OUTPUT, SERVICE 만들기

저는 일단 OUTPUT구조를 만듭니다. OUTPUT이 뭐냐면 서버에 호출했을 때 클라이언트로 내려주는 응답 값이에요. 맨 위에서 저희는 응답으로 code값과 msg값을 주길기로 했습니다. 따라서 OUTPUT은 code와 msg입니다. 모두 문자열로 받을 거니까 둘 다 String으로 선언하면 되겠죠?

새로운 Login.kt파일을 만들고 아래와 같이 Data 형태를 정의해줍니다.

// Login.kt
package com.example.myapplication

data class Login(
    val code: String,
    val msg: String
)

data class Login으로 정의했고 안에는 문자열인 code와 msg가 있습니다. 이제 INPUT과 서비스를 정의해볼까요?

LoginService.kt 라는 문서를 만들고 아래와 같이 입력합니다.

// LoginService.kt
package com.example.myapplication

import retrofit2.Call
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.POST

interface LoginService{

    @FormUrlEncoded
    @POST("/app_login/")
    fun requestLogin(
        @Field("userid") userid:String,
        @Field("userpw") userpw:String
    ) : Call<Login>

}

서비스는 interface형태로 정의합니다. LoginService안에 requestLogin이라는 function이 있습니다. 여기에 annotation을 통해 어떤 서비스를 호출할 것인지 정의할 수 있습니다. url의 root경로는 나중에 retrofit객체를 만들 때 정의하기 때문에 root경로를 제외한 경로를 적어줍니다. 아까 위에서 app_login/으로 만들었기 때문에 /app_login/으로 적어줍니다.

POST형태로 데이터를 전송하기 때문에 @POST라고 적어줍니다.
그 위에 @FormUrlEncoded은 인코딩 옵션인데 적지 않으면 에러가 나더라구요. 이걸 적어야 Field로 선언한 변수들이 정상적으로 전달됩니다.

function안에는 api에 호출할 때 사용하는 변수들이 적혀있습니다. 저희는 여기서 id와 pw를 전달하기 때문에 userid, userpw를 적어놨습니다.

뒤에 Call<Login>은 호출하고 응답으로 어떤 값을 받아올지 적는 부분입니다. 여기에 아까 저희가 정의했던 Data class인 Login을 적어줍니다. 

이렇게 만들면 userid와 userpw를 INPUT으로 사용하고 Login class형태로 OUTPUT을 받아오는 POST Service가 생성되었습니다. 이제 retrofit객체를 만들어 이 서비스를 호출하면 서버와 통신이 됩니다.

 

RETROFIT 객체 만들기

이제 가장 중요한 retrofit 객체를 만들어 서비스를 올릴 건데요, 사실 뭐 없습니다. 아래와 같이 Retrofit.Builder().build()를 하시면 됩니다.

        var retrofit = Retrofit.Builder()
            .baseUrl("http://172.30.1.50:8000")
            .addConverterFactory(GsonConverterFactory.create())
            .build()

몇 가지 옵션이 들어갔는데, baseUrl은 호출하는 API 서버의 root경로입니다. 저는 집에서 공유기를 사용하고 있어서 ip주소가 172로 시작하는데, 에뮬레이터로 테스트해보니 잘 되더라구요 ^^ 

아래는 ConverterFactory옵션인데 Gson이라는 ConverterFactory를 사용합니다. Gson은 Json문서를 받아서 자동으로 java class형태로 만들어주는 역할을 합니다. 이 Gson을 사용하기 때문에 Django서버에서 실제로 응답 값은 Json형태로 주지만 안드로이드에서 Data class Login으로 받을 수 있는 겁니다. 만약 이게 없었다면 JsonParser를 이용해서 json 문서를 일일이 파싱해서 값을 넣어주는 작업이 필요했을 텐데, Gson을 사용하면 Json문서에 사용된 Key이름과 Data class의 이름만 맞춰주면 알아서 데이터가 들어갑니다. Data Class Login을 만들 때 code 변수명과 msg변수명이 서버에서 내려주는 Json과 일치했던 것도 바로 이 이유입니다. 

이렇게 retrofit객체를 선언하고 서비스를 올리는 방법 또한 간단합니다. 한 줄만 쓰시면 됩니다.

var loginService: LoginService = retrofit.create(LoginService::class.java)

LoginService로 interface를 만들어 논 것 기억나시죠? 그 형태로 객체를 하나 만들어줍니다. loginService라는 객체입니다. 이 객체는 retrofit.create(interface명)를 통해 생성할 수 있습니다. 이제 이 객체를 사용해 호출을 할 수 있습니다. 안드로이드에서 웹 통신할 때 예전에는 async를 사용하는 방법만 알고 있었는데 요즘엔 enqueue를 사용해서도 하더군요. 일단 호출 방법은 아래와 같습니다.

loginService.requestLogin(text1,text2).enqueue(object: Callback<Login>{
                override fun onFailure(call: Call<Login>, t: Throwable) {
					//실패할 경우
                }

                override fun onResponse(call: Call<Login>, response: Response<Login>) {
					//정상응답이 올경우
                }

            })

LoginService안에 requestLogin이라는 function을 정의한 것이 생각나실 겁니다. POST형태로 데이터를 두 개 보내고 응답으로 Login형태의 데이터를 받는 function이었습니다. 입력값은 requestLogin(text1, text2)이고 출력 값은 Callback<login>부분입니다. 성공하면 onResponse가 실행되고 실패하면 onFailure가 실행됩니다. 

이제 retrofit을 이용한 restful api호출 방법을 전부 알아봤습니다. 데이터 준비와 서비스 준비, 호출하는 방법도 알아봤으니 실제 버튼을 눌렀을 때 id, pw가 전송되고 응답으로 code값과 msg를 받아오도록 해보겠습니다.

class MainActivity : AppCompatActivity() {
    var login:Login? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var retrofit = Retrofit.Builder()
            .baseUrl("http://172.30.1.50:8000")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
        var loginService: LoginService = retrofit.create(LoginService::class.java)


        button.setOnClickListener{
            var text1 = editText.text.toString()
            var text2 = editText2.text.toString()

            loginService.requestLogin(text1,text2).enqueue(object: Callback<Login>{
                override fun onFailure(call: Call<Login>, t: Throwable) {
                    Log.e("LOGIN",t.message)
                    var dialog = AlertDialog.Builder(this@MainActivity)
                    dialog.setTitle("에러")
                    dialog.setMessage("호출실패했습니다.")
                    dialog.show()
                }

                override fun onResponse(call: Call<Login>, response: Response<Login>) {
                    login = response.body()
                    Log.d("LOGIN","msg : "+login?.msg)
                    Log.d("LOGIN","code : "+login?.code)
                    var dialog = AlertDialog.Builder(this@MainActivity)
                    dialog.setTitle(login?.msg)
                    dialog.setMessage(login?.code)
                    dialog.show()
                }
            })
        }
    }
}

 

MainActivity가 실행되면 retrofit객체와 서비스를 올려서 loginService를 만들고 버튼이 눌리면 loginservice가 호출되어 Django에 applogin을 타고 로그인 성공 여부를 알려줘서 팝업창이 뜨게 됩니다. 실제로 값을 넣어 테스트해보면 응답이 정상적으로 오는 걸 확인할 수 있습니다.

로그인 성공

서버에도 로그가 정상적으로 찍히는 것을 알 수 있습니다.

서버에 찍히는 로그

 

 

마무리하며

이번 포스팅에서는 지난 시간 만들었던 restfulapi서버와 안드로이드에서 retrofit을 이용해서 로그인 기능을 구현해봤습니다. 실제 서비스되는 앱에서는 이것보다 복잡하게 이루어지지만 전체적인 흐름은 비슷합니다. 로그인뿐만 아니라 다른 모든 api 호출서비스도 응답 값과 출력 값의 크기만 다를 뿐 비슷하게 사용되고 있습니다. 추가적인 기능들을 만들어보면서 다른 형태의 서비스 호출은 어떻게 하는지 공부하면 좋을 것 같습니다. 혹시 안되시는 부분이나 질문은 댓글 남겨주시면 답변해드리겠습니다. 감사합니다. 

 

 

 

 

 

 

 

 

 

 

댓글(9)

  • 궁금이
    2020.09.25 16:19

    loginService.requestLogin(text1,text2).enqueue(object: Callback<Login> {
    override fun onFailure(call: Call<Login>, t: Throwable) {

    여기서요 t: Throwable 이부분이요 Type mismatch: inferred type is String? but String was expected
    이런 에러 나오는데 어떻게 없애나요??

    • 2020.09.27 19:35 신고

      Null이 가능하도록 선언되어있는데 받는 값으로는 Null이 불가능하다라고 선언했기 때문에 발생하는 오류입니다. 어떤 값을 말하는지는 해당 메시지만 가지고는 모르겠네요. 코틀린에서 Nullable처리에 대해서 검색좀 해보신다음에 본인 코드랑 비교해보시면 될것같습니다.

  • 방문자
    2020.10.25 22:13

    소스코드 보고 싶은데 git 있을까요 ??

    • 2020.10.27 14:14 신고

      코틀린 코드는 git에 올려논게 없네요 '-'

  • ㅇㅇ
    2020.11.22 12:53

    장고를 runserver로 돌려놓은 다음 baseUrl을 "http://127.0.0.1:8000"으로 두고 로그인을 시도했는데 서버에 신호가 안옵니다. 이런식으로 하는게 아닌가요?

    • 2020.11.22 23:00 신고

      모바일에서 127.0.0.1로 호출 하셨다는건가요~? 그렇게 되면 모바일에서 모바일로 호출하기 때문에 장고로 호출이 안됩니다.

      장고가 켜있는 컴퓨터의 IP주소를 입력하셔야 됩니다!

  • ㅇㅇ
    2020.12.01 03:52

    회원가입이랑 이메일인증도 해주시면 안될까요 ㅠㅠ

    • 2020.12.01 23:27 신고

      아~ 웹에서 한것은 있는데 코틀린은 없긴한데.. .ㅎㅎ 비슷하게 만드시면 되지 않을까요?!

  • 코린이
    2020.12.02 16:15

    코틀린 retrofit 찾다찾다 왔는데 잘 설명해주셔서 구현 잘했네요 감사합니다! 덕분에 라이브러리 하나 배워가용~ 좋은하루되세요!!

Designed by JB FACTORY