firebase with coroutines

문제 상황

firebase를 이용하다가 보면 라이브러리에서 제공하는 대부분의 메서드는 listener를 통해 비동기로 응답을 보내주고, 그 응답을 가공하여 사용해야 하는 경우가 많다

예를 들어

fun subscribeWalkingDistanceData(successCallback : () -> Unit, failureCallback : () -> Unit){
    Fitness.getRecordingClient(GlobalApplication.getApplicationContext(),mGoogleSignInAccount).subscribe(DataType.TYPE_DISTANCE_DELTA)
.addOnSuccessListener{
	Log.d(TAG,"successfully subscribe distance delta")
	successCallback()
}.addOnFailureListener{
		Log.e(TAG,"there was a problem subscribing walking distance data${it.localizedMessage}")
	failureCallback()
	}
}

위와 같이 유저가 이동한 거리에 대한 데이터를 subscribe를 하기 위해 다음과 같은 메서드를 작성했다면, 각각 성공 실패 리스너를 통해 firebase가 어떤 응답을 주는가에 따라서 매개변수로 받은 콜백함수를 실행시키는 형태로 코드를 작성 할 수 있다.

그런데 이렇게 코드를 작성하다 보면 콜백지옥의 문제가 생긴다.

depth가 하나일 때는 무난하게 callback함수를 매개변수로 넘겨 실행 시켜도 문제가 없지만

depth가 깊어질 수록 매개변수로 콜백을 계속 물고 들어가기 때문에 점점 복잡해지는 문제가 생긴다.

사실 이전에는 콜백으로 처리하였는데, 이번에 MVP패턴을 적용하면서 함수를 단순한 기능의 형태로 쪼개다보니 파이어베이스에서 받아온 응답 데이터를 그대로 return 해줘야 하는 함수들을 만들기 시작했고, 이에 다른 방법을 찾아보게 되었다.

해결 방법

구글링 결과 이를 해결하기 위해서 파이어베이스 메서드를 비동기처리하는 라이브러리를 추가하면 된다는 사실을 알게되었다.

https://github.com/Kotlin/kotlinx.coroutines/tree/master/integration/kotlinx-coroutines-play-services

  1. 모듈 수준 gradle에 dependency를 추가해주자

implementation **'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.6.2'**

  • 현재기준 1.6.2가 가장 최신 버전이다.
  1. dependency를 추가한 후 firebase에서 제공하는 라이브러리 안의 함수를 호출할 때 .await()를 사용할 수 있게 되어 응답이 올 때 까지 대기 할 수 있게 된다.
  2. 코드를 바꿔보자.
/*하루동안 걸은 거리 관련 함수 */
//피트니스 데이터(걸은 거리)
suspend fun subscribeWalkingDistanceData(){
    Fitness.getRecordingClient(GlobalApplication.getApplicationContext(),mGoogleSignInAccount).subscribe(DataType.TYPE_DISTANCE_DELTA)
     .addOnSuccessListener{
		}.addOnFailureListener{
	}

val response = Fitness.getRecordingClient(GlobalApplication.getApplicationContext(),mGoogleSignInAccount)
        .subscribe(DataType.TYPE_DISTANCE_DELTA).await()
}

위처럼 리스너에 콜백 함수를 지정하는 방식에서 밑에 await를 통해 응답이 올때까지 대기 한 후 response라는 변수에 응답으로 온 데이터를 할당하는 방식으로 바꿀 수 있다.

google play api는 google play service의 하나의 파트 임.

google fit api는 안드로이드 4.1 이상부터 호환.

Google Fit API 장점

  • 거의 실시간의 히스토리 데이터를 적은 에너지의 블루투스 디바이스로부터 가져옴
  • 활동들을 기록 할 수 있음
  • 데이터를 세션과 연관시킬 수 있음
  • 피트니스 목표를 설정 할 수 있음

sensor data

  • 유저의 하루 활동 관련한 정보들을 앱에서 제공한다면 (예를 들어 하루동안의 걸음수), sensor data는 사용자의 행동을 거의 실시간으로 보여주는데 사용이 가능함.

record data

  • 앱이 꺼져있는 상황에서도 계속해서 데이터를 적재 할 수 있는 subscribe 메서드를 제공함

