[Android] SharedPreferences, Room, FusedLocationProvider ์ •๋ฆฌ

Minjun Kimยท2023๋…„ 9์›” 14์ผ
1

Android

๋ชฉ๋ก ๋ณด๊ธฐ
41/47
post-thumbnail

๐Ÿ“ ์ŠคํŒŒ๋ฅดํƒ€์ฝ”๋”ฉํด๋Ÿฝ์˜ '๋‚ด์ผ๋ฐฐ์›€์บ ํ”„' ์ง€๊ธ‰ ๊ฐ•์ขŒ๋ฅผ ์ •๋ฆฌํ•œ ๊ธ€ ์ž…๋‹ˆ๋‹ค.


๐Ÿ“ก SharedPreference

  • 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)

๐Ÿงฉ ์‹ค์Šต ์˜ˆ์ œ

  • activity_main.xml
<?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>

  • MainActivity.kt
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 ํŒŒ์ผ์ด ์ƒ์„ฑ๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.


๐Ÿ“ก Room

๐Ÿ“š Room ๊ฐœ์š”

- SQLite ๋ฅผ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ฐ์ฒด ๋งคํ•‘ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

- ์‰ฝ๊ฒŒ Query ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” API๋ฅผ ์ œ๊ณต

- Query ๋ฅผ ์ปดํŒŒ์ผ ์‹œ๊ฐ„์— ๊ฒ€์ฆํ•จ

- Query ๊ฒฐ๊ณผ๋ฅผ LiveData ๋กœํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ์‰ฝ๊ฒŒ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Œ

- ๊ตฌ๊ธ€์—์„œ๋„ SQLite ๋ณด๋‹ค Room ์‚ฌ์šฉ์„ ๊ถŒ์žฅํ•จ

๐Ÿ“š Room ์ฃผ์š” 3์š”์†Œ

- @Database : ํด๋ž˜์Šค๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ ์ง€์ •ํ•˜๋Š” annotation, RoomDatabase๋ฅผ ์ƒ์† ๋ฐ›์€ ํด๋ž˜์Šค์—ฌ์•ผ ํ•จ
			  Room.databaseBuilder ๋ฅผ ์ด์šฉํ•˜์—ฌ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑ

- @Entity : ํด๋ž˜์Šค๋ฅผ ํ…Œ์ด๋ธ” ์Šคํ‚ค๋งˆ๋กœ ์ง€์ •ํ•˜๋Š” annotation

- @Dao : ํด๋ž˜์Šค๋ฅผ DAO(Data Access Object) ๋กœ ์ง€์ •ํ•˜๋Š” annotation
		 ๊ธฐ๋ณธ์ ์ธ insert, delete, update SQL ์€ ์ž๋™์œผ๋กœ ๋งŒ๋“ค์–ด์ฃผ๋ฉฐ, ๋ณต์žกํ•œ SQL ์€ ์ง์ ‘ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Œ

๐Ÿ“š ์‚ฌ์šฉ๋ฒ•

๐Ÿงท gradle ์š”์†Œ ์ถ”๊ฐ€

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")
}

๐Ÿ“Œ Entity ์ƒ์„ฑ

// 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
}
  • Entity ๋Š” ํ…Œ์ด๋ธ” ์Šคํ‚ค๋งˆ ์ •์˜

๐Ÿ“Œ DAO ์ƒ์„ฑ

@Query("SELECT * from table") fun getAllData() : List<Data>
  • DAO ๋Š” interface ๋‚˜ abstract class ๋กœ ์ •์˜๋˜์–ด์•ผ ํ•จ

  • Annotation ์— SQL ์ฟผ๋ฆฌ๋ฅผ ์ •์˜ํ•˜๊ณ  ๊ทธ ์ฟผ๋ฆฌ๋ฅผ ์œ„ํ•œ ๋ฉ”์†Œ๋“œ๋ฅผ ์„ ์–ธ

  • ๊ฐ€๋Šฅํ•œ annotation ์œผ๋กœ @Insert, @Update, @Delete, @Query ๊ฐ€ ์žˆ์Œ

โ˜˜ @Insert, @Update, @Delete

- OnConflictStrategy.ABORT : key ์ถฉ๋Œ ์‹œ ์ข…๋ฃŒ

- OnConflictStrategy.IGNORE : key ์ถฉ๋Œ ์‹œ ๋ฌด์‹œ

