이전에는 서버를 사용하여 KakaoLogin을 사용했는데, 고학년이 되고 Kakao Developer를 다시 찾아보니 Client만을 사용해서도 사용하는 방법이 있었습니다. 오늘은 그것의 사용법에 대해 정리해보겠습니다.

 

자세한 내용은 https://developers.kakao.com/ 이곳을 찾아보시면 알 수 있을 것입니다.

 


Project단위 Gradle에 다음과 같이 삽입합니다.

allprojects {
    repositories {
        google()
        jcenter()
        mavenCentral()
        maven { url 'http://devrepo.kakao.com:8088/nexus/content/groups/public/' }
        
    }
}

 

그리고나서, App단위 Gradle의 Dependency에 다음을 삽입해줍니다.

// 카카오 로그인
    implementation group: 'com.kakao.sdk', name: 'usermgmt', version: '1.27.0'

 

마지막으로, Manifest에 삽입해줍니다.

<application>

    ...

    <meta-data
        android:name="com.kakao.sdk.AppKey"
        android:value="{NATIVE_APP_KEY}" /> //네이티브 앱 키 설정
</application>

 

여기서의 NATIVE_APP_KEY는 https://developers.kakao.com/ 이곳에서 내 애플리케이션을 생성해야 받을 수 있습니다.

 

이제 KakaoLoginSDK를 사용하기 위한 Class를 만들어보겠습니다.

class KakaoSDKAdapter : KakaoAdapter() {
    override fun getSessionConfig(): ISessionConfig {
        return object : ISessionConfig {
            override fun getAuthTypes(): Array<AuthType> {
                return arrayOf(AuthType.KAKAO_LOGIN_ALL)
            }

            override fun isUsingWebviewTimer(): Boolean {
                return false
            }

            override fun getApprovalType(): ApprovalType? {
                return ApprovalType.INDIVIDUAL
            }

            override fun isSaveFormData(): Boolean {
                return true
            }

            override fun isSecureMode(): Boolean {
                return true
            }
        }
    }

    override fun getApplicationConfig(): IApplicationConfig {
        return IApplicationConfig {
            GlobalApplication.instance
        }
    }
}

이 Adapter를 객체로 생성하여 init을 해주어야합니다.

 KakaoSDK.init(KakaoSDKAdapter())

 

 

로그인 세션(Session)을 생성할 때 필요한 옵션을 얻는 추상 클래스(abstract class)인 ISessionConfig를 구현합니다. 카카오 로그인 설정을 담은 ISessionConfigKakaoAdapter에 기본 설정되어 있으며, 설정 변경이 필요할 때 상속해서 사용합니다.(출처 : https://developers.kakao.com/docs/latest/ko/getting-started/sdk-android-v1#import)

 

이처럼 로그인 세션을 생성할 때 필요한 ISessionConfig를 상속받는 Class를 만들어봅시다.

onSuccess 메소드를 통해 로그인한 유저의 Kakao 정보를 받아 사용하실 수 있습니다.

package com.example.mnt_android.service.repository

import android.app.Application
import android.util.Log
import com.example.mnt_android.service.model.KakaoUser
import com.example.mnt_android.view.ui.LoginActivity
import com.kakao.auth.ApiResponseCallback
import com.kakao.auth.AuthService
import com.kakao.auth.ISessionCallback
import com.kakao.auth.Session
import com.kakao.auth.network.response.AccessTokenInfoResponse
import com.kakao.network.ErrorResult
import com.kakao.usermgmt.UserManagement
import com.kakao.usermgmt.callback.MeV2ResponseCallback
import com.kakao.usermgmt.response.MeV2Response
import com.kakao.util.exception.KakaoException

class SessionCallback(application : Application) : ISessionCallback {

    val TAG = "SessionCallback"



    override fun onSessionOpenFailed(exception: KakaoException?) {

    }

        override fun onSessionOpened() {
        UserManagement.getInstance().me(object : MeV2ResponseCallback() {


            override fun onFailure(errorResult: ErrorResult?) {
                Log.e(TAG,"Fail")
            }

            override fun onSessionClosed(errorResult: ErrorResult?) {
                Log.d(TAG,"closed")
            }

            override fun onSuccess(result: MeV2Response?) {

				Log.d(TAG,"Success")

            }

        })


    }




}

 

 

가장 중요한 단계입니다. 이렇게 준비해놓고 로그인 버튼을 눌렀는데 로그인이 안된다구요?

 

카카오 디벨로퍼 사이트 내의 '플랫폼' 메뉴가 있습니다. 클릭하셔서 들어가시면 Android/iOS/Web 으로 나뉩니다.

저희는 Android 개발자이므로 Android 항목을 봅시다.

패키지명, 마켓URL, 키 해시를 적을 수 있습니다. 

패키지명은 자신의 프로젝트의 패키지명을 적습니다, ex) com.xxx.xxx

