SharedPreferences로 앱 설정값 저장하고 불러오기

이번 포스팅에서는 SharedPreferences를 사용하는 법에 대해 알아보도록 하겠습니다.

# 데이터를 앱에 저장하는 방법

데이터를 앱에 저장하는 방법에는 크게 세가지가 있습니다.

  • 파일 I/O (내부 또는 외부 저장소)
    • 접근 권한을 획득하고 파일을 열었다 닫았다 하는 수고가 필요함
  • 관계형 데이터베이스
    • SQLite 등을 이용해 복잡한 관계형 데이터를 저장할 수 있음
    • 간단한 데이터를 저장할거라면 구축과 관리에 많은 시간과 노려이 요구됨
  • SharedPreference
    • Key/Value 형태로 이용함
    • 내부적으로는 XML 파일로 저장됨
    • 파일을 열고 닫을 필요 없이 핸들러를 만들어서 간편하게 사용가능함

SharedPreferences는 보통 복잡한 데이터를 기록하기보다는, 게임의 환경설정이라든지 그런 단순한 내용을 저장하는데 적절한 저장공간이라고 생각하면 됩니다. 그래서 이번에는 SharedPreferences를 사용해서 게임설정값을 저장하고 복원하는 앱을 만들어보도록 하겠습니다.

# SharedPreferences 사용법

  • getSharedPreferences(KEY, MODE)로 핸들러를 받아옴
    • MODE에는 MODE_PRIVATE, MODE_WORLD_READABLE, MODE_WORLD_WRITABLE, MODE_MULTI_PROCESS 가 있다
    • 작성한 앱에서만 접근 가능하게 하는 MODE_PRIVATE 를 사용
  • 데이터 기록
    • 에디터를 써서 기록할 데이터를 메모리에 올림
    • commit 혹은 apply로 파일에 기록

# 화면 구성

위에서부터 순서대로 화면의 요소를 구성하는 여러가지 종류의 버튼을 배치하여 값을 변화시킬수 있도록 구성하였습니다. 앱이 처음 실행되었을 때 표시되는 기본값도 여기서 설정해줍니다. 그리고 마지막으로 SAVE버튼과 LOAD 동작을 하는 버튼 두개도 배치합니다.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="50dp"
        android:layout_marginEnd="16dp"
        android:text="Game Settings"
        android:textSize="30sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="40dp"
        android:layout_marginEnd="16dp"
        android:text="Graphic Quality"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

    <RadioGroup
        android:id="@+id/radio_graphics"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="20dp"
        android:layout_marginEnd="16dp"
        android:orientation="horizontal"
        android:checkedButton="@id/radioButton_medium"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView2">

        <RadioButton
            android:id="@+id/radioButton_low"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Low" />

        <RadioButton
            android:id="@+id/radioButton_medium"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Medium" />

        <RadioButton
            android:id="@+id/radioButton_high"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="High" />
    </RadioGroup>

    <TextView
        android:id="@+id/textView3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="40dp"
        android:layout_marginEnd="16dp"
        android:text="Music Volume"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/radio_graphics" />

    <SeekBar
        android:id="@+id/seekBar_music"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="20dp"
        android:layout_marginEnd="16dp"
        android:max="100"
        android:progress="50"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView3" />

    <TextView
        android:id="@+id/textView4"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="40dp"
        android:layout_marginEnd="16dp"
        android:text="SFX Volume"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/seekBar_music" />

    <SeekBar
        android:id="@+id/seekBar_sfx"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="20dp"
        android:layout_marginEnd="16dp"
        android:max="100"
        android:progress="50"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView4" />

    <TextView
        android:id="@+id/textView5"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="40dp"
        android:layout_marginEnd="16dp"
        android:text="Enable Vertical Sync"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/seekBar_sfx" />

    <androidx.appcompat.widget.SwitchCompat
        android:id="@+id/switch_vsync"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="30dp"
        android:layout_marginEnd="16dp"
        android:checked="true"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/seekBar_sfx" />

    <Button
        android:id="@+id/button_save"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="70dp"
        android:text="SAVE"

        app:layout_constraintEnd_toStartOf="@+id/button_load"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView5" />

    <Button
        android:id="@+id/button_load"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="70dp"
        android:text="LOAD"

        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/button_save"
        app:layout_constraintTop_toBottomOf="@+id/textView5" />