historical data

  • 만약 유저가 과거의 활동들로부터 피트니스 데이터를 보여주기를 원한다면 history api를 사용하면 됨
  • 데이터 subscribe가 선행 되어야 historical data를 읽을 수 있음 ( 도큐먼트가 친절하지 않아서 테스트 후에 알게 됨), 데이터가 없으면 빈 리스트로 응답이 옴.

session data

  • 개발자가 일정한 시간의 세션을 설정하고 그 설정안에 들어가는 범위의 데이터를 가져올 수 있음

내가 하고자 하는 것

  1. subscribe를 통해 앱 사용자의 활동내역(현재는 걸음 수, 이후에 추가할 예정)들을 클라우드 형태의 google fit store에 앱을 종료했을 때도 마찬가지로 계속해서 저장해야 함.
  2. 데이터가 있는 날부터 일(00시~24시) 기준으로 걸음 수 데이터를 보여줄 수 있어야 함
  3. 배터리 최적화, 잠자기 모드 등 예기치 못한 상황에서도 정상적으로 데이터를 가져와야 함
  4. 데이터는 자정 기준으로 새로 시작함

사전 준비

  1. google fit api 는 Google API Console 에 프로젝트를 등록 및 client ID 를 발급 받은 후 사용해야함.
  2. client ID 발급 후 사용할 library에서 google fit api 를 사용 설정 해야 함.
  3. 테스트를 위해서 OAuth 동의 화면 탭의 테스트 사용자에 테스트를 수행 할 사용자 정보를 등록해야 함.
  4. 모듈 범위의 gradle 에
plugin {
    id("com.android.application")
}

...

dependencies {
        implementation("com.google.android.gms:play-services-fitness:21.1.0")
        implementation("com.google.android.gms:play-services-auth:20.2.0")
}

다음 과 같이 dependency를 추가하여 필요한 라이브러리를 다운로드 함

  1. https://developers.google.com/fit/android/get-started 를 참고하면 더 자세히 setup에 관해 나와 있음

구현 방법

  1. fitness option 객체를 만들어 내가 사용하고자 하는 data들을 추가함
funcreateFitnessStepOptions() : FitnessOptions{
	val fitnessOptions = FitnessOptions.builder()
        .addDataType(DataType.TYPE_STEP_COUNT_DELTA, FitnessOptions.ACCESS_READ)
        .addDataType(DataType.AGGREGATE_STEP_COUNT_DELTA, FitnessOptions.ACCESS_READ)
        .addDataType(DataType.TYPE_DISTANCE_DELTA, FitnessOptions.ACCESS_READ)
        .addDataType(DataType.AGGREGATE_DISTANCE_DELTA, FitnessOptions.ACCESS_READ)
        .build()
	return fitnessOptions
}

걸음수와 지금까지 걸은 거리에 대한 데이터를 얻기 위해 위처럼 4개의 fitness data type을 추가함.

  1. google account를 가져옴
fun getAccount(fitnessOptions: FitnessOptions) : GoogleSignInAccount{
	val account = GoogleSignIn.getAccountForExtension(GlobalApplication.getApplicationContext(),fitnessOptions)
	return account
}

getAccountForExtension 메서드를 이용하여 추가한 데이터에 대한 인증을 사용하는 google signin account를 얻음.

  1. google fit api를 사용하기 위한 권한 요청
fun checkHasGrantedDataAccess(account: GoogleSignInAccount, fitnessOptions: FitnessOptions, callback: () -> Unit){
    Log.i(TAG,"checkHasGrantedDataAccess${account.id}, fitnessOptions${fitnessOptions}")
if(!GoogleSignIn.hasPermissions(account, fitnessOptions)){
	//해당 google id가 permission이 허용되었는지 체크함.
	Log.d(TAG,"!GoogleSignIn.hasPermissions")
	GoogleSignIn.requestPermissions(mActivity,GOOGLE_FIT_PERMISSIONS_REQUEST_CODE, account, fitnessOptions)
  }else{
	  callback()
  }
}

account 가 접근하고자 하는 google fit api data에 대한 권한이 있는지 체크하고, 권한이 있다면 callback함수를 실행함. 없다면 권한을 요청함.

  1. 걸음 수 / 걸은 거리 데이터 구독 시작
