서버에 로그인 요청을 보내면 응답으로 아래처럼 토큰 정보들을 반환받는다. 로그인 이후에 사용자가 접근할 정보들은 모두 헤더에 AccessToken을 담아서 요청을 하도록 했다. 그래서 어떤 저장소에 담아두었다가 필요할 때 사용하게 될 것 같다.
{
"accessToken": "string",
"accessTokenExpireDate": 0,
"grantType": "string",
"refreshToken": "string"
}
로컬 저장소에 데이터를 저장해야 하는데, 선택지는 2가지가 있었다. ROOM or SharedPreferences 이 있다는 걸 알게 되었다.
ROOM 지속성 라이브러리는 DB클래스, DB테이블, DAO를 만든 후에 DB인스턴스 생성+메소드 활용 을 하는 방식이다.
SharedPreferences는 객체 형식이고 key-value, 메소드(읽기,쓰기,핸들 가져오기)를 사용하며, 액티비티에서 접근한다는 특징이 있다.
String 3개, Int 1개는 당근 간단한 정보니까 SharedPreferences를 사용하기로 했다.
로그인을 한 뒤에 AccessToken을 받아서 계속 가지고 다녀야 한다. 리뷰요청이나 사용자&팔로워 조회에 사용할 것을 고려해야 하기 때문이다!
싱글톤의 장단점은 아래와 같다.
<장점>
1.한 번의 객체 생성으로 여러 클래스가 공유됨 -> 메모리 낭비 방지, 다른 객체와 공유 가능
2.두 번째 호출부터는 객체 로딩시간이 줄어듦 -> 성능 향상
<단점>
1.남용할 경우 GC(Garbage Collector)가 자원을 회수하지 못함 -> 메모리 자원이 줄어든다
2.싱글톤 클래스에 과도한 데이터를 넣고 여러 클래스가 공유할 경우 -> 결합도 증가
SharedPreferences는 아무리 간단한 정보라도, 전역적으로 자주 사용되지 않으면 메모리 자원을 낭비할 가능성이 있다. 하지만 AccessToken은 거의 모든 API호출에 사용될 것으로 예상하기 때문에 이 패턴을 공유클래스의 형태로 사용해도 될 것 같다.
A라는 액티비티에서 SharedPreferences 객체를 사용해 데이터저장을 해야한다면, A의 생명주기의 onCreate()보다 먼저 초기화가 되어야 한다. 그래서 SharedPreferences 지역변수를 갖는 TokenSharedPreferences 클래스가 App이라는 공유클래스의 전역변수로 먼저 초기화되도록 했다.
구글링을 하다가 어디서 봤는지 기억이 안나지만...분명히 B라는 클래스 안에 SharedPreferences를 지역변수로 설정해두고 B를 공유클래스에 등록한 코드가 있었다. 단순한 전역객체or 변수가 아닌 SharedPreferences가 가진 특수한 메소드
내 프로젝트의 경우로 생각해보자면 공유클래스 App은 전역적인 자원의 공간이고, 그 안에 당장은 토큰정보만 등록했다. 하지만 나중에는 로그인유무, 간단한 설정값을 넣게 될 수도 있다. App 하위에서 바로 데이터 값에 접근하는 것보다 App.token_prefs.accessToken, App.login_prefs.login, App.setting.language 이런식으로 구체화된 클래스와 지역변수가 있는 게 더 실수를 줄일 수 있을 지도 모른다. (아님 말고... ㅎㅎ)
App.kt
package com.example.bangu
import android.app.Application
import com.example.bangu.login.data.TokenSharedPreferences
class App:Application() {
companion object{
lateinit var token_prefs : TokenSharedPreferences
}
override fun onCreate() {
token_prefs = TokenSharedPreferences(applicationContext)
super.onCreate()
}
}
SharedPreferences 객체에서 제공하는 putInt(), putString()등은 set()으로, getInt(),getString()등은 get()으로 통일해주었다.
그리고 SharedPreferences객체에서 제공하는 쓰기 메소드를 사용하고 나서는 꼭 apply()나 commit() 메소드로 변경을 저장해야 한다.
apply()는 메모리 내 SharedPreferences 객체를 즉시 변경하지만 업데이트를 디스크에 비동기적으로 씁니다. 또는 commit()을 사용하여 데이터를 디스크에 동기적으로 쓸 수 있습니다. 그러나 commit()은 동기적이므로 기본 스레드에서 호출하는 것을 피해야 합니다. UI 렌더링이 일시중지될 수 있기 때문입니다.
토큰 정보 저장이 동기적이어서 딱히 좋을 게 없는 것 같아서 apply()를 사용했다.
TokenSharedPreferences.kt
class TokenSharedPreferences(context: Context) {
private val prefsFilename = "token_prefs"
private val key_accessToken = "accessToken"
private val key_accessTokenExpireDate = "accessTokenExpireDate"
private val key_grantType = "grantType"
private val key_refreshToken = "refreshToken"
private val prefs:SharedPreferences = context.getSharedPreferences(prefsFilename,0)
var accessToken: String?
get() = prefs.getString(key_accessToken,"")
set(value) = prefs.edit().putString(key_accessToken,value).apply()
var accessTokenExpireDate:Int
get() = prefs.getInt(key_accessTokenExpireDate,0)
set(value) = prefs.edit().putInt(key_accessTokenExpireDate,value).apply()
var grantType:String?
get() = prefs.getString(key_grantType,"")
set(value) = prefs.edit().putString(key_grantType,value).apply()
var refreshToken: String?
get() = prefs.getString(key_refreshToken,"")
set(value) = prefs.edit().putString(key_refreshToken,value).apply()
}
처음엔 MVVM 철학(?)에 맞춰서 기능구현을 하려면 어떻게 해야 하는지 감이 잘 안왔다. 근데 이번에는 로컬 데이터 저장과 관련되어서 그런지 퍼즐을 쉽게 찾은 기분이라 삽질 덜한 상쾌함이 있다.
참고자료
공유클래스 관련
https://uroa.tistory.com/43
싱글톤 패턴 관련
https://tecoble.techcourse.co.kr/post/2020-11-07-singleton/
sharedPreferences 관련
https://koohee.tistory.com/12
https://developer.android.com/training/data-storage/shared-preferences?hl=ko