Group Study (2023-2024)/Android 입문

[Android 입문] 5주차 스터디 - RoomDB, SharedPreferences, Datastore

샘스카이브 2023. 12. 25. 18:42

1.RoomDB

  • 앱은 DAO를 사용하여 데이터베이스의 데이터를 연결된 데이터 항목 객체의 인스턴스로 검색할 수 있게 한다.
  • Room의 3가지 주요 요소: 데이터베이스 클래스(포인터), 데이터 항목(테이블), 데이터 액세스 객체(메서드)

1) RoomDB 설치

  • MainActivity에서 Room 을 입력 후 alt+enter 로 add dependecy 한다.
  • build.gradle에서 plugins 에 kotlin-kapt 와 dependencies에 kapt를 추가한다.
plugins{
    id("kotlin-kapt")
}
dependencies {
    implementation("androidx.room:room-runtime:2.6.1")
    annotationProcessor("androidx.room:room-compiler:2.6.1")

    implementation("androidx.room:room-ktx:2.6.1")
    implementation ("androidx.room:room-runtime:2.6.1")
    kapt("androidx.room:room-compiler:2.6.1")
}

2) 데이터베이스  -AppDatabase.kt 

데이터베이스 구성을 정의하고 영구 데이터에 대한 앱의 기본 액세스 포인트 역할을 한다.

    @Database(entities = [Todo::class], version = 1)
    abstract class AppDatabase : RoomDatabase() {
        abstract fun todoDao(): TodoDao
    }

3) Todo 데이터 항목을 정의한다.  

@Entity
data class Todo( //data를 넣어줘야 getter setter toString 재정의를 안해도 됨
    var title: String
){
    @PrimaryKey(autoGenerate = true) var id: Int = 0 //var로 바꾸고 초기값 0
}

 4)데이터 액세스 객체(DAO) -Todo라는 DAO를 정의한다. 데이터베이스의 데이터와 상호작용하는 메서드들을 정의한다.

TodoDao.interface

@Dao
interface TodoDao {
    @Query("SELECT * FROM Todo")
    fun getAll(): List<Todo> //todo에서 모든 값 호출

    @Insert
    fun insert(todo: Todo)

    @Update
    fun  //todo객체 수정한거 업데이트 되도록
            update(todo: Todo)

    @Delete
    fun delete(todo: Todo)
}

5) MainActivity.kt

class MainActivity : AppCompatActivity() {
    private lateinit var binding : ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding=ActivityMainBinding.inflate(layoutInflater) //뷰를 객체화?
        setContentView(binding.root)

        val db = Room.databaseBuilder(
            applicationContext,
            AppDatabase::class.java, "database-name"
        ).allowMainThreadQueries()
            .build()

        binding.resultText.text=db.todoDao().getAll().toString()

        binding.addButton.setOnClickListener {
            db.todoDao().insert(Todo(binding.todoEdit.text.toString()))
            binding.resultText.text = db.todoDao().getAll().toString() //result가 보이게 함
        }
    }
}

cf) MainActivity.java와 비교

public class MainActivity extends AppCompatActivity {
    private EditText mTodoEditText;
    private TextView mResultTextView;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTodoEditText = findViewById(R.id.todo_edit);
        mResultTextView = findViewById(R.id.result_text);
//database 객체 생성
        final AppDatabase db = Room.databaseBuilder(this,AppDatabase.class, "todo-db") //전역으로 빼도 좋음
                .allowMainThreadQueries() //메인스레드에서 db조작을해도 괜찮은 코드 ->메인스레드에 db접근 코드 추가
                .build();

        mResultTextView.setText(db.todoDao().getAll().toString());
        findViewById(R.id.add_button).setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View view){
                db.todoDao().insert(new Todo(mTodoEditText.getText().toString())); //추가
                mResultTextView.setText(db.todoDao().getAll().toString()); //화면에 표시 -> 반복코드 메서드로 빼면 좋음
            }
        });
    }
}

할일을 추가할 수 있다.

2.SharedPreference

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

  • 파일 내부 또는 외부 저장소
  • 관계형 데이터베이스
  • sharedpreference : key, value 형태로 이용하고 내부적으로 XML 파일로 저장됨, 파일을 열고 닫을 필요없이 핸들러를 만들어서 간편하게 사용이 가능하다. -복잡한 데이터기록보다 간단한 데이터 기록에 용이

1)  뷰바인딩을 적용하고 save 버튼을 누르면 savePref(), load 버튼을 누르면 loadPref()가 실행되도록 한다.

2) 접근키는 key-value 형태로 저장하고 불러오기 때문에 companion object로 준비한다.

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_VSYNK = "vertical sync"
    }

3)savePref() - 데이터를 저장한다. 핸들러를 만들고 editor 객체를 만들고 put 함수로 key-value 쌍을 하나씩 저장한다. 

 private fun savePref(){
        val sharedPreferences = getSharedPreferences(KEY_PREFS, Context.MODE_PRIVATE) //sharedpref 에 핸들러 받아옴, mode_private: 이 앱에서만 사용가능
        val editor = sharedPreferences.edit() //editor라는 인스턴스 -> 하나씩 저장

        editor.putInt(KEY_GRAPHIC, binding.radioGraphics.checkedRadioButtonId)
        editor.putInt(KEY_MUSIC, binding.seekBarMusic.progress)
        editor.putInt(KEY_SFX, binding.seekBarSfx.progress)
        editor.putBoolean(KEY_VSYNK, binding.switchVsync.isChecked) // ischecked 의 true false->boolean editor에 boolean형태로 넣음

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

4)loadPref() - 저장된 데이터를 불러온다. 핸들러를 만들고 저장할 때 사용한 키와 동일한 키로 get함수로 값을 불러온다. 그 값을 버튼에 반영한다. - > if문으로 sharedpreferences 안에 데이터가 존재하는 지 확인한다. 