- OnConflictStrategy.REPLACE : key ์ถฉ๋Œ ์‹œ ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋กœ ๋ณ€๊ฒฝ
  • @Insert, @Update, @Delete ๋Š” SQL ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜์ง€ ์•Š์•„๋„ ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•จ

  • @Insert ๋‚˜ @Update ๋Š” key๊ฐ€ ์ค‘๋ณต๋˜๋Š” ๊ฒฝ์šฐ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด onConflict ๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Œ

  • @Update ๋‚˜ @Delete ๋Š” primary key์— ํ•ด๋‹น๋˜๋Š” ํŠœํ”Œ์„ ์ฐพ์•„์„œ ๋ณ€๊ฒฝ/์‚ญ์ œ ํ•จ

โ˜˜ LiveData

@Query("SELECT * from table") fun getAllData() : List<Data>
  • @Query ๋กœ ๋ฆฌํ„ด๋˜๋Š” ๋ฐ์ดํ„ฐ์˜ ํƒ€์ž…์„ LiveData<>๋กœ ํ•˜๋ฉด, ๋‚˜์ค‘์— ์ด ๋ฐ์ดํ„ฐ๊ฐ€ ์—…๋ฐ์ดํŠธ๋  ๋•Œ Observer๋ฅผ ํ†ตํ•ด ์•Œ ์ˆ˜ ์žˆ์Œ

โ˜˜ Parameter

@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 ์ƒ์„ฑ

@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() ๋ฅผ ์ด์šฉํ•จ

โ˜˜ Migration

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")
    }
}
  • ์•ž์—์„œ MyRoomDatabase ๊ฐ์ฒด ์ƒ์„ฑ ํ›„ addMigration() ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ Migration ๋ฐฉ๋ฒ•์„ ์ง€์ •ํ–ˆ์Œ
    • ์—ฌ๋Ÿฌ๊ฐœ์˜ Migration ์ง€์ •๋„ ๊ฐ€๋Šฅ

โ˜˜ UI์™€ ์—ฐ๊ฒฐ

myDao = MyDatabase.getDatabase(this).getMyDao()
runBlocking { // (์ฃผ์˜) UI๋ฅผ ๋ธ”๋กํ•  ์ˆ˜ ์žˆ๋Š” DAO ๋ฉ”์†Œ๋“œ๋ฅผ UI ์Šค๋ ˆ๋“œ์—์„œ ๋ฐ”๋กœ ํ˜ธ์ถœํ•˜๋ฉด ์•ˆ๋จ
    myDao.insertStudent(Student(1, "james"))  // suspend ์ง€์ •๋˜์–ด ์žˆ์Œ
}
val allStudents = myDao.getAllStudents() // LiveData๋Š” Observer๋ฅผ ํ†ตํ•ด ๋น„๋™๊ธฐ์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ด
  • ์•ˆ๋“œ๋กœ์ด๋“œ ์•„ํ‚คํ…์ฒ˜์—์„œ๋Š” Repository ์™€ ViewModel์„ ์‚ฌ์šฉํ•˜๊ธธ ๊ถŒ์žฅ
    • ์ง€๊ธˆ์€ Room๊ณผ LiveData ์‚ฌ์šฉ๋งŒ ๋‹ค๋ฃธ
  • RoomDatabase ๊ฐ์ฒด์—์„œ DAO ๊ฐ์ฒด๋ฅผ ๋ฐ›์•„์˜ค๊ณ , ์ด DAO ๊ฐ์ฒด์˜ ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ ‘๊ทผํ•จ

โ˜˜ UI์™€ ์—ฐ๊ฒฐ - LiveData

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 ์Šค๋ ˆ๋“œ์—์„œ ์ง์ ‘ ํ˜ธ์ถœํ•ด๋„ ๋ฌธ์ œ ์—†์Œ

๐Ÿงฉ ์‹ค์Šต ์˜ˆ์ œ

  • build.gradle
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")
  • activity_main.xml
<?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>

  • MyEntity.kt
package com.kotdev99.android.test

@Entity(tableName = "student_table")    // ํ…Œ์ด๋ธ” ๋ช… ์ง€์ •
data class Student(
	@PrimaryKey @ColumnInfo(name = "student_id")
	val id: Int,
	val name: String
)

  • MyDatabase.kt
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
		}
	}
}

  • MyDao.kt
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
}

  • MainActivity.kt
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 = ""
					}
				}
			}
		}
	}
}

โ— ๋นŒ๋“œ ์‹œ ์—๋Ÿฌ ๋ฐœ์ƒํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด build.gradle ์ˆ˜์ •

  • build.gradle.kts
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) ์ด์ƒ์—์„œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ƒํƒœ์—์„œ ์œ„์น˜์— ์ ‘๊ทผํ•˜๋Š” ๊ถŒํ•œ