키 해시가 중요합니다. 런칭을 하지 않은 App들은 키 해시가 등록되어있어야지만 로그인이 가능합니다.

키 해시 값은 XXXXXXX= 로 되어있으며 도출된 값을 복붙하여 추가해주시면 됩니다.

키 해시값을 얻을 수 있는 Java코드입니다. Kotlin을 사용하시는 개발자 분들은 Kotlin으로 변경하셔서 사용하면 되겠습니다.

 

public static String getKeyHash(final Context context) {
    PackageInfo packageInfo = getPackageInfo(context, PackageManager.GET_SIGNATURES);
    if (packageInfo == null)
        return null;

    for (Signature signature : packageInfo.signatures) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA");
            md.update(signature.toByteArray());
            return Base64.encodeToString(md.digest(), Base64.NO_WRAP);
        } catch (NoSuchAlgorithmException e) {
            Log.w(TAG, "Unable to get MessageDigest. signature=" + signature, e);
        }
    }
    return null;
}

 

 

이렇게 모든 준비가 끝났습니다.

 

- - -

 

SDK가 제공해주는 KakaoLogin 버튼을 사용하면 간단히 로그인 할 수 있습니다.

저는 디자이너 분들과 함께 작업을 했기 때문에 커스텀 버튼을 사용해야했기에... Custom Button에 DataBinding을 통해 onclick 함수를 적어주었습니다.