//피트니스 데이터(걸음 수)구독셋팅
fun subscribeStepCountData(successCallback: () -> Unit?, failureCallback: () -> Unit?){
    Fitness.getRecordingClient(GlobalApplication.getApplicationContext(),mGoogleSignInAccount)
  .subscribe(DataType.TYPE_STEP_COUNT_DELTA)// no scopes are specified.
	.addOnSuccessListener{
	Log.i(TAG,"successfully subscribe step count data!!")
	  successCallback()
	}.addOnFailureListener{
	Log.w(TAG,"there was a problem subscribing step count data${it}")
	  failureCallback()
	}
}

  1. 걸음 수를 원하는 날짜부터 가져옴 (수정 중)
fun getStepCountForDays(days : Int){

	val cal = Calendar.getInstance()
	val now = Date()
	    cal.time= now
	val endTime = cal.timeInMillis
	
	cal.set(Calendar.HOUR_OF_DAY, -days)
	    cal.set(Calendar.MINUTE, 0)
	    cal.set(Calendar.SECOND, 0)
	    cal.set(Calendar.MILLISECOND, 0)
	
	val startTime = cal.timeInMillis
	
	val readRequest = DataReadRequest.Builder()
	        .aggregate(DataType.TYPE_STEP_COUNT_DELTA, DataType.AGGREGATE_STEP_COUNT_DELTA)
	        .bucketByTime(1, TimeUnit.DAYS)
	        .setTimeRange(startTime, endTime, TimeUnit.SECONDS)
	        .build()
	
	    Fitness.getHistoryClient(mActivity,mGoogleSignInAccount)
	    .readData(readRequest)
	    .addOnSuccessListener{
	response->
	for(dataSetinresponse.buckets.flatMap{ it.dataSets}) {
	  Log.i(TAG,"dataSet ==${dataSet}")
		//원하는 로직 추가 }
	}.addOnFailureListener{
		//실패 처리로직 추가
	}
}

원하는 날부터 현재까지 일 단위의 걸음 수 데이터를 가져오는 로직

결과

테스트를 해보려고 구글 피트니스 앱과 데이터를 비교한 결과 데이터가 잘 조회 되는 것을 확인하였음. 이 과정에서 한번 더 고민하고 액션을 취했던 부분은 혹시나 하는 마음에 매일 자정이 되기 찰나의 순간에 하루동안의 데이터를 조회해 오는 메서드 readDailyTotal 를 호출하여 데이터를 저장하는 방법과, DataReadRequest 객체에 startTime과 endTime을 설정해 그 기간동안의 data를 가져와 사용하는 방법 간 데이터 결과에 차이가 있는가에 대한 테스트를 했고 데이터간 차이가 없는 것을 확인하고 후자의 방식으로 채택했음. 그러나 startTime과 endTime을 잘못 설정하면 자정이 기준이 아니고 현재 시간을 기준으로 하루가 책정되기 때문에 startTime과 endTime을 설정을 자신이 필요한 데이터에 따라 정확히 할 필요가 있음.

Chronometer를 이용한 스톱워치 구현

<Chronometer
android:id="@+id/timer"
android:layout_width="200dp"
android:layout_height="40dp"
android:textSize="16sp"/>

xml파일에 다음과 같이 Chronometer 를 추가함.

나는 start, pause, stop 버튼에 각각 시작, 일시정지, 중지 기능을 추가해 넣었음.

전역변수로는 일시 정지를 누른 시간을 저장함

fun startTimer(){
	timer.base= SystemClock.elapsedRealtime() +pauseTime
  timer.start()
}

fun pauseTimer() {
	pauseTime=timer.base- SystemClock.elapsedRealtime()
	timer.stop()
}

fun resetTimer(){
	pauseTime= 0L
	timer.stop()
}

각각 버튼의 클릭리스너에 기능에 맞는 함수를 호출하면 됨.

여기서 timer는 Chronometer 임**.**

1. 하고자 하는 기능?

사용자가 자신이 갔던 경로를 트랙킹하여 지도에 표시해 타 사용자들에게 공유하는 기능

2. 프로세스

  1. 해당 화면 접근 시 런타임에 위치 권한 체크
  2. 위치 권한 허용 시 (3)으로 이동, 거부시 (5)로이동
  3. start 버튼 선택 시 현재 내 위치로 이동 및 tracking시작
  4. 설정한 시간 간격으로 위치를 얻어와 지도 위에 선으로 표시
  5. stop버튼 선택 시 tracking 종료
  6. 위치 권한 재 요청, 2번 거부 시 수동으로 앱 설정에 가서 허용하라는 다이얼로그 표시

3. 구현 방법

3-1) 런타임 위치 권한 체크