</androidx.constraintlayout.widget.ConstraintLayout>

# 버튼에 클릭리스너 설정

View Binding을 적용하고 SAVE 버튼을 눌렀을 때는 savePref, LOAD 버튼을 눌렀을때는 loadPref를 실행하도록 합니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.buttonSave.setOnClickListener {
            savePref()
        }
        binding.buttonLoad.setOnClickListener {
            loadPref()
        }
    }

    private fun savePref() {

    }

    private fun loadPref() {

    }
}

# 접근키 작성

SharedPreferences는 Key-Value 형태로 값을 저장하고 불러오기 때문에 이 때 사용할 Key 값을 companion object로 준비해서 편하게 사용할 수 있도록 준비합니다.

1
2
3
4
5
6
7
companion object {
    private const val KEY_PREFS = "game_settings"
    private const val KEY_GRAPHIC = "graphic_quality"
    private const val KEY_MUSIC = "music_volume"
    private const val KEY_SFX = "sfx_volume"
    private const val KEY_VSYNC = "vertical_sync"
}

# savePref 작성

데이터를 저장하는 부분을 작성합니다. 핸들러를 만들고 Editor 객체를 만들어서 put 함수로 Key-Value 쌍을 저장하도록 했습니다. 버튼의 값은 개체가 가진 프로퍼티로 가져올 수 있습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
private fun savePref() {
    val sharedPreferences = getSharedPreferences(KEY_PREFS, Context.MODE_PRIVATE)
    val editor = sharedPreferences.edit()

    editor.putInt(KEY_GRAPHIC, binding.radioGraphics.checkedRadioButtonId)
    editor.putInt(KEY_MUSIC, binding.seekBarMusic.progress)
    editor.putInt(KEY_SFX, binding.seekBarSfx.progress)
    editor.putBoolean(KEY_VSYNC, binding.switchVsync.isChecked)

    editor.apply()
    Toast.makeText(applicationContext, "Game settings has saved", Toast.LENGTH_SHORT).show()
}

# loadPref() 작성

저장된 데이터를 불러오는 부분을 작성합니다. 저장할때와 마찬가지로 핸들러를 만들고 저장할때 사용한 키와 동일한 키를 사용해서 get함수로 값을 불러온 뒤 그 값을 버튼에 반영합니다. 이 때 contains를 사용해서 SharedPreferences 데이터가 존재하는지를 우선 확인하고 작업을 수행하도록 했습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
private fun loadPref() {
    val sharedPreferences = getSharedPreferences(KEY_PREFS, Context.MODE_PRIVATE)
    if (sharedPreferences.contains(KEY_GRAPHIC)) {
        val graphicValue = sharedPreferences.getInt(KEY_GRAPHIC, 0)
        val musicValue = sharedPreferences.getInt(KEY_MUSIC, 50)
        val sfxValue = sharedPreferences.getInt(KEY_SFX, 50)
        val vsyncValue = sharedPreferences.getBoolean(KEY_VSYNC, true)

        binding.radioGraphics.check(graphicValue)
        binding.seekBarMusic.progress = musicValue
        binding.seekBarSfx.progress = sfxValue
        binding.switchVsync.isChecked = vsyncValue

        Toast.makeText(applicationContext, "Game settings has loaded", Toast.LENGTH_SHORT).show()
    }

이렇게 SharedPreferences를 써서 앱의 데이터를 저장하고 불러오는 법에 대해 알아보았습니다.

Built with Hugo
Theme Stack designed by Jimmy