private fun loadPref(){
        val sharedPreferences = getSharedPreferences(KEY_PREFS, Context.MODE_PRIVATE) //핸들러 가져옴
        if(sharedPreferences.contains(KEY_GRAPHIC)){ //sharedpreferences 안에 key값이 있는지 -> put했던 것 get으로 가져와서 value에 넣음
            val graphicValue = sharedPreferences.getInt(KEY_GRAPHIC, 0) //defaultvalue는 값이 없을 경우, 하지만 이미 if문으로 걸렀기 때문에 의미가 없음
            val musicValue = sharedPreferences.getInt(KEY_MUSIC, 50)
            val sfxValue = sharedPreferences.getInt(KEY_SFX, 50)
            val vsyncValue = sharedPreferences.getBoolean(KEY_VSYNK, true)
            //property들로 값 반영
            binding.radioGraphics.check(graphicValue)
            binding.seekBarMusic.progress = musicValue
            binding.seekBarSfx.progress = sfxValue
            binding.switchVsync.isChecked = vsyncValue
            //반영이 잘되었는지 toast로 확인
            Toast.makeText(applicationContext, "Game setting has loaded", Toast.LENGTH_SHORT).show()
        }
    }

세팅을 저장하고 저장상태를 다시 로드할 수 있다.

3.Datastore

  • sharedPreference를 대체하는 개선된 신규 데이터 저장소 솔루션
  • 코틀린 코루틴과 flow를 기반으로 한 datastore는 <타입 객체 저장(Proto Datastore)> or <키-값쌍을 저장 (preference datastore)>를 제공
  • 복잡한 데이터 지원 -> Room, 소규모 단순 데이터 세트 -> DataStore

1)build.gradle의 dependencies에 다음 코드를 추가한다.

 //datastore
    implementation ("androidx.datastore:datastore-preferences:1.0.0")

    //coroutines
    implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
    implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4")

    //lifecycle
    implementation ("androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1")
    implementation ("androidx.lifecycle:lifecycle-runtime-ktx:2.5.1")
    implementation ("androidx.lifecycle:lifecycle-livedata-ktx:2.5.1")

    //lottie
    implementation ("com.airbnb.android:lottie:3.4.0")

2)DataStoreManager.kt

class DataStoreManager(context: Context) {
//데이터 스토어 생성
    private val Context.dataStore : DataStore<Preferences> by preferencesDataStore(name="THEME_KEY")
    private val dataStore = context.dataStore //데이터스토어 사용할 수 있는 참조를 저장

    companion object{ //테마와 관련된 키를 정의
        val darkModeKey = booleanPreferencesKey("DARK_MODE_KEY")
    }
//테마 설정
    suspend fun setTheme(isDarkMode : Boolean){
        dataStore.edit { pref->
            pref[darkModeKey] = isDarkMode
        }
    }
//테마 가져옴 -> 데이터가 변경될 때마다 알림
    fun getTheme() : Flow<Boolean> {
        return dataStore.data
            .catch { exception ->
                if(exception is IOException){
                    emit(emptyPreferences()) //예외 발생시 빈 preference 반환
                }
                else{
                    throw exception
                }
            }
            //preference 에서 dark_mode_key에 해당하는 값을 추출해서 반환
            //엘비스 연산자('?:')는 null체크에 사용됨, null이 아닌 경우 왼쪽 피연산자, null이면 오른쪽 값 반환
            .map{ pref ->
                val uiMode = pref[darkModeKey] ?: false
                uiMode

            }
    }
    
}

3) viewmodel 설정

class MainViewModel(application: Application) : AndroidViewModel(application) {
    //datastoremanager 초기화
    val dataStore = DataStoreManager(application)
    
    //테마값을 flow로 받아와서 livedata로 노출
    //이를 통해 ui에서 테마 값을 관찰하고 데이터가 변경될 때마다 ui를 업데이트 할 수 있음
    val getTheme = dataStore.getTheme().asLiveData(Dispatchers.IO)
    
    //'setTheme'메서드는 ui에서 전달받은 테마 값을 datasotre를 통해 설정
    //비동기 작업을 안전하게 수행(viewModelScope.launch)->앱의 메인 스레드를 차단하지 않고 데이터 변경가능
    fun setTheme(isDarkMode : Boolean){
        viewModelScope.launch{
        //datastoremanager를 통해 테마를 설정
            dataStore.setTheme(isDarkMode)
        }
    }


}

 

 

마지막으로 저장한 mode가 저장된다.

 


SharedPreferences로 앱 설정값 저장하고 불러오기 (cliearl.github.io)

Room을 사용하여 로컬 데이터베이스에 데이터 저장  |  Android 개발자  |  Android Developers

https://developer.android.com/codelabs/android-preferences-datastore?hl=ko#0

How to use DataStore Preferences in Kotlin - YouTube