๐Ÿ“š ํ”Œ๋žซํผ API์˜ ์œ„์น˜ ๋งค๋‹ˆ์ €

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..
  • ํ˜„์žฌ ๊ธฐ๊ธฐ์— ์–ด๋–ค ์œ„์น˜ ์ œ๊ณต์ž๊ฐ€ ์žˆ๋Š”์ง€ ์•Œ๊ณ  ์‹ถ๋‹ค๋ฉด LocationManager ์˜ 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() : ์œ„์น˜ ์ œ๊ณต์ž๊ฐ€ ์ด์šฉํ•  ์ˆ˜ ์—†๋Š” ์ƒํ™ฉ์ด๋ฉด ํ˜ธ์ถœ.

๐Ÿ“š ๊ตฌ๊ธ€ Play ์„œ๋น„์Šค์˜ ์œ„์น˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

๐Ÿงท ์œ„์น˜ ์ œ๊ณต์ž ์ง€์ • ์‹œ ๊ณ ๋ ค ์‚ฌํ•ญ

  • ์ „๋ ฅ์„ ์ ๊ฒŒ ์†Œ๋น„ํ•˜๋Š”๊ฐ€?

  • ์ •ํ™•๋„๋Š” ๋†’์€๊ฐ€?

  • 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 : ์œ„์น˜ ์ œ๊ณต์ž ์ค€๋น„ ๋“ฑ ๋‹ค์–‘ํ•œ ์ฝœ๋ฐฑ์„ ์ œ๊ณต.

    • GoogleApi Client์—์„œ๋Š” GoogleApiClient.ConnectionCallbacks ์™€ GoogleApiClient.OnConnection FailedListener ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ ๊ฐ์ฒด๋ฅผ ์ง€์ •

โ˜˜ ํ”„๋กœ๋ฐ”์ด๋” ์ดˆ๊ธฐํ™”

val providerClient = LocationServices.getFusedLocationProviderClient(this)
  • FusedLocationProviderClient ์ดˆ๊ธฐํ™”

โ˜˜ ์œ„์น˜ ์ œ๊ณต์ž ์š”์ฒญ

apiClient.connect()
  • GoogleApiClient ๊ฐ์ฒด์— ์œ„์น˜ ์ œ๊ณต์ž๋ฅผ ์š”์ฒญ

โ˜˜ getLastLocation() ํ˜ธ์ถœ

// ์œ„์น˜ ์ œ๊ณต์ž๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ƒํ™ฉ์ผ ๋•Œ
    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()
        }
    }
  • onConnected() ํ•จ์ˆ˜์—์„œ FusedLocationProviderClient์˜ 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 ->
        }

๐Ÿงฉ ์‹ค์Šต ์˜ˆ์ œ

๐Ÿงท ์ข…์†์„ฑ ์„ค์ •

  • build.gradle
implementation 'com.google.android.gms:play-services-maps:18.1.0'
implementation 'com.google.android.gms:play-services-location:21.0.1'

โ• ํผ๋ฏธ์…˜ ์„ค์ •

  • Manifest.xml
<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 ํ‚ค ๋ฅผ ๋ฐœ๊ธ‰

  • ๊ตฌ๊ธ€ ๊ฐœ๋ฐœ์ž ์ฝ˜์†”์—์„œ ์–ป์€ ์ง€๋„ 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"/>

๐Ÿ“  Manifest ์ „์ฒด ์ฝ”๋“œ

  • Manifest.xml
<?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>

๐ŸŽจ ์•กํ‹ฐ๋น„ํ‹ฐ ๋ ˆ์ด์•„์›ƒ

  • activity_main.xml
<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" />

๐Ÿ“‚ ์†Œ์Šค ์ฝ”๋“œ

  • MainActivity.kt
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 ๋ฉ”๋‰ด์—์„œ ๊ฐ€์ƒ ์œ„์น˜ ์…‹ํŒ…์„ ํ•ด์ฃผ์ž.


๐Ÿงฉ ๋ฏธ์„ธ๋จผ์ง€ ์•ฑ ์‹ค์Šต

๊ณต๊ณต๋ฐ์ดํ„ฐํฌํ„ธ์—์„œ 'ํ•œ๊ตญํ™˜๊ฒฝ๊ณต๋‹จ์—์–ด์ฝ”๋ฆฌ์•„๋Œ€๊ธฐ์˜ค์—ผ์ •๋ณด' ์ด์šฉํ•œ๋‹ค.


๐Ÿ“ก ๋“ฑ๊ธ‰๊ณผ ์•„์ด์ฝ˜

