1. SharedPreferences
SP 사용법은 아주 간단하다. 아래 코드 참고.
class SharedPreferencesFragment : Fragment() {
private var _binding: FragmentFirstBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
_binding = FragmentFirstBinding.inflate(layoutInflater)
loadData() // 데이터 가져오기
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.btnSave.setOnClickListener {
saveData()
Snackbar.make(
requireContext(), binding.root,
"데이터 저장 완료", Snackbar.LENGTH_SHORT
).apply {
anchorView = binding.viewSnack // TabLayout 위에 배치
show()
}
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null // (중요) 바인딩 객체를 메모리에세 해제
}
// 데이터 저장하기
private fun saveData() {
val prefSingleFile = activity?.getPreferences(Context.MODE_PRIVATE) // Mode_PRIVATE = 0
val pref = activity.let {
it!!.getSharedPreferences("data", 0)
}
val edit = pref.edit()
edit.putString("name", binding.etInput.text.toString())
edit.apply()
}
// 데이터 가져오기
private fun loadData() {
val pref = activity.let {
it!!.getSharedPreferences("data", 0)
}
val name = pref.getString("name", "")!!
binding.etInput.setText(name)
}
}
2. Room
- Room 이란? : SQLite DB를 쉽게 사용할 수 있는 DB 객체 매핑 라이브러리
- 쉽게 Query를 사용할 수 있는 API를 제공
- Query를 컴파일 시간에 검증함 -> ?
- Query 결과를 LiveData, Flow 등으로 지정하여 DB가 변경될 때마다 쉽게 UI에 적용할 수 있음
- Room의 주요 3요소
- 1. @Database : 클래스를 DB로 지정함
- class가 RommDB를 상속받은 클래스 여야함
- Room.databaseBuilder를 이용하여 인스턴스 객체를 생성함
- 2. @Entity : 클래스를 테이블 스키마로 지정함|
- 3. @Dao(Data Access Object) : 클래스를 DAO로 지정함
- 기본적인 insert, delete, update SQL은 자동으로 만들어주며, 복잡한 SQL은 직접 만들 수 있음
(1) 기본작업 gradle 설정 (최신 room 버전인 2.6.1 사용)
plugins {
id("kotlin-kapt")
}
dependencies {
val room_version = "2.6.1"
implementation("androidx.room:room-runtime:$room_version")
annotationProcessor("androidx.room:room-compiler:$room_version")
kapt("androidx.room:room-compiler:$room_version")
// optional - Kotlin Extensions and Coroutines support for Room
implementation("androidx.room:room-ktx:$room_version")
// optional - Test helpers
testImplementation("androidx.room:room-testing:$room_version")
}
(2) Entity 생성
@Entity(tableName = "student_table")
data class Student(
@PrimaryKey
@ColumnInfo(name = "student_id")
val id: Int,
val name: String,
)
(3) DAO 생성
@Dao
interface StudentDao {
//
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(student: Student)
// TODO: 오류 수정 필요 - 컴파일 시 오류 발생함
// @Insert(onConflict = OnConflictStrategy.REPLACE)
// suspend fun insertAll(vararg students: List<Student>)
@Query("SELECT * FROM student_table")
fun getAll(): LiveData<List<Student>>
@Query("SELECT * FROM student_table WHERE name = :name")
suspend fun findByName(name: String): List<Student>
@Delete
suspend fun delete(student: Student)
}
(4) Database 객체 생성 (싱글톤, 마이그레이션 - DB 버전 관리 코드 포함)
@Database(
entities = [Student::class],
exportSchema = false,
version = 1
)
abstract class StudentDatabase: RoomDatabase() {
abstract fun getStudentDao(): StudentDao
companion object {
private var INSTANCE: StudentDatabase? = null
private var MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
}
}
fun getDatabase(context: Context): StudentDatabase {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
context,
StudentDatabase::class.java,
"student_database"
)
.addMigrations(MIGRATION_1_2)
.build()
}
return INSTANCE as StudentDatabase
}
}
}
(5) DB Call 단 Fragment 코드 (onViewCreated 내부 코드)
1. LiveData 활용
val allStudent = studentDao.getAll()
// allStudent의 반환 타입이 LiveData 이므로 observe 사용이 가능하다
// livedata 사용 시, observe를 통해 데이터 갱신을 알 수 있으므로
// 자체가 코루틴 스코프가 된다.
allStudent.observe(requireActivity()) {
val sb = StringBuilder().apply {
for ((id, name) in it) {
append(id, "-", name, "\n")
}
}
binding.textStudentList.text = sb.toString()
}
2. 일반적인 CoroutineScope 사용법
with(binding) {
addStudent.setOnClickListener {
val id = editStudentId().toInt()
val name = editStudentName()
// DB에 추가
if (id > 0 && name.isNotEmpty()) {
CoroutineScope(Dispatchers.IO).launch {
val student = Student(id, name)
studentDao.insert(student)
}
}
// UI 데이터 초기화
listOf(editStudentName, editStudentId).forEach {
it.text = null
}
}
}
3. runBlocking 사용법
runBlocking은 호출한 위치를 Blocking 시키는 특징이 있어 사용에 유의해야함
자세한 내용: https://thdev.tech/kotlin/2020/12/15/kotlin_effective_15/
- 일반적인 CoroutineScope나 ViewModelScope만 쓰는 게 좋을 듯
// launch : Job 객체이며, 결과값을 반환하지 않음
runBlocking(Dispatchers.IO) {
val studentList = async {
studentDao.findByName(name)
}.await()
val sb = StringBuilder()
for (it in studentList) {
sb.append(it.id, "-", it.name, "\n")
}
// Dispatchers.IO 코루틴 스코프 안이므로 UI 업데이트가 불가능함 (UI 업데이트는 Main Thread에서)
// withContext : 코루틴 실행 도중에 실행 환경을 바꾸고 싶을 때 사용한다.
withContext(Dispatchers.Main) {
textQueryStudent.text = sb.toString()
}
}
4. async, await 사용법
// data 변수의 타입 : Deferred<StringBuilder>
val data = CoroutineScope(Dispatchers.IO).async {
val studentList = studentDao.findByName(name)
val sb = StringBuilder()
for (it in studentList) {
sb.append(it.id, "-", it.name, "\n")
}
sb // 마지막 라인이 async 블럭의 반환값
}
CoroutineScope(Dispatchers.Main).launch {
val searchStudentList = data.await()
textQueryStudent.text = searchStudentList.toString()
}
Tip1. 코루틴 사용 절차
1. Dispatchers 정하기 (어떤 쓰레드에서 실행할 것인가?)
(1) Dispatchers.Main : 메인 쓰레드로, UI 작업(갱신 등)을 위해 사용함
(2) Dispatchers.IO : 네트워크 통신(API Call), 디스크 I/O (File, 내장 DB 등)작업에 최적화
(3) Dispatchers.Default : CPU 사용량이 많은 무거운 작업(데이터 전처리, JSON 파싱 등)에 최적화
2. 코루틴이 실행될 Scope 정하기 (어떤 방법으로 suspend 함수를 호출할 것인가?)
(0) runBlocking : 사용 X, Call 시 반환값을 받을 때 까지 쓰레드가 block됨
(1) CoroutineScope : 사용자 정의(지정) scope
(2) GlobalScope : App이 실행될 때부터 종료될 때까지 실행
(3) ViewModelScope : ViewModel이 제거되면, 자동으로 코루틴 작업이 취소된다.
(4) lifecycleScope : 액티비티, 프래그먼트, 서비스 등의 Lifecycle이 끝날 때 코루틴 작업이 자동으로 취소된다.
(5) LiveData : 별도의 scope 없이 옵저버 패턴으로 데이터 갱신
3. 코루틴 실행 방법 정하기 (반환값을 어디에서 처리할 것인가?)
(1) launch : 코드 실행시, 반환값에 대한 처리를 내부에서 할 때 사용 (Job 객체)
(2) async(+await()) : 코드 실행 시, 반환값 처리를 람다 외부에서 해야할 때 사용 (Deferred 객체)
'Android' 카테고리의 다른 글
firebase storage, database 사용을 위한 기본기 (0) | 2024.05.27 |
---|---|
GPS Location, 사용자 위치 정보 가져오기 (0) | 2024.05.08 |
ch 6주차 정리 (1) | 2024.05.01 |
ch 5주차 정리 (0) | 2024.04.30 |
앱 개발 숙련 과제 후기 (1) | 2024.04.18 |