** 위치 권한 체크 시 유의할 점

내가 구현하고자 했던 기능은 앱이 백그라운드 상태에 가 있을 때에도 사용자의 위치를 추적해야 했다. 안드로이드 6부터는 앱에서 필요한 권한이 있을 때 런타임에서 권한을 받게 되었는데, 위치 권한은

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

manifest.xml

ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION 두개의 권한을 받아 사용하였다. 첫번째 줄에 선언한 권한은 네트워크 만을 이용하여 대략적인 위치 정보를 요청하는 권한이고 두번째 줄에 선언한 권한은 GPS와 네트워크를 이용하여 정확한 위치 정보를 요청하는 권한이다. ACCESS_FINE_LOCATION 권한은 반드시 ACCESS_COARSE_LOCATION권한이 허용되어야 한다.

백그라운드에서의 위치 권한은 안드로이드 10 미만으로는 따로 선언하지 않고 사용할 수 있다.

안드로이드 10 이상 부터는 위치 정보 사용이 포그라운드/ 백그라운드로 나누어지게 되는데 백그라운드에서 위치 권한을 사용하려면

<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

manifest.xml

ACCESS_BACKGROUND_LOCATION 권한을 따로 요청해야한다.

안드로이드 11이상 부터는 백그라운드에서 위치정보 사용 시 런타임 권한을 두 번 요청 해야한다.

포그라운드/ 백그라운드 위치 권한을 동시에 받으면 제대로 권한 체크가 되지 않고 무시하게 된다.

백그라운드 위치 권한은 왜 백그라운드 위치 권한을 받는지에 대한 다이얼로그와 함께 백그라운드 위치 권한을 사용자가 허용으로 설정할 수 있도록 설정 페이지로 보내주어야 하며 사용자 선택에 따른 결과 처리도 알맞게 따로 해주어야 한다.

3-2) 좌표 값 얻기

나는 Google Play 서비스 Location API를 사용하여 일정한 시간 주기로 좌표 값을 가져왔다. FusedLocationProviderClient가 제공하는 getCurrentLocation 메서드를 이용하여 현재 위치 값을 가져왔다. 문서에는 getLastLocation메서드를 사용해 좌표값을 가져오는 것을 권장한다고 적혀있지만 이전 프로젝트에서 사용했을 때 위치 설정을 막아 놓았다가 막 켠 상태라면 저장되어 있는 좌표 값이 없어 좌표가 null로 반환되는 오류가 있었다. 이 때문에 지속적으로 위치 정보를 가져오지 않고 한번만 가져와도 무방하다면 getCurrentLocation 메서드를 사용하는게 더 좋다고 생각한다.

계속해서 업데이트 되는 좌표를 얻기 위해서는 requestLocationUpdates 메서드를 사용하여 지속적으로 위치를 업데이트 할 수 있다. 위치 정보를 가져올 시간 간격, 정확도를 설정해 LocationRequest객체를 생성하여 설정한 간격으로 좌표값을 가져온다.

코루틴

코루틴 이란 ? 비동기적으로 실행되는 코드를 간소화 하기 위해 안드로이드에서 사용할 수 있는 동시 실행 설계 패턴입니다.

  • 경량 : 코루틴은 실행중인 스레드를 차단하지 않는 정지를 지원하므로 단일 스레드에서 많은 코루틴을 실행할 수 있습니다.
  • 메모리 누수 감소 : 구조화된 동시 실행을 사용하여 범위 내에서 작업을 실행합니다.
  • 기본으로 제공되는 취소 지원 : 실행 중인 코루틴 계층 구조를 통해 취소가 전달됩니다.
  • 이외 Jetpack 지원등..

백그라운드 스레드에서 실행