(Data Binding 에 대해 정리한 내용 : https://wlgusdn700.tistory.com/9)

(Data Binding 을 사용해본 예제 : https://wlgusdn700.tistory.com/13)

android:onClick="@{()->loginActivity.login()}"

 

login() 메소드에서는

ISessionCallback 객체를 생성하고, 그 객체의 파라미터로 application Context를 넘겨줍니다.

그리고 Session을 통해 로그인 방법 등을 설정하실 수 있습니다.

저의 코드는 이렇습니다.

fun login()
    {
        //눌러서 로그인
       val callback = SessionCallback(application)
        Session.getCurrentSession().addCallback(callback)
        Session.getCurrentSession().checkAndImplicitOpen()
        Session.getCurrentSession().open(AuthType.KAKAO_LOGIN_ALL,this@LoginActivity)
        loginViewModel.kuser = callback.user
    }

 

이 단계들을 모두 적용하여 따라하면 KakaoLogin을 이용하실 수 있습니다.

 

Response Body에

...

"data" : [

...

"asd": "asd"

...]

...

 

형식으로 있을 때 Flowable<List<Object>> 형식으로 받아오려했지만 에러가 나서 찾아본 후

Json 반환 명이 다르다는 것을 알았고 해결을 위해

새로운 List<Object>를 담는 class를 하나 더 생성했다.

 

ex)

class CheckRoomList(
    @SerializedName("data")
    val checkRoomList : List<CheckRoom>
)

이렇게 생성한 후 Flowable<CheckRoomList> 로만 선언해주면 에러없이 동작하게 된다.

 

객체를 받아올때 

endDay로 Response가 온다면

내가 생성한 객체의 변수도 endDay여야 한다.

endDate로 했다가 안 들어왔다.

 

 

https://developers.kakao.com/docs/android/kakaotalk-link#카카오톡링크

 

Kakao Developers_

더 나은 세상을 꿈꾸고 그것을 현실로 만드는 이를 위하여 카카오에서 앱 개발 플랫폼 서비스를 시작합니다.

developers.kakao.com

자세한 사용법은 링크를 통해 확인이 가능하다.

링크를 통하면 커스텀, 피드, 소셜 등등 많은 것들을 사용가능하다...

귀찮은 사람들을 위해 내가 사용한 방법만을 간단하게 설명하겠다.

 

 

  • 첫번째로 카카오SDK를 사용하기 위한 dependency를 추가해야한다.
// 카카오링크 sdk를 사용하기 위해 필요.
    implementation group: 'com.kakao.sdk', name: 'kakaolink', version: '1.27.0'

 

 

  • 두번째로 App Key를 받아와야하는데..
    • 간단하게 설명하자면 Kakao Developer 사이트에 들어가 App을 등록하고 등록 후 제공되는 App키를 받아서 
    • Manifest 파일에 넣는 것이다.
 <meta-data
            android:name="com.kakao.sdk.AppKey"
            android:value="@string/kakao_app_key" />
  1. 내가 사용하는 Text 위주의 Link를 보내고 그 Link에 Button을 달아 App을 설치/실행 시키는 코드이다.\
    1. 3줄에 보이는 TextTemplate는 제공되는 class로 Text를 전달할 때 사용된다. 외의 것들은 후에 설명하겠으며
    2. 눈여겨 볼 것은 setAndroidExevutionParams인데 내가 맘대로 네이버로 바꿔놨지만 이곳에는 내 App의 설치 주소가 들어가면 되겠다.
    3. 그 뒤에 addButton(...)은 Button을 추가하여 그 버튼이 눌렸을 시 이동할 곳들을 적는 것이다....
 fun sendKakaoLink(roomnum : String)
    {
        var params = TextTemplate
            .newBuilder("마니또를 생성하였습니다", LinkObject.newBuilder().setAndroidExecutionParams("https://www.naver.com").build())
            .addButton(ButtonObject("앱에서 보기",LinkObject.newBuilder().setWebUrl("'https://www.naver.com'").setMobileWebUrl("'https://www.naver.com'")
                .setAndroidExecutionParams("roomnum=$roomnum").build())).build()

        var serverCallbackArgs  = HashMap<String, String>();
        var aa : Map<Any,Any> = HashMap<Any,Any>()


        var aaa  = object : ResponseCallback<KakaoLinkResponse>(){
            override fun onSuccess(result: KakaoLinkResponse?) {


            }

            override fun onFailure(errorResult: ErrorResult?) {

            }

        }

        KakaoLinkService.getInstance().sendDefault( this, params, serverCallbackArgs,aaa)

    }

 

  1. 다음 으로는 Link에 이미지를 넣는 코드이다.
    1. 크게 보이는 다른 점은 FeedTemplate를 사용했다. (Text와 Feed 또 Custom도 가능한 class도 있기 때문에 사용하는 사람의 용도에 맞게 developer사이트에서 찾아보면 되겠다.)
    2. ContentObject를 통해 Image를 삽입하는데 필수로 적어야할 3개의 parameters가 있으니 사이트를 참고할 수 있도록 하며, 내가 올린 코드는 서버에 있는 Image파일을 전달하는 방법이지만 휴대폰 내부에 저장된 Image를 보내는 방법도 있으니 확인바란다.
 var params = FeedTemplate
            .newBuilder(ContentObject.newBuilder("타이틀","https://lh3.googleusercontent.com/proxy/nzDAOkpvo9qjn2ZniqWhUrK7cv1g_Y5sZgCUt4hOZelp4jhMn3S0VMAxI9h2WQ9zavJYMWrISfKxAWPNBPa-HKPo6k8yVvZkG7n3cjfsUeG99EDsQ3XDRwuMK8e7znE",
                LinkObject.newBuilder().setAndroidExecutionParams("https://www.naver.com").build()).build())
            .addButton(ButtonObject("앱에서 보기",LinkObject.newBuilder().setWebUrl("'https://www.naver.com'").setMobileWebUrl("'https://www.naver.com'")
                .setAndroidExecutionParams("234").build())).build()

 

App을 개발할 때 가장 많이 사용되는 KakaoLink를 사용해보았다. 한번 사용해보면 기본적인 사용법은 익혀지니 좋은 것 같다.

항상 이론만 공부하다보니 내가 아는게 맞나? 싶은 생각만 들었다.

사실 외부 동아리때문에 프로젝트를 MVVM으로 진행하다보니 DataBinding과 LiveData와 MVVM패턴에는 조금 익숙해져 있었지만, 아직 서버를 연동하지 못 했고 유동적인 Data가 없다보니 Retrofit과 RxJava를 예제만 봐왔었고 직접 코딩해보지 않았다.

 

곧 서버 API를 붙힌다고하니 연습을 해보려 해본 예제이다.

 

API는 Github에 있는 API를 사용하여 팔로워를 가져오는 API를 사용했다.

이때 나의 팔로워는 1명이기에... 그냥 그렇다...

 

어쨋든 내가 생성한 Model 이다

 

class User(
    val login: String,
    val id: Long,
    val node_id: String,
    val avatar_url: String,
    val gravatar_id: String,
    val url: String,
    val html_url: String,
    val followers_url: String,
    val following_url: String,
    val gists_url: String,
    val starred_url: String,
    val subscriptions_url: String,
    val organizations_url: String,
    val repos_url: String,
    val events_url: String,
    val received_events_url: String,
    val type: String,
    val site_admin: Boolean
)
{
    override fun toString(): String {
        return id.toString()+node_id+url+html_url
    }
}

굉장히 많은 API 반환값이 있지만 .... 나는 간단히 돌아가는 것만 확인하고 후에 진행할 것이기 때문에 쉽게 짯다...

 


다음은 Repository로

API를 사용하는 함수를 모아놓은 class(라고 하는게 맞나??)이다.

class GithubRepository {

    private val api = ApiManager.githubApi

    /*fun getMyRepos(): Single<List<MyRepos>> =
        api.getMyRepos()

    fun getMyProfile(): Single<MyInfo> =
        api.getMyProfile()

    fun getGithubRepos(): Single<List<GithubRepo>> =
        api.getRepos()
*/
    fun getUser(): Maybe<User> =
        api.getUser()

    fun getFollowers() : Observable<List<User>> = api.getFollowers("wlgusdn")

    fun updateUser(): Completable =
        api.updatteUser()

/*
    fun searchGithubRepos(q: String): Single<List<GithubRepo>> =
        api.searchRepos(q)
            .map {
                it.asJsonObject.getAsJsonArray("items")
                    .map{
                        ApiManager.gson.fromJson(it, GithubRepo::class.java)!!
                    }
            }//map으로 필터역할을 해서 반환
            .subscribeOn(Schedulers.io())
*/
}

주석 처리된 함수들은 사용하지 않는 함수들이다.

Maybe , Single Observable Flowable 등의 많은 객체가 존재하지만 각각의 용도와 장단점은 천천히 공부해봐야겠다.

글을 쓰는 다음날 공부해봐야지...(사족으로 알고리즘과 안드로이드 두개만해도 하루가 끝나는데 자격증은 언제따지...)

 


ApiManager라고 불리는 Object를 생성한다.

이 객체에서는 App과 Network를 통신하게 하는 okHttp와 API를 사용할 수 있게 하는 Retrofit을 생성한다.

okhttp가 retrofit에 맞춰져있기에 사용한다고 알고 있으며, 이전에 사용하던 httpresponseurl 인가??? 긴 객체를 사용하여 Network와 통신하는 것은 잘 안쓰게 될것 같다... 왜냐면 Runnable생성하고 그 안에서 파라미터 넘겨주고 파라미터 받고.. 너무 귀찮고 또한 Retrofit의 장점인 Json으로 파싱까지 해주기 떄문에 결론은 Retrofit짱!!

 

또한 Interceptor은 2개를 생성가능한데...

쉽게 말하면

App->Network로 갈 때의 Intercept 동작을 하는 Interceptor

Network->App로 갈 때의 Intercept 동작을 하는 Interceptor 두개가 존재한다고 한다

나는 App->Network로 갈 때의 Intercept 동작을 하는 Interceptor만 생성했고 이건 Log를 보기 위함이다.

 

object ApiManager
{
    val gson =
        GsonBuilder()
            .setLenient()
            .create()

    private val okHttpClientBuilder =
        OkHttpClient.Builder()
            .addInterceptor(provideLoggingInterceptor())//App Intercept
            .build()

    private val githubAdapter by lazy {
        Retrofit.Builder()
            .baseUrl("https://api.github.com/")//기본적으로 통신할 API주소
            .client(okHttpClientBuilder)//통신을 할 주체?(Ok HTTP)
            .addConverterFactory(GsonConverterFactory.create(gson))//받은 응답을 Observable로 반환가능
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())//Json 형식으로 Data를 보내고 이를 파싱가능
            .build()
    }

    val githubApi: GithubApi by lazy {
        githubAdapter.create(
            GithubApi::class.java)
    }

    private fun provideLoggingInterceptor(): HttpLoggingInterceptor {
        val interceptor = HttpLoggingInterceptor()
        interceptor.level = HttpLoggingInterceptor.Level.BODY

        return interceptor
    }
}

