๐ ์คํ๋ฅดํ์ฝ๋ฉํด๋ฝ์ '๋ด์ผ๋ฐฐ์์บ ํ' ์ง๊ธ ๊ฐ์ข๋ฅผ ์ ๋ฆฌํ ๊ธ ์ ๋๋ค.
K-V ๋ก ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๋ค.
์์ฉ ํ๋ก๊ทธ๋จ ๋ด์ ์กํฐ๋นํฐ ๊ฐ์ ๊ณต์ ํ๋ฉฐ, ํ์ชฝ ์กํฐ๋นํฐ์์ ์์ ์ ๋ค๋ฅธ ์กํฐ๋นํฐ๋ ์์ ๋ ๊ฐ์ ์ฝ์ ์ ์๋ค.
์์ฉ ํ๋ก๊ทธ๋จ์ ๊ณ ์ ํ ์ ๋ณด์ด๋ฏ๋ก ์ธ๋ถ์์๋ ์ฝ์ ์ ์๋ค.
getSharedPreference(name, mode)
์ฌ๋ฌ๊ฐ์ SharePreference ํ์ผ๋ค์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ
name : Preference ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ XML ํ์ผ์ ์ด๋ฆ
mode : ํ์ผ์ ๊ณต์ ๋ชจ๋
MODE_PRIVATE : ์์ฑ๋ XML ํ์ผ์ ํธ์ถํ ์ดํ๋ฆฌ์ผ์ด์ ๋ด์์๋ง ์ฝ๊ธฐ ์ฐ๊ธฐ๊ฐ ๊ฐ๋ฅ
MODE_WORLD_READABLE, MODE_WORLD_WRITEABLE ์ ๋ณด์์ ์ด์ ๋ก API level 17 ์์ deprecated ๋จ
val sharedPref = activity?.getSharedPreference(
getString(R.string.preference_file_key), Context.MODE_PRIVATE)
<?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">
<EditText
android:id="@+id/et_hello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textPersonName"
android:textColor="#000000"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_save"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="์ ์ฅํ๊ธฐ"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/et_hello" />
</androidx.constraintlayout.widget.ConstraintLayout>
package com.kotdev99.android.test
class MainActivity : AppCompatActivity() {
private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
binding.btnSave.setOnClickListener {
saveData()
Toast.makeText(this, "Data Saved", Toast.LENGTH_SHORT).show()
}
loadData()
}
private fun saveData() {
val pref = getSharedPreferences("pref", 0)
val edit = pref.edit() // ์์ ๋ชจ๋
// ์ฒซ ๋ฒ์งธ ์ธ์๋ ํค, ๋ ๋ฒ์งธ ์ธ์๋ ์ค์ ๋ด์๋ ๊ฐ
edit.putString("name", binding.etHello.text.toString())
edit.apply() // ์ ์ฅ ์๋ฃ
}
private fun loadData() {
val pref = getSharedPreferences("pref", 0)
// ์ฒซ ๋ฒ์งธ ์ธ์๋ ํค, ๋ ๋ฒ์งธ ์ธ์๋ ๋ฐ์ดํฐ๊ฐ ์กด์ฌํ์ง ์์ ๊ฒฝ์ฐ์ ๊ฐ
binding.etHello.setText(pref.getString("name", ""))
}
}
data/data/ํจํค์ง๋ช /shared_prefs ํ์์ XML ํ์ผ์ด ์์ฑ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
- SQLite ๋ฅผ ์ฝ๊ฒ ์ฌ์ฉํ ์ ์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ฐ์ฒด ๋งคํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- ์ฝ๊ฒ Query ๋ฅผ ์ฌ์ฉํ ์ ์๋ API๋ฅผ ์ ๊ณต
- Query ๋ฅผ ์ปดํ์ผ ์๊ฐ์ ๊ฒ์ฆํจ
- Query ๊ฒฐ๊ณผ๋ฅผ LiveData ๋กํ์ฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค ์ฝ๊ฒ ๋ณ๊ฒฝํ ์ ์์
- ๊ตฌ๊ธ์์๋ SQLite ๋ณด๋ค Room ์ฌ์ฉ์ ๊ถ์ฅํจ
- @Database : ํด๋์ค๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ก ์ง์ ํ๋ annotation, RoomDatabase๋ฅผ ์์ ๋ฐ์ ํด๋์ค์ฌ์ผ ํจ
Room.databaseBuilder ๋ฅผ ์ด์ฉํ์ฌ ์ธ์คํด์ค๋ฅผ ์์ฑ
- @Entity : ํด๋์ค๋ฅผ ํ
์ด๋ธ ์คํค๋ง๋ก ์ง์ ํ๋ annotation
- @Dao : ํด๋์ค๋ฅผ DAO(Data Access Object) ๋ก ์ง์ ํ๋ annotation
๊ธฐ๋ณธ์ ์ธ insert, delete, update SQL ์ ์๋์ผ๋ก ๋ง๋ค์ด์ฃผ๋ฉฐ, ๋ณต์กํ SQL ์ ์ง์ ๋ง๋ค ์ ์์
plugins {
....
id ("kotlin-kapt")
}
.....
dependencies {
val room_version = "2.5.0"
implementation("androidx.room:room-runtime:$room_version")
annotationProcessor("androidx.room:room-compiler:$room_version")
// To use Kotlin annotation processing tool (kapt)
kapt("androidx.room:room-compiler:$room_version")
// To use Kotlin Symbol Processing (KSP)
ksp("androidx.room:room-compiler:$room_version")
// optional - Kotlin Extensions and Coroutines support for Room
implementation("androidx.room:room-ktx:$room_version")
// optional - RxJava2 support for Room
implementation("androidx.room:room-rxjava2:$room_version")
// optional - RxJava3 support for Room
implementation("androidx.room:room-rxjava3:$room_version")
// optional - Guava support for Room, including Optional and ListenableFuture
implementation("androidx.room:room-guava:$room_version")
// optional - Test helpers
testImplementation("androidx.room:room-testing:$room_version")
// optional - Paging 3 Integration
implementation("androidx.room:room-paging:$room_version")
}
// Sql๋ฌธ์ผ๋ก ์์ฑํ๋ ๊ฒฝ์ฐ
CREATE TABLE student_table (student_id INTEGER PRIMARY KEY, name TEXT NOT NULL);
// ํด๋์ค๋ก ์์ฑํ๋ ๊ฒฝ์ฐ
@Entity(tableName = "student_table") // ํ
์ด๋ธ ์ด๋ฆ ์ง์
data class Student {
@PrimaryKey
@ColumnInfo(name = "student_id")
val id: Int,
val name: String
}
@Query("SELECT * from table") fun getAllData() : List<Data>
DAO ๋ interface ๋ abstract class ๋ก ์ ์๋์ด์ผ ํจ
Annotation ์ SQL ์ฟผ๋ฆฌ๋ฅผ ์ ์ํ๊ณ ๊ทธ ์ฟผ๋ฆฌ๋ฅผ ์ํ ๋ฉ์๋๋ฅผ ์ ์ธ
๊ฐ๋ฅํ annotation ์ผ๋ก @Insert, @Update, @Delete, @Query ๊ฐ ์์
- OnConflictStrategy.ABORT : key ์ถฉ๋ ์ ์ข
๋ฃ
- OnConflictStrategy.IGNORE : key ์ถฉ๋ ์ ๋ฌด์
- OnConflictStrategy.REPLACE : key ์ถฉ๋ ์ ์๋ก์ด ๋ฐ์ดํฐ๋ก ๋ณ๊ฒฝ
@Insert, @Update, @Delete ๋ SQL ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ์ง ์์๋ ์ปดํ์ผ๋ฌ๊ฐ ์๋์ผ๋ก ์์ฑํจ
@Insert ๋ @Update ๋ key๊ฐ ์ค๋ณต๋๋ ๊ฒฝ์ฐ ์ฒ๋ฆฌ๋ฅผ ์ํด onConflict ๋ฅผ ์ง์ ํ ์ ์์
@Update ๋ @Delete ๋ primary key์ ํด๋น๋๋ ํํ์ ์ฐพ์์ ๋ณ๊ฒฝ/์ญ์ ํจ
@Query("SELECT * from table") fun getAllData() : List<Data>
@Query("SELECT * FROM student_table WHERE name = :sname") // ์ธ์ sname์ SQL์์ :sname์ผ๋ก ์ฌ์ฉ
suspend fun getStudentByName(sname: String): List<Student>
@Query์ SQL์ ์ ์ํ ๋ ๋ฉ์๋์ ์ธ์๋ฅผ ์ฌ์ฉํ ์ ์์
fun ์์ suspend๋ Kotlin coroutine ์ ์ฌ์ฉํจ์ ์๋ฏธ, ๋์ค์ ์ด ๋ฉ์๋๋ฅผ ๋ถ๋ฅผ ๋๋
runBlocking {}
๋ด์์ ํธ์ถํด์ผ ํจ
LiveData ๋ ๋น๋๊ธฐ์ ์ผ๋ก ๋์ํ๊ธฐ ๋๋ฌธ์ coroutine ์ผ๋ก ํ ํ์๊ฐ ์์
@Database(entities = [Student::class, ClassInfo::class, Enrollment::class, Teacher::class], version = 1)
abstract class MyDatabase : RoomDatabase() {
abstract fun getMyDao() : MyDAO
companion object {
private var INSTANCE: MyDatabase? = null
private val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) { ์๋ต }
}
private val MIGRATION_2_3 = object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) { ์๋ต }
}
fun getDatabase(context: Context) : MyDatabase {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
context, MyDatabase::class.java, "school_database")
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
.build()
}
return INSTANCE as MyDatabase
}
}
}
RoomDatabase
๋ฅผ ์์ํ์ฌ ์์ ์ Room ํด๋์ค๋ฅผ ๋ง๋ค์ด์ผ ํจ
ํฌํจ๋๋ Entity๋ค๊ณผ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ฒ์ (version)์ @Database annotation์ ์ง์ ํจ
version์ด ๊ธฐ์กด์ ์ ์ฅ๋์ด ์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ณด๋ค ๋์ผ๋ฉด, ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ openํ ๋ migration์ ์ํํ๊ฒ ๋จ
Migration ์ํ ๋ฐฉ๋ฒ์ RoomDatabase ๊ฐ์ฒด์ addMigration() ๋ฉ์๋๋ฅผ ํตํด ์๋ ค์ค
DAO๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๋ getter ๋ฉ์๋๋ฅผ ์์ฑ
Room ํด๋์ค์ ์ธ์คํด์ค๋ ํ๋๋ง ์์ผ๋ฉด ๋๋ฏ๋ก Singleton ํจํด์ ์ฌ์ฉ
Room ํด๋์ค์ ๊ฐ์ฒด ์์ฑ์ Room.databaseBuilder() ๋ฅผ ์ด์ฉํจ
Room.databaseBuilder(...).addMigrations(MIGRATION_1_2, MIGRATION_2_3)
private val MIGRATION_1_2 = object : Migration(1, 2) { // version 1 -> 2
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE student_table ADD COLUMN last_update INTEGER")
}
}
private val MIGRATION_2_3 = object : Migration(2, 3) { // version 2 -> 3
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE class_table ADD COLUMN last_update INTEGER")
}
}
myDao = MyDatabase.getDatabase(this).getMyDao()
runBlocking { // (์ฃผ์) UI๋ฅผ ๋ธ๋กํ ์ ์๋ DAO ๋ฉ์๋๋ฅผ UI ์ค๋ ๋์์ ๋ฐ๋ก ํธ์ถํ๋ฉด ์๋จ
myDao.insertStudent(Student(1, "james")) // suspend ์ง์ ๋์ด ์์
}
val allStudents = myDao.getAllStudents() // LiveData๋ Observer๋ฅผ ํตํด ๋น๋๊ธฐ์ ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ด
val allStudents = myDao.getAllStudents()
allStudents.observe(this) { // Observer::onChanged() ๋ SAM ์ด๊ธฐ ๋๋ฌธ์ lambda๋ก ๋์ฒด
val str = StringBuilder().apply {
for ((id, name) in it) {
append(id)
append("-")
append(name)
append("\n")
}
}.toString()
binding.textStudentList.text = str
}
LiveData<>
ํ์
์ผ๋ก ๋ฆฌํด๋๋ DAO ๋ฉ์๋ ๊ฒฝ์ฐ
observe() ๋ฉ์๋๋ฅผ ์ด์ฉํ์ฌ Observer ๋ฅผ ์ง์
๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค ์๋์ผ๋ก Observer์ onChanged()
๊ฐ ํธ์ถ๋จ
LiveData<> ๋ฅผ ๋ฆฌํดํ๋ DAO ๋ฉ์๋๋ Observer ๋ฅผ ํตํด ๋น๋๊ธฐ์ ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ฐ๊ธฐ ๋๋ฌธ์, UI ์ค๋ ๋์์ ์ง์ ํธ์ถํด๋ ๋ฌธ์ ์์
plugins {
....
id ("kotlin-kapt")
}
....
dependencies {
....
val room_version = "2.5.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")
<?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"
android:padding="5dp"
tools:context=".MainActivity">
<EditText
android:id="@+id/edit_student_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:hint="ID"
android:inputType="number"
app:layout_constraintEnd_toStartOf="@+id/query_student"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/edit_student_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:hint="student name"
android:inputType="textPersonName"
app:layout_constraintEnd_toStartOf="@+id/add_student"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/edit_student_id" />
<Button
android:id="@+id/add_student"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Add Student"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/edit_student_name"
app:layout_constraintTop_toBottomOf="@+id/query_student" />
<Button
android:id="@+id/query_student"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Query Student"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/edit_student_id"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Result of Query Student"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/edit_student_name" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Student List"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_query_student" />
<TextView
android:id="@+id/text_query_student"
android:layout_width="0dp"
android:layout_height="100sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
<TextView
android:id="@+id/text_student_list"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView2" />
</androidx.constraintlayout.widget.ConstraintLayout>
package com.kotdev99.android.test
@Entity(tableName = "student_table") // ํ
์ด๋ธ ๋ช
์ง์
data class Student(
@PrimaryKey @ColumnInfo(name = "student_id")
val id: Int,
val name: String
)
package com.kotdev99.android.test
@Database(
entities = [Student::class],
exportSchema = false, version = 1
)
abstract class MyDatabase : RoomDatabase() {
abstract fun getMyDao(): MyDao
companion object {
private var INSTANCE: MyDatabase? = null
private val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
}
}
private val MIGRATION_2_3 = object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE student_table ADD COLUMN last_update INTEGER")
}
}
fun getDatabase(context: Context): MyDatabase {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
context, MyDatabase::class.java, "school_database"
)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
.build()
// for in-memory database
/*INSTANCE = Room.inMemoryDatabaseBuilder(
context, MyDatabase::class.java
).build()*/
}
return INSTANCE as MyDatabase
}
}
}
package com.kotdev99.android.test
@Dao
interface MyDAO {
@Insert(onConflict = OnConflictStrategy.REPLACE) // INSERT, key ์ถฉ๋์ด ๋๋ฉด ์ ๋ฐ์ดํฐ๋ก ๊ต์ฒด
suspend fun insertStudent(student: Student)
@Query("SELECT * FROM student_table")
fun getAllStudents(): LiveData<List<Student>> // LiveData<> ์ฌ์ฉ
@Query("SELECT * FROM student_table WHERE name = :sname") // ๋ฉ์๋ ์ธ์๋ฅผ SQL๋ฌธ์์ :์ ๋ถ์ฌ ์ฌ์ฉ
suspend fun getStudentByName(sname: String): List<Student>
@Delete
suspend fun deleteStudent(student: Student); // primary key is used to find the student
}
package com.kotdev99.android.test
class MainActivity : AppCompatActivity() {
private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
private lateinit var myDao: MyDAO
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
myDao = MyDatabase.getDatabase(this).getMyDao()
val allStudents = myDao.getAllStudents()
allStudents.observe(this) {
val str = StringBuilder().apply {
for ((id, name) in it) {
append(id)
append("-")
append(name)
append("\n")
}
}.toString()
binding.textStudentList.text = str
}
binding.addStudent.setOnClickListener {
val id = binding.editStudentId.text.toString().toInt()
val name = binding.editStudentName.text.toString()
if (id > 0 && name.isNotEmpty()) {
CoroutineScope(Dispatchers.IO).launch {
myDao.insertStudent(Student(id, name))
}
}
binding.editStudentId.text = null
binding.editStudentName.text = null
}
binding.queryStudent.setOnClickListener {
val name = binding.editStudentName.text.toString()
CoroutineScope(Dispatchers.IO).launch {
val results = myDao.getStudentByName(name)
if (results.isNotEmpty()) {
val str = StringBuilder().apply {
results.forEach { student ->
append(student.id)
append("-")
append(student.name)
}
}
withContext(Dispatchers.Main) {
binding.textQueryStudent.text = str
}
} else {
withContext(Dispatchers.Main) {
binding.textQueryStudent.text = ""
}
}
}
}
}
}
android {
....
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
}
App Inspection ๋ฉ๋ด์์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๊น๋ณผ ์ ์๋ค.
android.permission.ACCESS_COARSE_LOCATION
: ์์ดํ์ด๋ ๋ชจ๋ฐ์ผ ๋ฐ์ดํฐ(๋๋ ๋ ๋ค) ๋ฅผ ์ฌ์ฉํด ๊ธฐ๊ธฐ์ ์์น์ ์ ๊ทผํ๋ ๊ถํ. ๋์์์ 1๋ธ๋ก ์ ๋์ ์ค์ฐจ ์์ค.
android.permission.ACCESS_FINE_LOCATION
: ์์ฑ, ์์ดํ์ด, ๋ฐ์ดํฐ ๋ฑ ์ด์ฉํ ์ ์๋ ์์น ์ ๊ณต์๋ฅผ ์ฌ์ฉํด ์ต๋ํ ์ ํํ ์์น์ ์ ๊ทผํ๋ ๊ถํ.
android.permission.ACCESS_BACKGROUND
: ์๋๋ก์ด๋ 10 (API Level 29) ์ด์์์ ๋ฐฑ๊ทธ๋ผ์ด๋ ์ํ์์ ์์น์ ์ ๊ทผํ๋ ๊ถํ
val manager = getSystemService(LOCATION_SERVICE) as LocationManager
LocationManager
๋ผ๋ ์์คํ
์๋น์ค๋ฅผ ์ด์ฉGPS : GPS ์์ฑ์ ์ด์ฉ.
Network : ์ด๋ ํต์ ๋ง์ ์ด์ฉ.
Wifi : ์์ดํ์ด๋ฅผ ์ด์ฉ.
Passive : ๋ค๋ฅธ ์ฑ์์ ์ด์ฉํ ๋ง์ง๋ง ์์น ์ ๋ณด๋ฅผ ์ด์ฉ.
var result = "All Providers : "
val providers = manager.allProviders
for (provider in providers) {
result = " $provider. "
}
Log.d("maptest", result) // All Providers : passive, gps, network..
allProviders
ํ๋กํผํฐ๋ฅผ ์ด์ฉ.result = "Enabled Providers : "
val enabledProviders = manager.getProviders(true)
for (provider in enabledProviders) {
result += $provider. "
}
Log.d("maptest", result) // Enabled Providers : passive, gps, network..
getProviders()
ํจ์๋ฅผ ์ด์ฉif (ContextCompat.checkSelfPermission( // ์ฐ์ ํผ๋ฏธ์
์ฒดํฌ
this,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
) {
val location: Location? = manager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
location?.let{
val latitude = location.latitude
val longitude = location.longitude
val accuracy = location.accuracy
val time = location.time
Log.d("map_test", "$latitude, $location, $accuracy, $time")
}
}
LocationManager ์ getLastKnownLocation() ํจ์๋ฅผ ์ด์ฉ
Location ์ ์์น์ ์ ํ๋, ์๋, ๊ฒฝ๋, ํ๋ ์๊ฐ ๋ฑ์ ๋ฐ์ดํฐ๋ฅผ ํฌํจ
getAccuracy() : ์ ํ๋
getLatitude() : ์๋
getLongitude() : ๊ฒฝ๋
getTime() : ํ๋ ์๊ฐ
val listener: LocationListener = object : LocationListener {
override fun onLocationChanged(location: Location) {
Log.d(
"map_test,","${location.latitude}, ${location.longitude}, ${location.accuracy}"
)
}
}
manager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 10_000L, 10f, listener)
// (.. ์๋ต ..) //
manager.removeUpdates(listener)
LocationListener
๋ฅผ ์ด์ฉonLocationChanged() : ์๋ก์ด ์์น๋ฅผ ๊ฐ์ ธ์ค๋ฉด ํธ์ถ.
onProviderEnabled() : ์์น ์ ๊ณต์๊ฐ ์ด์ฉํ ์ ์๋ ์ํฉ์ด๋ฉด ํธ์ถ.
onProviderDisabled() : ์์น ์ ๊ณต์๊ฐ ์ด์ฉํ ์ ์๋ ์ํฉ์ด๋ฉด ํธ์ถ.
์ ๋ ฅ์ ์ ๊ฒ ์๋นํ๋๊ฐ?
์ ํ๋๋ ๋์๊ฐ?
API ๊ฐ ๊ฐ๋จํ๊ฐ?
๋ถ๊ฐ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋๊ฐ?
๋๋ถ๋ถ ์๋๋ก์ด๋ ๊ธฐ๊ธฐ๋ฅผ ์ง์ํ๋๊ฐ?
๊ตฌ๊ธ์์๋ ์ต์ ์ ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ์์น ์ ๊ณต์๋ฅผ ์ง์ ํ ์ ์๋๋ก Fused Location Provider
๋ผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ ๊ณต.
implementation ("com.google.android.gms:play-services-location:21.0.1")
val connectionCallback = object: GoogleApiClient.ConnectionCallbacks{
override fun onConnected(p0: Bundle?) {
// ์์น ์ ๊ณต์๋ฅผ ์ฌ์ฉํ ์ ์์ ๋
// ์์น ํ๋
}
override fun onConnectionSuspended(p0: Int) {
// ์์น ์ ๊ณต์๋ฅผ ์ฌ์ฉํ ์ ์์ ๋
}
}
val onConnectionFailCallback = object : GoogleApiClient.OnConnectionFailedListener{
override fun onConnectionFailed(p0: ConnectionResult) {
// ์ฌ์ฉํ ์ ์๋ ์์น ์ ๊ณต์๊ฐ ์์ ๋
}
}
val apiClient = GoogleApiClient.Builder(this)
.addApi(LocationServices.API)
.addConnectionCallbacks(connectionCallback)
.addOnConnectionFailedListener(onConnectionFailCallback)
.build()
FusedLocationProviderClient : ์์น ์ ๋ณด๋ฅผ ํ๋.
GoogleApiClient : ์์น ์ ๊ณต์ ์ค๋น ๋ฑ ๋ค์ํ ์ฝ๋ฐฑ์ ์ ๊ณต.
val providerClient = LocationServices.getFusedLocationProviderClient(this)
apiClient.connect()
// ์์น ์ ๊ณต์๋ฅผ ์ฌ์ฉํ ์ ์๋ ์ํฉ์ผ ๋
override fun onConnected(p0: Bundle?) {
if(ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) === PackageManager.PERMISSION_GRANTED){
providerClient.lastLocation.addOnSuccessListener(
this@MainActivity,
object: OnSuccessListener<Location> {
override fun onSuccess(p0: Location?) {
p0?.let {
val latitude = p0.latitude
val longitude = p0.longitude
Log.d("map_test", "$latitude, $longitude")
}
}
}
)
apiClient.disconnect()
}
}
getLastLocation()
ํจ์ ํธ์ถGoogleMap.OnMapClickListener : ์ง๋ ํด๋ฆญ ์ด๋ฒคํธ
GoogleMap.OnMapLongClickListener : ์ง๋ ๋กฑ ํด๋ฆญ ์ด๋ฒคํธ
GoogleMap.OnMarkerClickListener : ๋ง์ปค ํด๋ฆญ ์ด๋ฒคํธ
GoogleMap.OnMarkerDragListener : ๋ง์ปค ๋๋๊ทธ ์ด๋ฒคํธ
GoogleMap.OnInfoWindowClickListener : ์ ๋ณด ์ฐฝ ํด๋ฆญ ์ด๋ฒคํธ
GoogleMap.OnCameraIdleListener : ์ง๋ ํ๋ฉด ๋ณ๊ฒฝ ์ด๋ฒคํธ
googleMap?.setOnMapClickListener { latLng ->
Log.d("map_test", "click : ${latLng.latitude} , ${latLng.longitude}")
}
googleMap?.setOnMapLongClickListener { latLng ->
Log.d("map_test", "long click : ${latLng.latitude} , ${latLng.longitude}")
}
googleMap?.setOnCameraIdleListener {
val position = googleMap!!.cameraPosition
val zoom = position.zoom
val latitude = position.target.latitude
val longitude = position.target.longitude
Log.d("map_test", "User change : $zoom $latitude , $longitude")
}
googleMap?.setOnMarkerClickListener { marker ->
true
}
googleMap?.setOnInfoWindowClickListener { marker ->
}
implementation 'com.google.android.gms:play-services-maps:18.1.0'
implementation 'com.google.android.gms:play-services-location:21.0.1'
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.INTERNET"/>
๊ตฌ๊ธ ์ง๋ API๋ฅผ ์ด์ฉํ๋ ํค๋ฅผ ๋ฑ๋ก
๊ตฌ๊ธ ๊ฐ๋ฐ์ ์ฝ์์ ์ ์ํด ํ๋ก์ ํธ๋ฅผ ์์ฑํ๊ณ ์ฌ์ฉ์ ์ธ์ฆ ์ ๋ณด๋ฅผ ๋ง๋ค๋ฉด ์ง๋ API ํค ๋ฅผ ๋ฐ๊ธ
๊ตฌ๊ธ ๊ฐ๋ฐ์ ์ฝ์์์ ์ป์ ์ง๋ APIํค๋ฅผ ๋งค๋ํ์คํธ ํ์ผ์ ๋ฑ๋ก
Manifest.xml
<uses-library android:name="org.apache.http.legacy" android:required="true"/>
<meta-data android:name="com.google.android.maps.v2.API_KEY"
android:value="### ๊ตฌ๊ธ ์ง๋ API ํค ๋ฑ๋ก ###"/>
<meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version"/>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MapTest"
tools:targetApi="31">
<uses-library
android:name="org.apache.http.legacy"
android:required="true" />
<meta-data
android:name="com.google.android.maps.v2.API_KEY"
android:value="### ๊ตฌ๊ธ ์ง๋ API ํค ๋ฑ๋ก ###" />
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/mapView"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
package com.kotdev99.android.maptest
class MainActivity : AppCompatActivity(), OnMapReadyCallback {
private lateinit var mGoogleMap: GoogleMap
// ์์น ์๋น์ค๊ฐ GPS๋ฅผ ์ฌ์ฉํ์ฌ ์์น๋ฅผ ํ์ธ
private lateinit var fusedLocationClient: FusedLocationProviderClient
// ์์น ๊ฐ ์์ฒญ์ ๋ํ ๊ฐฑ์ ์ ๋ณด๋ฅผ ๋ฐ๋ ๋ณ์
private lateinit var locationCallback: LocationCallback
private lateinit var locationPermission: ActivityResultLauncher<Array<String>>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
locationPermission = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { results ->
if (results.all { it.value }) {
(supportFragmentManager.findFragmentById(R.id.mapView) as SupportMapFragment)!!.getMapAsync(
this
)
} else {
Toast.makeText(this, "๊ถํ ์น์ธ์ด ํ์ํฉ๋๋ค.", Toast.LENGTH_LONG).show()
}
}
// ๊ถํ ์์ฒญ
locationPermission.launch(
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
)
)
}
// ์ง๋ ๊ฐ์ฒด๋ฅผ ์ด์ฉํ ์ ์๋ ์ํฉ์ด ๋ ๋
override fun onMapReady(p0: GoogleMap) {
val seoul = LatLng(37.566610, 126.978403)
mGoogleMap = p0
mGoogleMap.mapType = GoogleMap.MAP_TYPE_NORMAL // default ๋
ธ๋ง ์๋ต ๊ฐ๋ฅ
mGoogleMap.apply {
val markerOptions = MarkerOptions()
markerOptions.icon(
BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE)
)
markerOptions.position(seoul)
markerOptions.title("์์ธ์์ฒญ")
markerOptions.snippet("Tel:01-120")
addMarker((markerOptions))
}
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
updateLocation()
}
private fun updateLocation() {
val locationRequest = LocationRequest.create().apply {
interval = 1000
fastestInterval = 500
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
locationCallback = object : LocationCallback() {
// 1์ด์ ํ ๋ฒ์ฉ ๋ณ๊ฒฝ๋ ์์น ์ ๋ณด๊ฐ onLocationResult ์ผ๋ก ์ ๋ฌ๋๋ค.
override fun onLocationResult(p0: LocationResult) {
p0?.let {
for (location in it.locations) {
Log.d("์์น์ ๋ณด", "์๋: ${location.latitude} ๊ฒฝ๋: ${location.longitude}")
setLastLocation(location)
}
}
}
}
// ๊ถํ ์ฒ๋ฆฌ
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
return
}
fusedLocationClient.requestLocationUpdates(
locationRequest,
locationCallback,
Looper.myLooper()!!
)
}
private fun setLastLocation(location: Location) {
val LATLNG = LatLng(location.latitude, location.longitude)
val markerOptions = MarkerOptions().position(LATLNG).title("๋ณธ.์ธ. ํ์ฌ ์ฌ๊ธฐ")
val cameraPosition = CameraPosition.Builder().target(LATLNG).zoom(15.0f).build()
mGoogleMap.addMarker(markerOptions)
mGoogleMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition))
}
}
์๋ฎฌ๋ ์ดํฐ๋ผ ์ค์ ์์น๋ฅผ ๋ฐ์์ฌ ์๊ฐ ์๋ค. more ๋ฉ๋ด์์ ๊ฐ์ ์์น ์ ํ ์ ํด์ฃผ์.
๊ณต๊ณต๋ฐ์ดํฐํฌํธ์์ 'ํ๊ตญํ๊ฒฝ๊ณต๋จ์์ด์ฝ๋ฆฌ์๋๊ธฐ์ค์ผ์ ๋ณด' ์ด์ฉํ๋ค.
๋ฏธ์ธ๋จผ์ง์ ์์น๋ณ๋ก ๋ฑ๊ธ๊ณผ ์์ด์ฝ์ ๊ตฌ๋ถํ๋ค.
- https://apis.data.go.kr/B552584/ArpltnInforInqireSvc/ โ API ์๋น์ค๋ช
- getCtprvnRltmMesureDnsty? โ ์ธ๋ถ ์ต์
(์๊ตฐ๊ตฌ๋ณ ์ค์๊ฐ ํ๊ท ์ ๋ณด ์กฐํ)
- &serviceKey=YBZfZVJMj1yF6gVroDidoNameโฆ โ ์ธ์ฆํค
- &returnType=json โ ๊ฒฐ๊ณผ ๋ฐ์ดํฐ ์ ํ
- &numOfRows=50 โ ํํ์ด์ง ๊ฒฐ๊ณผ์
- &pageNo=1 โ ํ์ด์ง ๋ฒํธ
- &sidoName=์์ธ โ ์๋ ์ด๋ฆ(์์ธ,๋ถ์ฐ,๋๊ตฌ,์ธ์ฒโฆ.)
- &ver=1.0 โ ๋ฒ์
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.10.0'
implementation "com.github.skydoves:powerspinner:1.2.6"
retrofit
retrofit์ ์๋๋ก์ด๋์์REST api ํต์ ์ ์ง์ํ๊ธฐ ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค. ๊ธฐ์กด์ ์ฌ์ฉ์๊ฐ http ํต์ ์ ์ํด HttpURLConnection์ ์ฑ๋ฆฝ์ํค๊ณ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ์์ค๋ ๊ณผ์ ์ด ๋ค์ ๋ณต์กํ๋ค. ์ฌ๊ธฐ์ ๋ํด์ AsyncTask๊ฐ deprecated๋๋ฉด์ ์๋ฒ์์ ๋น๋๊ธฐ ํต์ ์ ์ํด ๊ณจ๋จธ๋ฆฌ๋ฅผ ์๊ฒ ๋๋๋ฐ.. retrofit์ Call ์ธํฐํ์ด์ค์ enqueue์ ํตํด ๋น๋๊ธฐ ๊ตฌํ์ด ๊ฐ๋ฅํ๋ค.(call ์ธํฐํ์ด์ค์ enqueue๋ฅผ ์ด์ฉํ์ง ์๊ณ rxjava, rxkotlin๋ฅผ ์ฌ์ฉํ์ฌ ๋น๋๊ธฐ ์ฒ๋ฆฌํ ์๋ ์๋ค.)
android {
....
buildFeatures {
viewBinding = true
dataBinding = true
}
}
dependencies {
....
implementation("com.google.code.gson:gson:2.10.1")
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.squareup.okhttp3:okhttp:4.10.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.10.0")
implementation("com.github.skydoves:powerspinner:1.2.6")
}
<uses-permission android:name="android.permission.INTERNET" />
<?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:id="@+id/main_bg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#9ED2EC"
tools:context=".MainActivity">
<com.skydoves.powerspinner.PowerSpinnerView
android:id="@+id/spinnerView_sido"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@color/md_green_200"
android:foreground="?attr/selectableItemBackground"
android:gravity="center"
android:hint="๋์ ์ ํ"
android:padding="10dp"
android:textColor="@color/white_93"
android:textColorHint="@color/white_70"
android:textSize="14.5sp"
app:layout_constraintEnd_toStartOf="@+id/spinnerView_goo"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:spinner_arrow_gravity="end"
app:spinner_arrow_tint="@color/yellow"
app:spinner_divider_color="@color/white_70"
app:spinner_divider_show="true"
app:spinner_divider_size="0.4dp"
app:spinner_item_array="@array/Sido"
app:spinner_item_height="46dp"
app:spinner_popup_animation="normal"
app:spinner_popup_background="@color/background800"
app:spinner_popup_elevation="14dp"
tools:ignore="HardcodedText,UnusedAttribute" />
<com.skydoves.powerspinner.PowerSpinnerView
android:id="@+id/spinnerView_goo"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@color/md_green_100"
android:foreground="?attr/selectableItemBackground"
android:gravity="center"
android:hint="์ง์ญ ์ ํ"
android:padding="10dp"
android:textColor="@color/white_93"
android:textColorHint="@color/white_70"
android:textSize="14.5sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/spinnerView_sido"
app:layout_constraintTop_toTopOf="parent"
app:spinner_arrow_gravity="end"
app:spinner_arrow_tint="@color/yellow"
app:spinner_divider_color="@color/white_70"
app:spinner_divider_show="true"
app:spinner_divider_size="0.4dp"
app:spinner_item_height="46dp"
app:spinner_popup_animation="normal"
app:spinner_popup_background="@color/background800"
app:spinner_popup_elevation="14dp"
tools:ignore="HardcodedText,UnusedAttribute" />
<ImageView
android:id="@+id/iv_face"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/mise1" />
<TextView
android:id="@+id/tv_p10value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text=" - ใ/ใฅ"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/iv_face" />
<TextView
android:id="@+id/tv_p10grade"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text=""
android:textColor="#048578"
android:textSize="30sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_p10value" />
<TextView
android:id="@+id/tv_cityname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="50dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:text="๋์๋ฅผ ์ ํํด ์ฃผ์ธ์."
android:textColor="#242323"
android:textSize="36sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/iv_face"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:text=""
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_cityname" />
</androidx.constraintlayout.widget.ConstraintLayout>
<resources>
<string name="app_name">Miseya</string>
<string-array name="Sido">
<item>์ ๊ตญ</item>
<item>์์ธ</item>
<item>๋ถ์ฐ</item>
<item>๋๊ตฌ</item>
<item>์ธ์ฒ</item>
<item>๊ด์ฃผ</item>
<item>๋์ </item>
<item>์ธ์ฐ</item>
<item>๊ฒฝ๊ธฐ</item>
<item>๊ฐ์</item>
<item>์ถฉ๋ถ</item>
<item>์ถฉ๋จ</item>
<item>์ ๋ถ</item>
<item>์ ๋จ</item>
<item>๊ฒฝ๋ถ</item>
<item>๊ฒฝ๋จ</item>
<item>์ ์ฃผ</item>
<item>์ธ์ข
</item>
</string-array>
</resources>
์๋ฒ ํต์ ์ request body ๋๋ response body์์ ์ฌ์ฉํ JSONํํ์ ๋ชจ๋ธ ํด๋์ค ์์ฑ -> Kotlin์์๋ data class ํํ๋ก ์์ฑํ๋ค!
๋ณ์๋ช ์ ์๋ ์๋ฒ์์ ์ฌ์ฉํ๋ ๊ฐ๊ณผ ๋๊ฐ์ด ์์ฑํด์ผ ๋๋ค.
๋ง์ฝ ์ฑ ๋ด์์ ๋ค๋ฅธ ๋ณ์๋ช ์ผ๋ก ์ฌ์ฉํ๊ณ ์ถ๋ค๋ฉด ์๋ ์ฝ๋์ฒ๋ผ ' @SerializedName("์๋ฒ์์ ๋ณ์๋ช ") val ์ฑ๋ด๋ณ์๋ช :์๋ฃํ ' ์ ์ฌ์ฉํ๋ค.
package com.kotdev99.android.miseya.data
data class Dust(val response: DustResponse)
data class DustResponse(
@SerializedName("body")
val dustBody: DustBody,
@SerializedName("header")
val dustHeader: DustHeader
)
class DustBody(
val totalCount: Int,
@SerializedName("items")
val dustItem: MutableList<DustItem>?,
val pageNo: Int,
val numOfRows: Int
)
class DustHeader(
val resultCode: String,
val resultMsg: String
)
data class DustItem(
val so2Grade: String,
val coFlag: String?,
val khaiValue: String,
val so2Value: String,
val coValue: String,
val pm25Flag: String?,
val pm10Flag: String?,
val o3Grade: String,
val pm10Value: String,
val khaiGrade: String,
val pm25Value: String,
val sidoName: String,
val no2Flag: String?,
val no2Grade: String,
val o3Flag: String?,
val pm25Grade: String,
val so2Flag: String?,
val dataTime: String,
val coGrade: String,
val no2Value: String,
val stationName: String,
val pm10Grade: String,
val o3Value: String
)
Gson์ Json ๋ฐ์ดํฐ๋ฅผ ๊ฐ๊ณตํ๋๋ฐ ์์ด ์ข ๋ ํธํ๊ณ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํ ์ ์๋๋ก ๋์์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก google์์ ์ ๊ณตํ๋ Json์ ์ค์ฌ Gson์ด๋ผ๊ณ ๋ถ๋ฅธ๋ค๊ณ ๋ณด๋ฉด ๋๋ค.
package com.kotdev99.android.miseya.retrofit
interface NetWorkInterface {
@GET("getCtprvnRltmMesureDnsty") // ์๋๋ณ ์ค์๊ฐ ์ธก์ ์ ๋ณด ์กฐํ ์ฃผ์
suspend fun getDust(@QueryMap param: HashMap<String, String>): Dust
}
package com.kotdev99.android.miseya.retrofit
object NetWorkClient {
private const val DUST_BASE_URL = "https://apis.data.go.kr/B552584/ArpltnInforInqireSvc"
private fun createOkHttpClient(): OkHttpClient {
val interceptor = HttpLoggingInterceptor()
if (BuildConfig.DEBUG)
interceptor.level = HttpLoggingInterceptor.Level.BODY
else
interceptor.level = HttpLoggingInterceptor.Level.NONE
return OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.addNetworkInterceptor(interceptor)
.build()
}
private val dustRetrofit = Retrofit.Builder()
.baseUrl(DUST_BASE_URL).addConverterFactory(GsonConverterFactory.create()).client(
createOkHttpClient()
).build()
val dustNetWork: NetWorkInterface = dustRetrofit.create(NetWorkInterface::class.java)
}
package com.kotdev99.android.miseya
class MainActivity : AppCompatActivity() {
private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
var items = mutableListOf<DustItem>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
binding.spinnerViewSido.setOnSpinnerItemSelectedListener<String> { _, _, _, text ->
communicationNetWork(setUpDustParameter(text))
}
binding.spinnerViewGoo.setOnSpinnerItemSelectedListener<String> { _, _, _, text ->
Log.d("miseya", "selectedItem: spinnerViewGoo selected > $text")
var selectedItem = items.filter { f -> f.stationName == text }
Log.d("miseya", "selectedItem: sidoName > " + selectedItem[0].sidoName)
Log.d("miseya", "selectedItem: pm10Value > " + selectedItem[0].pm10Value)
binding.tvCityname.text = selectedItem[0].sidoName + " " + selectedItem[0].stationName
binding.tvDate.text = selectedItem[0].dataTime
binding.tvP10value.text = selectedItem[0].pm10Value + " ใ/ใฅ"
when (getGrade(selectedItem[0].pm10Value)) {
1 -> {
binding.mainBg.setBackgroundColor(Color.parseColor("#9ED2EC"))
binding.ivFace.setImageResource(R.drawable.mise1)
binding.tvP10grade.text = "์ข์"
}
2 -> {
binding.mainBg.setBackgroundColor(Color.parseColor("#D6A478"))
binding.ivFace.setImageResource(R.drawable.mise2)
binding.tvP10grade.text = "๋ณดํต"
}
3 -> {
binding.mainBg.setBackgroundColor(Color.parseColor("#DF7766"))
binding.ivFace.setImageResource(R.drawable.mise3)
binding.tvP10grade.text = "๋์จ"
}
4 -> {
binding.mainBg.setBackgroundColor(Color.parseColor("#BB3320"))
binding.ivFace.setImageResource(R.drawable.mise4)
binding.tvP10grade.text = "๋งค์ฐ๋์จ"
}
}
}
}
private fun communicationNetWork(param: HashMap<String, String>) = lifecycleScope.launch() {
val responseData = NetWorkClient.dustNetWork.getDust(param)
Log.d("Parsing Dust ::", responseData.toString())
items = responseData.response.dustBody.dustItem!!
val goo = ArrayList<String>()
items.forEach {
Log.d("add Item", it.stationName)
goo.add(it.stationName)
}
runOnUiThread {
binding.spinnerViewGoo.setItems(goo)
}
}
private fun setUpDustParameter(sido: String): HashMap<String, String> {
val authKey =
"YBZ(....)SVg1pEC39CVbmsA=="
return hashMapOf(
"serviceKey" to authKey,
"returnType" to "json",
"numOfRows" to "100",
"pageNo" to "1",
"sidoName" to sido,
"ver" to "1.0"
)
}
fun getGrade(value: String): Int {
val mValue = value.toInt()
var grade = 1
grade = if (mValue >= 0 && mValue <= 30) {
1
} else if (mValue >= 31 && mValue <= 80) {
2
} else if (mValue >= 81 && mValue <= 100) {
3
} else 4
return grade
}
}
๋๋ถ์ ๋์ ๋์ต๋๋ค~~ ์๋ณด๊ณ ๊ฐ์!