기본 스레드에서 네트워크 요청을 보내면 응답을 받을 때까지 스레드가 대기하거나 차단됩니다.

스레드가 차단될 경우 앱이 정지되고 응답없음(ANR) 대화상자가 표시될 수 있습니다.

이것의 해결방식으로 네트워크 요청을 새로운 코루틴을 만들고 I/O스레드에서 네트워크 요청을 실행할 수 있습니다.

example 1)

class LoginViewModel(
    private val loginRepository: LoginRepository
): ViewModel() {

    fun login(username: String, token: String) {
        // Create a new coroutine to move the execution off the UI thread
        viewModelScope.launch(Dispatchers.IO) {
            val jsonBody = "{ username: \\"$username\\", token: \\"$token\\"}"
            loginRepository.makeLoginRequest(jsonBody)
        }
    }
}
  • viewModelScope는 사전 정의된 CoroutineScope 입니다. 모든 코루틴은 코루틴범위 내에서 실행해야 합니다. CoroutineScope는 하나 이상의 관련 코루틴을 관리합니다.
  • launch는 코루틴을 만들고 함수 본문의 실행을 해당하는 디스패처에 전달하는 함수입니다.
  • Dispatchers.IO는 이 코루틴을 I/O작업용으로 예약된 스레드에서 해야 함을 나타냅니다.

기본 안전을 위해 코루틴 사용

기본 스레드에서 UI 업데이트를 차단하지 않는 한수를 기본 안전 함수로 간주합니다. 기본스레드에서 네트워크 요청을 하면 UI가 차단되므로 이 네트워크 요청이 담긴 함수는 기본 안전 함수가 아닙니다.

코루틴 라이브러리의 withContext() 함수를 사용하여 코루틴 실행을 다른 스레드로 이동합니다.

example 2)

class LoginRepository(...) {
    ...
    suspend fun makeLoginRequest(
        jsonBody: String
    ): Result<LoginResponse> {

        // Move the execution of the coroutine to the I/O dispatcher
        return withContext(Dispatchers.IO) {
            // Blocking network request code
        }
    }
}
  • withContext(Dispatcher.IO)는 코루틴 실행을 I/O스레드로 이동하여 호출 함수를 기본 안전 함수로 만들고 필요에 따라 UI를 업데이트 하도록 설정합니다.
  • makeLoginRequest 에는 supsend 키워드가 표시됩니다. suspend키워드는 코루틴 내에서 함수가 호출되도록 강제하는 Kotlin의 방법입니다.

example 3)

class LoginViewModel(
    private val loginRepository: LoginRepository
): ViewModel() {

    fun login(username: String, token: String) {

        // Create a new coroutine on the UI thread
        viewModelScope.launch {
            val jsonBody = "{ username: \\"$username\\", token: \\"$token\\"}"

            // Make the network call and suspend execution until it finishes
            val result = loginRepository.makeLoginRequest(jsonBody)

            // Display result of the network request to the user
            when (result) {
                is Result.Success<LoginResponse> -> // Happy path
                else -> // Show error in UI
            }
        }
    }
}

다시 example 1을 수정한 버전을 살펴봅시다.

  • launch가 Dispatchers.IO 매개변수를 사용하지 않습니다. Dispatcher를 launch에 전달하지 않으면 코루틴은 기본 스레드에서 실행됩니다.
  • 앱이 기본스레드의 View 레이어에서 login 함수를 호출합니다.
  • launch가 기본 스레드에서 네트워크 요청을 보낼 때 코루틴을 만들며, 코루틴이 실행을 시작합니다.
  • 코루틴 내에서 makeLoginRequest 호출은 withContext 블록 실행이 끝날 때 까지 코루틴의 추가 실행을 정지합니다 (ViewmodelScope의 코루틴의 코드를 makeLoginRequest의 withContext 블록 실행이 끝날때까지 일시 정지하고 끝난 후 부터 다시 실행합니다.)
  • withContext블록이 완료되면 네트워크 요청 결과와 함께 기본 스레드에서 실행을 재개합니다.

 

출처 : https://developer.android.com/kotlin/coroutines?hl=ko 

 

Android의 Kotlin 코루틴  |  Android 개발자  |  Android Developers

