refactor(preferences): DataStore 설정 저장 안정성을 높인다
This commit is contained in:
@@ -1,12 +1,10 @@
|
||||
package kr.co.vividnext.sodalive.chat.talk.room
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.edit
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
@@ -41,7 +39,6 @@ class ChatBackgroundPickerDialogFragment : DialogFragment() {
|
||||
private var characterId: Long = 0L
|
||||
private var profileUrl: String = ""
|
||||
|
||||
private val prefsName = "chat_room_prefs"
|
||||
private fun bgImageIdKey(roomId: Long) = "chat_bg_image_id_room_$roomId"
|
||||
|
||||
private lateinit var adapter: BgAdapter
|
||||
@@ -87,8 +84,7 @@ class ChatBackgroundPickerDialogFragment : DialogFragment() {
|
||||
|
||||
private fun loadData() {
|
||||
// 초기 선택: 저장된 이미지 ID 사용
|
||||
val prefs = requireActivity().getSharedPreferences(prefsName, Context.MODE_PRIVATE)
|
||||
val savedId = prefs.getLong(bgImageIdKey(roomId), -1L)
|
||||
val savedId = ChatRoomPreferenceManager.getLong(bgImageIdKey(roomId), -1L)
|
||||
selectedId = if (savedId > 0) savedId else null
|
||||
items.clear()
|
||||
|
||||
@@ -124,12 +120,10 @@ class ChatBackgroundPickerDialogFragment : DialogFragment() {
|
||||
}
|
||||
|
||||
private fun saveAndApply(item: BgItem) {
|
||||
val prefs = requireActivity().getSharedPreferences(prefsName, Context.MODE_PRIVATE)
|
||||
prefs.edit {
|
||||
if (item.id > 0) putLong(
|
||||
bgImageIdKey(roomId),
|
||||
item.id
|
||||
) else remove(bgImageIdKey(roomId))
|
||||
if (item.id > 0) {
|
||||
ChatRoomPreferenceManager.putLong(bgImageIdKey(roomId), item.id)
|
||||
} else {
|
||||
ChatRoomPreferenceManager.remove(bgImageIdKey(roomId))
|
||||
}
|
||||
(activity as? ChatRoomActivity)?.setChatBackground(item.url)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import android.text.TextWatcher
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
@@ -55,12 +54,10 @@ class ChatRoomActivity : BaseActivity<ActivityChatRoomBinding>(
|
||||
// 5.3 헤더 데이터 (7.x 연동 전까지는 nullable 보관)
|
||||
private var characterInfo: CharacterInfo? = null
|
||||
|
||||
// 5.4 SharedPreferences (안내 메시지 접힘 상태 저장)
|
||||
private val prefs by lazy { getSharedPreferences("chat_room_prefs", MODE_PRIVATE) }
|
||||
private fun noticePrefKey(roomId: Long) = "chat_notice_hidden_room_${roomId}"
|
||||
private fun isNoticeHidden(): Boolean = prefs.getBoolean(noticePrefKey(roomId), false)
|
||||
private fun isNoticeHidden(): Boolean = ChatRoomPreferenceManager.getBoolean(noticePrefKey(roomId), false)
|
||||
private fun setNoticeHidden(hidden: Boolean) {
|
||||
prefs.edit { putBoolean(noticePrefKey(roomId), hidden) }
|
||||
ChatRoomPreferenceManager.putBoolean(noticePrefKey(roomId), hidden)
|
||||
}
|
||||
|
||||
override fun setupView() {
|
||||
@@ -866,7 +863,7 @@ class ChatRoomActivity : BaseActivity<ActivityChatRoomBinding>(
|
||||
}
|
||||
|
||||
fun applyBackgroundVisibility() {
|
||||
val visible = prefs.getBoolean("chat_bg_visible_room_$roomId", true)
|
||||
val visible = ChatRoomPreferenceManager.getBoolean("chat_bg_visible_room_$roomId", true)
|
||||
binding.ivBackgroundProfile.isVisible = visible
|
||||
binding.viewCharacterDim.isVisible = visible
|
||||
}
|
||||
@@ -885,7 +882,7 @@ class ChatRoomActivity : BaseActivity<ActivityChatRoomBinding>(
|
||||
private fun bgImageIdPrefKey(): String = "chat_bg_image_id_room_$roomId"
|
||||
|
||||
private fun getSavedBackgroundImageId(): Long? {
|
||||
val id = prefs.getLong(bgImageIdPrefKey(), -1L)
|
||||
val id = ChatRoomPreferenceManager.getLong(bgImageIdPrefKey(), -1L)
|
||||
return if (id > 0) id else null
|
||||
}
|
||||
|
||||
@@ -912,9 +909,7 @@ class ChatRoomActivity : BaseActivity<ActivityChatRoomBinding>(
|
||||
"chat_bg_image_id_room_$roomId",
|
||||
noticePrefKey(roomId)
|
||||
)
|
||||
prefs.edit {
|
||||
keys.forEach { remove(it) }
|
||||
}
|
||||
ChatRoomPreferenceManager.removeAll(keys)
|
||||
}
|
||||
|
||||
fun onResetChatRequested() {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package kr.co.vividnext.sodalive.chat.talk.room
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@@ -11,7 +10,6 @@ import android.widget.RelativeLayout
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.switchmaterial.SwitchMaterial
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import androidx.core.content.edit
|
||||
|
||||
/**
|
||||
* 채팅방 우측 상단 더보기 버튼 클릭 시 표시되는 전체화면 다이얼로그.
|
||||
@@ -42,13 +40,12 @@ class ChatRoomMoreDialogFragment : DialogFragment() {
|
||||
// 닫기 버튼
|
||||
view.findViewById<ImageView>(R.id.iv_close)?.setOnClickListener { dismiss() }
|
||||
|
||||
val prefs = requireActivity().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||
val bgKey = bgPrefKey(roomId)
|
||||
|
||||
val switch = view.findViewById<SwitchMaterial>(R.id.sw_background)
|
||||
switch?.isChecked = prefs.getBoolean(bgKey, true)
|
||||
switch?.isChecked = ChatRoomPreferenceManager.getBoolean(bgKey, true)
|
||||
switch?.setOnCheckedChangeListener { _, isChecked ->
|
||||
prefs.edit { putBoolean(bgKey, isChecked) }
|
||||
ChatRoomPreferenceManager.putBoolean(bgKey, isChecked)
|
||||
(activity as? ChatRoomActivity)?.applyBackgroundVisibility()
|
||||
}
|
||||
|
||||
@@ -76,7 +73,6 @@ class ChatRoomMoreDialogFragment : DialogFragment() {
|
||||
|
||||
companion object {
|
||||
private const val ARG_ROOM_ID = "arg_room_id"
|
||||
private const val PREFS_NAME = "chat_room_prefs"
|
||||
private fun bgPrefKey(roomId: Long) = "chat_bg_visible_room_$roomId"
|
||||
|
||||
fun newInstance(roomId: Long): ChatRoomMoreDialogFragment {
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
package kr.co.vividnext.sodalive.chat.talk.room
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.SharedPreferencesMigration
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.longPreferencesKey
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStoreFile
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
object ChatRoomPreferenceManager {
|
||||
private const val LEGACY_PREFERENCES_NAME = "chat_room_prefs"
|
||||
private const val DATASTORE_FILE_NAME = "chat_room_preferences"
|
||||
|
||||
private lateinit var dataStore: DataStore<Preferences>
|
||||
private val appScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
private val preferenceState = MutableStateFlow<Map<String, Any?>>(emptyMap())
|
||||
private val initLock = Any()
|
||||
private var observerJob: Job? = null
|
||||
|
||||
@Volatile
|
||||
private var initialized = false
|
||||
|
||||
fun init(context: Context) {
|
||||
if (initialized) {
|
||||
return
|
||||
}
|
||||
|
||||
synchronized(initLock) {
|
||||
if (initialized) {
|
||||
return
|
||||
}
|
||||
|
||||
val appContext = context.applicationContext
|
||||
if (!this::dataStore.isInitialized) {
|
||||
dataStore = androidx.datastore.preferences.core.PreferenceDataStoreFactory.create(
|
||||
migrations = listOf(SharedPreferencesMigration(appContext, LEGACY_PREFERENCES_NAME)),
|
||||
produceFile = { appContext.preferencesDataStoreFile(DATASTORE_FILE_NAME) }
|
||||
)
|
||||
}
|
||||
|
||||
val initialPreferences = runBlocking {
|
||||
dataStore.data.first()
|
||||
}
|
||||
updateState(initialPreferences)
|
||||
initialized = true
|
||||
|
||||
observerJob?.cancel()
|
||||
observerJob = appScope.launch {
|
||||
dataStore.data.collect { preferences ->
|
||||
updateState(preferences)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateState(preferences: Preferences) {
|
||||
preferenceState.value = preferences.asMap().entries.associate { (key, value) ->
|
||||
key.name to value
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
fun resetForTest() {
|
||||
synchronized(initLock) {
|
||||
observerJob?.cancel()
|
||||
observerJob = null
|
||||
preferenceState.value = emptyMap()
|
||||
initialized = false
|
||||
}
|
||||
}
|
||||
|
||||
fun getBoolean(key: String, defaultValue: Boolean): Boolean {
|
||||
ensureInitialized()
|
||||
return preferenceState.value[key] as? Boolean ?: defaultValue
|
||||
}
|
||||
|
||||
fun putBoolean(key: String, value: Boolean) {
|
||||
ensureInitialized()
|
||||
preferenceState.update { state ->
|
||||
val mutable = state.toMutableMap()
|
||||
mutable[key] = value
|
||||
mutable
|
||||
}
|
||||
|
||||
appScope.launch {
|
||||
dataStore.edit { preferences ->
|
||||
preferences[booleanPreferencesKey(key)] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getLong(key: String, defaultValue: Long): Long {
|
||||
ensureInitialized()
|
||||
return preferenceState.value[key] as? Long ?: defaultValue
|
||||
}
|
||||
|
||||
fun putLong(key: String, value: Long) {
|
||||
ensureInitialized()
|
||||
preferenceState.update { state ->
|
||||
val mutable = state.toMutableMap()
|
||||
mutable[key] = value
|
||||
mutable
|
||||
}
|
||||
|
||||
appScope.launch {
|
||||
dataStore.edit { preferences ->
|
||||
preferences[longPreferencesKey(key)] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getString(key: String, defaultValue: String): String {
|
||||
ensureInitialized()
|
||||
return preferenceState.value[key] as? String ?: defaultValue
|
||||
}
|
||||
|
||||
fun putString(key: String, value: String) {
|
||||
ensureInitialized()
|
||||
preferenceState.update { state ->
|
||||
val mutable = state.toMutableMap()
|
||||
mutable[key] = value
|
||||
mutable
|
||||
}
|
||||
|
||||
appScope.launch {
|
||||
dataStore.edit { preferences ->
|
||||
preferences[stringPreferencesKey(key)] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun remove(key: String) {
|
||||
ensureInitialized()
|
||||
preferenceState.update { state ->
|
||||
val mutable = state.toMutableMap()
|
||||
mutable.remove(key)
|
||||
mutable
|
||||
}
|
||||
|
||||
appScope.launch {
|
||||
dataStore.edit { preferences ->
|
||||
val prefKey = preferences.asMap().keys.firstOrNull { it.name == key } ?: return@edit
|
||||
preferences.remove(prefKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun removeAll(keys: Collection<String>) {
|
||||
ensureInitialized()
|
||||
if (keys.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
preferenceState.update { state ->
|
||||
val mutable = state.toMutableMap()
|
||||
keys.forEach { key ->
|
||||
mutable.remove(key)
|
||||
}
|
||||
mutable
|
||||
}
|
||||
|
||||
appScope.launch {
|
||||
dataStore.edit { preferences ->
|
||||
val mapKeys = preferences.asMap().keys
|
||||
keys.forEach { keyName ->
|
||||
val prefKey = mapKeys.firstOrNull { it.name == keyName } ?: return@forEach
|
||||
preferences.remove(prefKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ensureInitialized() {
|
||||
check(initialized) { "ChatRoomPreferenceManager is not initialized." }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user