메인 요리? 라고 볼 수 있는 Retrofit으로 API 통신하는 함수를 선언하는 인터페이스이다.

 

   @GET("user/starred/{owner}/{repo}")
    fun checkStar(
        @Path("owner") owner: String,
        @Path("repo") repo: String
    ): Completable

    @GET("users/{username}/followers")
    fun getFollowers(@Path("username") username: String):Observable<List<User>>
    

보이는 그대로이다 

여느 REST API처럼 GET PUT POST DELETE가 가능하며

@Path를 통해 파라미터로 쉽게 보낼수 있다.

 


이 뒤로는 내가 알고 있던 MVVM패턴이다.

 

MVVM에 대한 것들은 내 이전 포스팅에 많이 적혀있으니 정리하면 되겠다.

 

중요한 것으로는 ViewModel 코드인데

예전처럼 LiveData만을 사용하여 하지 않고

Retrofit을 사용하여 반환된 Observable객체(Observable,Single,Flowable...)들로 반환하여 쓰레드를 결정하고

subscribe()를 통해 수행할 UI작업을 결정한다.

이때 DataBinding으로 따로 UI적인 부분을 만지지않는 것이 Point~

 

 

class MainViewModel : ViewModel()
{

    val repository = GithubRepository()
    val disposable = CompositeDisposable()
    var text = MutableLiveData<List<User>>()

    fun getUserRepo(): Maybe<User> =
        repository.getUser()
            .subscribeOn(Schedulers.io())   //이전 stream에 대한 스레드 지정
            .observeOn(AndroidSchedulers.mainThread())  //아래에 대한 스레드 지정

    fun getFollowers() : Observable<List<User>> =
        repository.getFollowers()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())


    fun getf()
    {
        disposable.add(getFollowers().subscribe({ t: List<User>? ->
            text.value=t
        }))
    }


}

(+ 내가 하고싶었던 코드는 원래 아래의 코드이다)

fun getFollowers() : Observable<List<User>> =
        repository.getFollowers()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe{ t ->
            //LiveData 값 = t 
            }

아래의 코드를 하고 싶었는데 이상하게 계속해서 t가 disposable로 나왔다....

그래서 내일 공부할 것이다~

 

MVVM에 이어 Retrofit과 RxJava까지 조금씩 이해가 되고있다..

iOS는 언제 공부하지...

 

 

+ Recent posts