Android의 Kotlin 코루틴 코루틴은 비동기적으로 실행되는 코드를 간소화하기 위해 Android에서 사용할 수 있는 동시 실행 설계 패턴입니다. 코루틴은 Kotlin 버전 1.3에 추가되었으며 다른 언어에서 확

developer.android.com

 

맞닥드린 문제

최근 구글 플레이스토어 HTML 태그를 파싱해서 현재 스토어에 올라가있는 앱 버전을 가지고와 업데이트 체크를 하는 로직이 구글측에서 태그를 변경하면서 사용할 수 없게 되자 RemoteConfig로 config값을 옮기는 과정이 있었다. 그러던 중 업데이트 버전을 받아오는 로직을 독립적인 클래스의 함수로 만들어 의존성을 줄이고자 하였는데, listener안에서 적시에 받아온 값을 어떻게 사용하느냐에 대한 문제가 생겼다. 

 

생각해본 해결방법

매개변수에 함수를 전달하여 실행시키면 어떨까? 라는 생각이 들어 검색해봤고, 코틀린은 고차함수와 람다를 제공함을 알아내어 적용시켜보았다. 고차함수란 일반함수에 또다른 함수를 인자나 반환값으로 사용하는 함수이다. 

사용법과 설명은 https://play.kotlinlang.org/byExample/04_functional/01_Higher-Order%20Functions

 

Kotlin Playground: Edit, Run, Share Kotlin Code Online

 

play.kotlinlang.org

https://kotlinlang.org/docs/lambdas.html#function-literals-with-receiver

 

High-order functions and lambdas | Kotlin

 

kotlinlang.org

를 참고하면 좋다.

 

해결?

나는 인자에 () -> Unit 형의 함수를 넣어 원하는 곳에서 실행시키는 방식으로 이 문제를 해결하였다. 반환자료형이 없을 때는 Return을 Unit 형태로 선언하면 된다. 고차함수와 람다는 생소해서 잘 쓰지 않게되는 문법이지만, 필요할때가 종종 있어 인자에 함수를 넣는 형태로는 사용할때가 있다.. 모양이 어색해서 '이게 잘 쓰는건가?' 싶을때가 많지만 어쨌든 코틀린의 편리한 특징임은 틀림 없는듯 하다

 

해결해야 하는 문제점

1. 당시 우리 서비스는 쇼핑몰 아이템을 클릭 후 뒤로가기 시 다시 서버에 요청을 보내 데이터를 받아와 화면을 갱신해야하는 요구사항이 있었기 때문에 앞서 소개했던 keep-alive라던가, web storage에 모든 데이터를 넣는 방법이라던가, scrollBehavior을 통해 스크롤 위치를 return하는 방법을 그대로 사용하기 어려웠다.

 

2. 또, 알 수 없이 페이지를 렌더링 할 때 스크롤이 맨 아래로 향해있는 이슈가 있어 window.scrollTo(0,0) 코드를 넣어 가장 상위로 스크롤을 강제로 위치시키고 있었다.

 

어떻게 해결했는가?

1. Vue Router meta 정보에 savePosition, preservePosition 이라는 속성(boolean)을 할당하여, 페이지가 넘어갈 시 스크롤 위치를 저장하는 페이지와 다시 돌아올 때 저장했던 스크롤 위치를 불러오는 페이지를 설정할 수 있게 했다.

 

2. scrollBehavior에서 뒤로가기를 할 시, 이전 scroll위치를 받아와 sessionStorage에 저장하였고, 뒤로가기가 아닐시에는 (0,0)을 반환하여 처음 위치로 가게 하였다.

 

3. 라이프사이클 updated 훅에서 nextTick을 이용하여 모든 화면(자식 컴포넌트 포함)이 렌더링 된 후 window.scrollTo를 이용하여 sessionStorage에서 저장한 이전 scroll위치로 스크롤을 위치시켰다.

*자식 컴포넌트를 포함한 모든 화면이 update가 된 이후에 메서드를 호출하려면 mounted가 아닌 updated의 nextTick 안에서 해야한다*

 