๋ฏธ์„ธ๋จผ์ง€์˜ ์ˆ˜์น˜๋ณ„๋กœ ๋“ฑ๊ธ‰๊ณผ ์•„์ด์ฝ˜์„ ๊ตฌ๋ถ„ํ•œ๋‹ค.

๐Ÿ”‘ Request Parameter

https://apis.data.go.kr/B552584/ArpltnInforInqireSvc/getCtprvnRltmMesureDnsty?serviceKey=YBZfZVJMj1yF6gVroDHjfOwdMCo%2Baq87JOXQOC4lPccAi6ekELdgoIA7n4f4zVIiQcOVls%2FSVg1pEC39CVbmsA%3D%3D&returnType=json&numOfRows=50&pageNo=1&sidoName=์„œ์šธ&ver=1.0

- https://apis.data.go.kr/B552584/ArpltnInforInqireSvc/		โ†’ API ์„œ๋น„์Šค๋ช…

- getCtprvnRltmMesureDnsty?									โ†’ ์„ธ๋ถ€ ์˜ต์…˜(์‹œ๊ตฐ๊ตฌ๋ณ„ ์‹ค์‹œ๊ฐ„ ํ‰๊ท ์ •๋ณด ์กฐํšŒ)

- &serviceKey=YBZfZVJMj1yF6gVroDidoNameโ€ฆ					โ†’ ์ธ์ฆํ‚ค

- &returnType=json											โ†’ ๊ฒฐ๊ณผ ๋ฐ์ดํ„ฐ ์œ ํ˜•

- &numOfRows=50												โ†’ ํ•œํŽ˜์ด์ง€ ๊ฒฐ๊ณผ์ˆ˜

- &pageNo=1													โ†’ ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ

- &sidoName=์„œ์šธ												โ†’ ์‹œ๋„ ์ด๋ฆ„(์„œ์šธ,๋ถ€์‚ฐ,๋Œ€๊ตฌ,์ธ์ฒœโ€ฆ.)

- &ver=1.0													โ†’ ๋ฒ„์ „

๐Ÿ“— ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

  • build.gradle.kts
	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๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌํ•  ์ˆ˜๋„ ์žˆ๋‹ค.)


๐Ÿ“‚ ์†Œ์Šค ์ฝ”๋“œ

๐Ÿงท ์ข…์†์„ฑ ์ถ”๊ฐ€

  • build.gradle.kts
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")
}

โ• ํผ๋ฏธ์…˜ ์ถ”๊ฐ€

  • Manifest.xml
<uses-permission android:name="android.permission.INTERNET" />

๐ŸŽจ ์•กํ‹ฐ๋น„ํ‹ฐ ๋ ˆ์ด์•„์›ƒ

  • activity_main.xml
<?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>

  • strings.xml
<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 ์•ฑ๋‚ด๋ณ€์ˆ˜๋ช…:์ž๋ฃŒํ˜• ' ์„ ์‚ฌ์šฉํ•œ๋‹ค.

  • DustDTO.kt
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
)

๐Ÿ“— retrofit

Gson์€ Json ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€๊ณตํ•˜๋Š”๋ฐ ์žˆ์–ด ์ข€ ๋” ํŽธํ•˜๊ณ  ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ google์—์„œ ์ œ๊ณตํ•˜๋Š” Json์„ ์ค„์—ฌ Gson์ด๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค๊ณ  ๋ณด๋ฉด ๋œ๋‹ค.

  • NetWorkInterface.kt
package com.kotdev99.android.miseya.retrofit

interface NetWorkInterface {
	@GET("getCtprvnRltmMesureDnsty")    // ์‹œ๋„๋ณ„ ์‹ค์‹œ๊ฐ„ ์ธก์ •์ •๋ณด ์กฐํšŒ ์ฃผ์†Œ
	suspend fun getDust(@QueryMap param: HashMap<String, String>): Dust
}

  • NetWorkClient.kt
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)
}

๐Ÿ“ ๋ฉ”์ธ ์†Œ์Šค ์ฝ”๋“œ

  • MainActivity.kt
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
	}
}

๐Ÿ“ฒ ๊ฒฐ๊ณผ

profile
์‘์•  ๋‚˜ ์•„๊ธฐ ๋‰ด๋น„

2๊ฐœ์˜ ๋Œ“๊ธ€

comment-user-thumbnail
2023๋…„ 9์›” 15์ผ

๋•๋ถ„์— ๋„์›€ ๋์Šต๋‹ˆ๋‹ค~~ ์ž˜๋ณด๊ณ  ๊ฐ€์š”!

1๊ฐœ์˜ ๋‹ต๊ธ€