4. 알 수 없이 페이지의 스크롤이 맨 아래로 향해있었던 이슈는 DOM에서 computed로 계산된 유저의 이름을 바로 사용하고 있었는데, 그 부분을 data의 변수에 할당하고 computed가 아닌 data안의 변수를 DOM에서 사용하는 방식으로 변경하니 이슈가 해결되었다. 그런데 정확한 원인은 아직 제대로 파악하지 못하였다..

 

 

다음은 카카오 로그인 릴리즈 해시키를 등록해보겠습니다.

 

일단 윈도우에서 릴리즈 해시키를 쉽게 얻으려면 크롬에서 제공해주는 리눅스 터미널 TermLinux를 사용해야합니다.

https://chrome.google.com/webstore/detail/termlinux-terminal-for-co/hlgjjjociconbkooaggfmjhalogadcee

 

TermLinux 명령 행을위한 터미널

소형 온라인 리눅스 가상 머신에 명령 행 콘솔을 제공하는 웹 터미널

chrome.google.com

1편에서 얻었던 SHA-1키를 복사한후 TermLinux 터미널에 

echo <SHA-1 키 입력> | xxd -r -p | openssl base64

명령어를 입력합니다.

 

명령어 입력후 나온 해시키를 복사하여

카카오 개발자 홈페이지 앱설정 -> 플랫폼 -> 키 해시에 복사한 해시키를 붙여 넣습니다.

 

플랫폼 선택
키 해시 추가

이제 완료되었으니 릴리즈로 테스트 해봅니다!

안드로이드 앱을 릴리즈 버전에서 구글로그인, 카카오 로그인 api를 사용하기 위해서는 

릴리즈키를 얻어 개발자 콘솔에 등록해야 합니다.

 

1. 릴리즈 키스토어를 생성합니다.

 

2. 커맨드창을 열고 jdk/bin 디렉토리로 이동합니다

keytool -list -v -keystore <키스토어 패스>

ex)

대부분의 jdk 디렉토리는 program files\java\밑에 있습니다

그리고 명령어를 입력해줍니다.

keytool -list -v -keystore <키스토어 path>

키스토어 path는 build -> Generate Signed Bundle or APK 를 선택하여 확인하실 수 있습니다.

결과로 받은 SHA1 을 복사해 줍니다.

SHA1을 복사하여 Google Cloud Platform 혹은 Firebase Console에  등록합니다.

 

Google Cloud Platform -> API 및 서비스 -> 사용자 인증 정보 선택 -> 사용자 인증 정보 만들기 -> android 선택 후 

패키지 명과 SHA-1 칸에 복사해둔 SHA1키를 붙여넣고, 저장을 누릅니다.

이제 연동이 끝났으니 릴리즈 버전에서 테스트를 해봅니다!

안드로이드에서 애플 로그인을 구현하려면 파이어베이스와 연동이 필요합니다.

★진행하기에 앞서서 파이어베이스에 프로젝트를 추가 해주세요!!★

 

1. 애플 개발자 사이트에 앱 설정하기

1-1. 앱 등록

위와 같은 순서로 진행 해주세요

 

 

1-2. 키 등록

로그인을 체크하고 configure 버튼을 눌러주세요

 

앞서 만든 앱 ID를 선택하고 configure를 save 해주세요
키를 만들고 다운로드 해주세요, 비공개키는 안전하게 프로젝트 디렉토리안에 옮겨둡시다

 

1-3. 서비스 ID 등록

 

순서대로 진행하여 service id를 생성해주세요

 

2. 파이어베이스 연동하기

authentication -> apple 로그인을 사용설정 해줍시다.

앞서 등록한 service id, key, 앱의 id를 각각 입력해주시고, 다운받은 비공개 키를 열어 복사 붙여넣기 해줍니다!

그리고 스위치를 on 으로 변경한 후 저장을 눌러주세요

 

구현 코드는 아래 사이트를 참고 해주세요.

https://firebase.google.com/docs/auth/android/apple?hl=ko 

 

Android에서 Apple을 통해 인증  |  Firebase

Firebase SDK를 사용하여 엔드 투 엔드 OAuth 2.0 로그인 과정을 실행하면 사용자가 Apple ID를 사용해 Firebase에 인증하도록 할 수 있습니다. 중요: Apple로 로그인하려면 사용자는 다음을 충족해야 합니다

firebase.google.com

 

+ Recent posts