diff --git a/README.md b/README.md index 89b5266d..dc08388c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # android-map-refactoring ## 기능 요구 사항 -- 데이터베이스를 Room으로 변경한다. -- 가능한 모든 부분에 대해서 의존성 주입을 적용한다. -## 프로그래밍 요구 사항 -- 의존성 주입을 위해서 Hilt를 사용한다. -- 코드 컨벤션을 준수하며 프로그래밍한다. +- MVVM 아키텍처 패턴을 적용한다. +- DataBinding, LiveData를 사용한다. +- 비동기 처리를 Coroutine으로 변경한다. \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/MyApplication.kt b/app/src/main/java/campus/tech/kakao/MyApplication.kt index ab7243a8..e1d4eaec 100644 --- a/app/src/main/java/campus/tech/kakao/MyApplication.kt +++ b/app/src/main/java/campus/tech/kakao/MyApplication.kt @@ -2,7 +2,6 @@ package campus.tech.kakao import android.app.Application import com.kakao.vectormap.KakaoMapSdk -import campus.tech.kakao.map.BuildConfig import campus.tech.kakao.map.R import dagger.hilt.android.HiltAndroidApp diff --git a/app/src/main/java/campus/tech/kakao/map/adapter/keyword/KeywordAdapter.kt b/app/src/main/java/campus/tech/kakao/map/adapter/keyword/KeywordAdapter.kt index 0c0bd926..7e84e95f 100644 --- a/app/src/main/java/campus/tech/kakao/map/adapter/keyword/KeywordAdapter.kt +++ b/app/src/main/java/campus/tech/kakao/map/adapter/keyword/KeywordAdapter.kt @@ -9,7 +9,7 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import campus.tech.kakao.map.R -import campus.tech.kakao.map.viewmodel.OnKeywordItemClickListener +import campus.tech.kakao.map.view.OnKeywordItemClickListener class KeywordAdapter(private val onKeywordItemClickListener: OnKeywordItemClickListener) : ListAdapter( diff --git a/app/src/main/java/campus/tech/kakao/map/adapter/search/SearchAdapter.kt b/app/src/main/java/campus/tech/kakao/map/adapter/search/SearchAdapter.kt index fcfbaa47..39eaf019 100644 --- a/app/src/main/java/campus/tech/kakao/map/adapter/search/SearchAdapter.kt +++ b/app/src/main/java/campus/tech/kakao/map/adapter/search/SearchAdapter.kt @@ -9,17 +9,17 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import campus.tech.kakao.map.R -import campus.tech.kakao.map.model.Item -import campus.tech.kakao.map.viewmodel.OnSearchItemClickListener +import campus.tech.kakao.map.model.Location +import campus.tech.kakao.map.view.OnSearchItemClickListener class SearchAdapter( private val onSearchItemClickListener: OnSearchItemClickListener -) : ListAdapter( - object : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: Item, newItem: Item) = +) : ListAdapter( + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Location, newItem: Location) = oldItem.place == newItem.place - override fun areContentsTheSame(oldItem: Item, newItem: Item) = + override fun areContentsTheSame(oldItem: Location, newItem: Location) = oldItem == newItem } ) { @@ -39,13 +39,13 @@ class SearchAdapter( private val address: TextView = view.findViewById(R.id.address) private val category: TextView = view.findViewById(R.id.category) - fun bindViewHolder(item: Item, onSearchItemClickListener: OnSearchItemClickListener) { - place.text = item.place - address.text = item.address - category.text = item.category + fun bindViewHolder(location: Location, onSearchItemClickListener: OnSearchItemClickListener) { + place.text = location.place + address.text = location.address + category.text = location.category itemList.setOnClickListener { - onSearchItemClickListener.onSearchItemClick(item) + onSearchItemClickListener.onSearchItemClick(location) } } } diff --git a/app/src/main/java/campus/tech/kakao/map/api/KakaoLocalApi.kt b/app/src/main/java/campus/tech/kakao/map/api/KakaoLocalApi.kt index 8b0771f8..50333460 100644 --- a/app/src/main/java/campus/tech/kakao/map/api/KakaoLocalApi.kt +++ b/app/src/main/java/campus/tech/kakao/map/api/KakaoLocalApi.kt @@ -51,4 +51,4 @@ data class Meta( @SerializedName("is_end") val isEnd: Boolean -) +) \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/database/AppDatabase.kt b/app/src/main/java/campus/tech/kakao/map/database/AppDatabase.kt index 03e9618f..e218bfb2 100644 --- a/app/src/main/java/campus/tech/kakao/map/database/AppDatabase.kt +++ b/app/src/main/java/campus/tech/kakao/map/database/AppDatabase.kt @@ -2,13 +2,13 @@ package campus.tech.kakao.map.database import androidx.room.Database import androidx.room.RoomDatabase -import campus.tech.kakao.map.model.Keyword -import campus.tech.kakao.map.model.Item +import campus.tech.kakao.map.entity.KeywordEntity +import campus.tech.kakao.map.entity.LocationEntity import campus.tech.kakao.map.repository.keyword.KeywordDao -import campus.tech.kakao.map.repository.location.ItemDao +import campus.tech.kakao.map.repository.location.LocationDao -@Database(entities = [Keyword::class, Item::class], version = 1) +@Database(entities = [KeywordEntity::class, LocationEntity::class], version = 2) abstract class AppDatabase : RoomDatabase() { abstract fun keywordDao(): KeywordDao - abstract fun itemDao(): ItemDao -} + abstract fun locationDao(): LocationDao +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/di/AppModule.kt b/app/src/main/java/campus/tech/kakao/map/di/AppModule.kt index 420b71ba..7d7f2b85 100644 --- a/app/src/main/java/campus/tech/kakao/map/di/AppModule.kt +++ b/app/src/main/java/campus/tech/kakao/map/di/AppModule.kt @@ -1,13 +1,9 @@ -package campus.tech.kakao.di +package campus.tech.kakao.map.di -import android.content.Context import campus.tech.kakao.map.api.KakaoLocalApi -import campus.tech.kakao.map.repository.keyword.KeywordRepository -import campus.tech.kakao.map.repository.location.LocationSearcher import dagger.Module import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory @@ -31,16 +27,4 @@ object AppModule { fun provideKakaoLocalApi(retrofit: Retrofit): KakaoLocalApi { return retrofit.create(KakaoLocalApi::class.java) } - - @Provides - @Singleton - fun provideKeywordRepository(@ApplicationContext context: Context): KeywordRepository { - return KeywordRepository(context) - } - - @Provides - @Singleton - fun provideLocationSearcher(@ApplicationContext context: Context): LocationSearcher { - return LocationSearcher(context) - } } \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/di/DatabaseModule.kt b/app/src/main/java/campus/tech/kakao/map/di/DatabaseModule.kt new file mode 100644 index 00000000..f29cf8af --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/di/DatabaseModule.kt @@ -0,0 +1,56 @@ +package campus.tech.kakao.map.di + +import android.content.Context +import androidx.room.Room +import campus.tech.kakao.map.database.AppDatabase +import campus.tech.kakao.map.repository.keyword.KeywordDao +import campus.tech.kakao.map.repository.location.LocationDao +import campus.tech.kakao.map.repository.keyword.KeywordRepository +import campus.tech.kakao.map.repository.location.LocationSearcher +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object DatabaseModule { + + @Provides + @Singleton + fun provideDatabase(@ApplicationContext context: Context): AppDatabase { + return Room.databaseBuilder( + context.applicationContext, + AppDatabase::class.java, + "app_database" + ) + .fallbackToDestructiveMigration() // 필요시 마이그레이션 재생성 + .build() + } + + @Provides + @Singleton + fun provideKeywordDao(database: AppDatabase): KeywordDao { + return database.keywordDao() + } + + @Provides + @Singleton + fun provideItemDao(database: AppDatabase): LocationDao { + return database.locationDao() + } + + @Provides + @Singleton + fun provideKeywordRepository(keywordDao: KeywordDao): KeywordRepository { + return KeywordRepository(keywordDao) + } + + @Provides + @Singleton + fun provideLocationSearcher(locationDao: LocationDao): LocationSearcher { + return LocationSearcher(locationDao) + } +} diff --git a/app/src/main/java/campus/tech/kakao/map/entity/KeywordEntity.kt b/app/src/main/java/campus/tech/kakao/map/entity/KeywordEntity.kt new file mode 100644 index 00000000..0ec47d18 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/entity/KeywordEntity.kt @@ -0,0 +1,11 @@ +package campus.tech.kakao.map.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "keywords") +data class KeywordEntity( + @PrimaryKey(autoGenerate = true) val id: Int = 0, + @ColumnInfo(name = "keyword") val keyword: String +) \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/entity/LocationEntity.kt b/app/src/main/java/campus/tech/kakao/map/entity/LocationEntity.kt new file mode 100644 index 00000000..15184089 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/entity/LocationEntity.kt @@ -0,0 +1,15 @@ +package campus.tech.kakao.map.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "location") +data class LocationEntity( + @PrimaryKey(autoGenerate = true) val id: Int = 0, + @ColumnInfo(name = "place_name") val placeName: String, + @ColumnInfo(name = "address_name") val addressName: String, + @ColumnInfo(name = "category_group_name") val categoryGroupName: String, + @ColumnInfo(name = "latitude") val latitude: Double, + @ColumnInfo(name = "longitude") val longitude: Double +) \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/model/Item.kt b/app/src/main/java/campus/tech/kakao/map/model/Item.kt deleted file mode 100644 index a715e872..00000000 --- a/app/src/main/java/campus/tech/kakao/map/model/Item.kt +++ /dev/null @@ -1,14 +0,0 @@ -package campus.tech.kakao.map.model - -import androidx.room.Entity -import androidx.room.PrimaryKey - -@Entity(tableName = "location") -data class Item( - @PrimaryKey(autoGenerate = true) val id: Int = 0, // 기본값 추가 - val place: String, - val address: String, - val category: String, - val latitude: Double, - val longitude: Double -) diff --git a/app/src/main/java/campus/tech/kakao/map/model/Keyword.kt b/app/src/main/java/campus/tech/kakao/map/model/Keyword.kt index 487eae05..0b5be24a 100644 --- a/app/src/main/java/campus/tech/kakao/map/model/Keyword.kt +++ b/app/src/main/java/campus/tech/kakao/map/model/Keyword.kt @@ -7,4 +7,4 @@ import androidx.room.PrimaryKey data class Keyword( @PrimaryKey(autoGenerate = true) val id: Int = 0, val recentKeyword: String -) +) \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/model/item.kt b/app/src/main/java/campus/tech/kakao/map/model/Location.kt similarity index 74% rename from app/src/main/java/campus/tech/kakao/map/model/item.kt rename to app/src/main/java/campus/tech/kakao/map/model/Location.kt index 27363ca9..9728800c 100644 --- a/app/src/main/java/campus/tech/kakao/map/model/item.kt +++ b/app/src/main/java/campus/tech/kakao/map/model/Location.kt @@ -1,9 +1,9 @@ package campus.tech.kakao.map.model -data class Item( +data class Location( val place: String, val address: String, val category: String, val latitude: Double, - val longitude:Double -) + val longitude: Double +) \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/repository/keyword/KeywordContract.kt b/app/src/main/java/campus/tech/kakao/map/repository/keyword/KeywordContract.kt deleted file mode 100644 index c27ea5a2..00000000 --- a/app/src/main/java/campus/tech/kakao/map/repository/keyword/KeywordContract.kt +++ /dev/null @@ -1,8 +0,0 @@ -package campus.tech.kakao.map.repository.keyword - -import android.provider.BaseColumns - -object KeywordContract : BaseColumns { - const val TABLE_NAME = "keyword" - const val RECENT_KEYWORD = "recent_keyword" -} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/repository/keyword/KeywordDao.kt b/app/src/main/java/campus/tech/kakao/map/repository/keyword/KeywordDao.kt index aa4baa48..f4a188c2 100644 --- a/app/src/main/java/campus/tech/kakao/map/repository/keyword/KeywordDao.kt +++ b/app/src/main/java/campus/tech/kakao/map/repository/keyword/KeywordDao.kt @@ -1,19 +1,20 @@ package campus.tech.kakao.map.repository.keyword import androidx.room.Dao +import androidx.room.Delete import androidx.room.Insert +import androidx.room.OnConflictStrategy import androidx.room.Query -import androidx.room.Delete -import campus.tech.kakao.map.model.Keyword +import campus.tech.kakao.map.entity.KeywordEntity @Dao interface KeywordDao { - @Insert - suspend fun insert(keyword: Keyword) + @Query("SELECT * FROM keywords") + suspend fun getAllKeywords(): List - @Query("SELECT * FROM keyword") - suspend fun getAllKeywords(): List + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertKeyword(keyword: KeywordEntity) @Delete - suspend fun delete(keyword: Keyword) -} + suspend fun deleteKeyword(keyword: KeywordEntity) +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/repository/keyword/KeywordDbHelper.kt b/app/src/main/java/campus/tech/kakao/map/repository/keyword/KeywordDbHelper.kt deleted file mode 100644 index 469e36f4..00000000 --- a/app/src/main/java/campus/tech/kakao/map/repository/keyword/KeywordDbHelper.kt +++ /dev/null @@ -1,44 +0,0 @@ -package campus.tech.kakao.map.repository.keyword - -import android.content.Context -import android.database.sqlite.SQLiteOpenHelper -import android.database.sqlite.SQLiteDatabase -import android.provider.BaseColumns - -class KeywordDbHelper(context: Context) : - SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) { - - override fun onCreate(db: SQLiteDatabase?) { - db?.let { createDatabase(it) } - } - - override fun onUpgrade( - db: SQLiteDatabase?, - oldVersion: Int, - newVersion: Int - ) { - db?.let { upgradeDatabase(it, oldVersion, newVersion) } - } - - private fun createDatabase(db: SQLiteDatabase) { - db.execSQL(SQL_CREATE_ENTRIES) - } - - private fun upgradeDatabase(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { - db.execSQL(SQL_DELETE_ENTRIES) - createDatabase(db) - } - - companion object { - private const val SQL_CREATE_ENTRIES = - "CREATE TABLE ${KeywordContract.TABLE_NAME} (" + - "${BaseColumns._ID} INTEGER PRIMARY KEY," + - "${KeywordContract.RECENT_KEYWORD} TEXT)" - - private const val SQL_DELETE_ENTRIES = - "DROP TABLE IF EXISTS ${KeywordContract.TABLE_NAME}" - - private const val DATABASE_VERSION = 1 - private const val DATABASE_NAME = "keyword.db" - } -} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/repository/keyword/KeywordRepository.kt b/app/src/main/java/campus/tech/kakao/map/repository/keyword/KeywordRepository.kt index 67bab2e0..652dd9d3 100644 --- a/app/src/main/java/campus/tech/kakao/map/repository/keyword/KeywordRepository.kt +++ b/app/src/main/java/campus/tech/kakao/map/repository/keyword/KeywordRepository.kt @@ -1,27 +1,21 @@ package campus.tech.kakao.map.repository.keyword -import android.content.Context -import androidx.room.Room -import campus.tech.kakao.map.database.AppDatabase -import campus.tech.kakao.map.model.Keyword +import campus.tech.kakao.map.entity.KeywordEntity +import javax.inject.Inject +import javax.inject.Singleton -class KeywordRepository(context: Context) { - private val db = Room.databaseBuilder( - context.applicationContext, - AppDatabase::class.java, "app-database" - ).build() - private val keywordDao = db.keywordDao() +@Singleton +class KeywordRepository @Inject constructor(private val keywordDao: KeywordDao) { - suspend fun update(keyword: String) { - keywordDao.insert(Keyword(recentKeyword = keyword)) + suspend fun read(): List { + return keywordDao.getAllKeywords().map { it.keyword } } - suspend fun read(): List { - return keywordDao.getAllKeywords().map { it.recentKeyword } + suspend fun update(keyword: String) { + keywordDao.insertKeyword(KeywordEntity(keyword = keyword)) } suspend fun delete(keyword: String) { - val keywordEntity = keywordDao.getAllKeywords().find { it.recentKeyword == keyword } - keywordEntity?.let { keywordDao.delete(it) } + keywordDao.deleteKeyword(KeywordEntity(keyword = keyword)) } -} +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/repository/location/ItemDao.kt b/app/src/main/java/campus/tech/kakao/map/repository/location/ItemDao.kt deleted file mode 100644 index d76fa620..00000000 --- a/app/src/main/java/campus/tech/kakao/map/repository/location/ItemDao.kt +++ /dev/null @@ -1,19 +0,0 @@ -package campus.tech.kakao.map.repository.location - -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.Query -import androidx.room.Delete -import campus.tech.kakao.map.model.Item - -@Dao -interface ItemDao { - @Query("SELECT * FROM location WHERE category LIKE :keyword") - suspend fun search(keyword: String): List - - @Insert - suspend fun insert(item: Item) - - @Delete - suspend fun delete(item: Item) -} diff --git a/app/src/main/java/campus/tech/kakao/map/repository/location/LocationContract.kt b/app/src/main/java/campus/tech/kakao/map/repository/location/LocationContract.kt deleted file mode 100644 index db17e41a..00000000 --- a/app/src/main/java/campus/tech/kakao/map/repository/location/LocationContract.kt +++ /dev/null @@ -1,12 +0,0 @@ -package campus.tech.kakao.map.repository.location - -import android.provider.BaseColumns - -object LocationContract : BaseColumns { - const val TABLE_NAME = "location" - const val PLACE_NAME = "place_name" - const val ADDRESS_NAME = "address_name" - const val CATEGORY_GROUP_NAME = "category_group_name" - const val LATITUDE = "latitude" - const val LONGITUDE = "longitude" -} diff --git a/app/src/main/java/campus/tech/kakao/map/repository/location/LocationDao.kt b/app/src/main/java/campus/tech/kakao/map/repository/location/LocationDao.kt new file mode 100644 index 00000000..abe76027 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/repository/location/LocationDao.kt @@ -0,0 +1,11 @@ +package campus.tech.kakao.map.repository.location + +import androidx.room.Dao +import androidx.room.Query +import campus.tech.kakao.map.entity.LocationEntity + +@Dao +interface LocationDao { // ItemDao 이름 변경 + @Query("SELECT * FROM location WHERE category_group_name = :category") + suspend fun searchByCategory(category: String): List +} diff --git a/app/src/main/java/campus/tech/kakao/map/repository/location/LocationDbHelper.kt b/app/src/main/java/campus/tech/kakao/map/repository/location/LocationDbHelper.kt deleted file mode 100644 index 270bc19e..00000000 --- a/app/src/main/java/campus/tech/kakao/map/repository/location/LocationDbHelper.kt +++ /dev/null @@ -1,36 +0,0 @@ -package campus.tech.kakao.map.repository.location - -import android.content.Context -import android.database.sqlite.SQLiteDatabase -import android.database.sqlite.SQLiteOpenHelper -import android.provider.BaseColumns - -class LocationDbHelper(context: Context) : - SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) { - - override fun onCreate(db: SQLiteDatabase?) { - db?.execSQL(SQL_CREATE_ENTRIES) - } - - override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) { - db?.execSQL(SQL_DELETE_ENTRIES) - onCreate(db) - } - - companion object { - private const val SQL_CREATE_ENTRIES = - "CREATE TABLE ${LocationContract.TABLE_NAME} (" + - "${BaseColumns._ID} INTEGER PRIMARY KEY," + - "${LocationContract.PLACE_NAME} TEXT," + - "${LocationContract.ADDRESS_NAME} TEXT," + - "${LocationContract.CATEGORY_GROUP_NAME} TEXT," + - "${LocationContract.LATITUDE} REAL," + - "${LocationContract.LONGITUDE} REAL)" - - private const val SQL_DELETE_ENTRIES = - "DROP TABLE IF EXISTS ${LocationContract.TABLE_NAME}" - - private const val DATABASE_VERSION = 1 - private const val DATABASE_NAME = "location.db" - } -} diff --git a/app/src/main/java/campus/tech/kakao/map/repository/location/LocationSearcher.kt b/app/src/main/java/campus/tech/kakao/map/repository/location/LocationSearcher.kt index c9d3bcce..988fc310 100644 --- a/app/src/main/java/campus/tech/kakao/map/repository/location/LocationSearcher.kt +++ b/app/src/main/java/campus/tech/kakao/map/repository/location/LocationSearcher.kt @@ -1,19 +1,22 @@ package campus.tech.kakao.map.repository.location -import android.content.Context -import androidx.room.Room -import campus.tech.kakao.map.database.AppDatabase -import campus.tech.kakao.map.model.Item +import campus.tech.kakao.map.model.Location +import javax.inject.Inject +import javax.inject.Singleton -class LocationSearcher(context: Context) { - private val db = Room.databaseBuilder( - context.applicationContext, - AppDatabase::class.java, "app-database" - ).build() - private val itemDao = db.itemDao() - - suspend fun search(keyword: String): List { - return itemDao.search("%$keyword%") +@Singleton +class LocationSearcher @Inject constructor(private val locationDao: LocationDao) { + suspend fun search(keyword: String): List { + val locationEntities = locationDao.searchByCategory(keyword) + return locationEntities.map { + Location( + place = it.placeName, + address = it.addressName, + category = it.categoryGroupName, + latitude = it.latitude, + longitude = it.longitude + ) + } } -} +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/view/MainActivity.kt b/app/src/main/java/campus/tech/kakao/map/view/MainActivity.kt index 78244ce6..cdc4b471 100644 --- a/app/src/main/java/campus/tech/kakao/map/view/MainActivity.kt +++ b/app/src/main/java/campus/tech/kakao/map/view/MainActivity.kt @@ -16,6 +16,7 @@ import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.Observer import androidx.databinding.DataBindingUtil +import campus.tech.kakao.map.databinding.ActivityMainBinding import com.google.android.material.bottomsheet.BottomSheetBehavior import com.kakao.vectormap.* import com.kakao.vectormap.camera.CameraAnimation @@ -25,14 +26,13 @@ import com.kakao.vectormap.label.LabelOptions import com.kakao.vectormap.label.LabelStyle import com.kakao.vectormap.label.LabelStyles import com.kakao.vectormap.label.LabelTextStyle -import campus.tech.kakao.map.databinding.ActivityMainBinding -import campus.tech.kakao.map.model.Item +import campus.tech.kakao.map.model.Location import campus.tech.kakao.map.repository.location.LocationSearcher import campus.tech.kakao.map.view.SearchActivity import campus.tech.kakao.map.viewmodel.keyword.KeywordViewModel import campus.tech.kakao.map.viewmodel.main.MainViewModel -import campus.tech.kakao.map.viewmodel.OnSearchItemClickListener -import campus.tech.kakao.map.viewmodel.OnKeywordItemClickListener +import campus.tech.kakao.map.view.OnSearchItemClickListener +import campus.tech.kakao.map.view.OnKeywordItemClickListener import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @@ -55,18 +55,19 @@ class MainActivity : AppCompatActivity(), OnSearchItemClickListener, OnKeywordIt private lateinit var bottomSheetAddress: TextView private lateinit var bottomSheetLayout: FrameLayout private lateinit var searchResultLauncher: ActivityResultLauncher + + // ViewModel을 Lazy하게 제공받기 private val keywordViewModel: KeywordViewModel by viewModels() private val mainViewModel: MainViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main) - binding.lifecycleOwner = this // 생명주기 소유자 성정 - binding.viewModel = mainViewModel // Viewmodel 바인딩 - binding.keywordViewModel = keywordViewModel // Keyword viewmodel 바인딩 + binding.lifecycleOwner = this // 생명주기 소유자 설정 + binding.viewModel = mainViewModel // ViewModel 바인딩 + binding.keywordViewModel = keywordViewModel // Keyword ViewModel 바인딩 // View 초기화 - // 바인딩을 활용하여 초기화하도록 변경 initializeViews(binding) // ActivityResultLauncher 초기화 @@ -103,13 +104,13 @@ class MainActivity : AppCompatActivity(), OnSearchItemClickListener, OnKeywordIt } // Observe the last marker position - mainViewModel.lastMarkerPosition.observe(this, Observer { item -> - item?.let { - Log.d(TAG, "Loaded last marker position: lat=${it.latitude}, lon=${it.longitude}, placeName=${it.place}, roadAddressName=${it.address}") - addLabel(it) - val position = LatLng.from(it.latitude, it.longitude) + mainViewModel.lastMarkerPosition.observe(this, Observer { location: Location? -> // 타입 명시적으로 지정 + location?.let { loc -> // 변수 이름을 명확하게 변경 + Log.d(TAG, "Loaded last marker position: lat=${loc.latitude}, lon=${loc.longitude}, placeName=${loc.place}, roadAddressName=${loc.address}") + addLabel(loc) + val position = LatLng.from(loc.latitude, loc.longitude) moveCamera(position) - updateBottomSheet(it.place, it.address) + updateBottomSheet(loc.place, loc.address) } }) } @@ -145,10 +146,10 @@ class MainActivity : AppCompatActivity(), OnSearchItemClickListener, OnKeywordIt Log.d(TAG, "Search result: $placeName, $roadAddressName, $latitude, $longitude") - // latitude와 longitude 값을 Double로 명시적으로 변환하여 Item 객체를 생성 - val item = Item(place = placeName, address = roadAddressName, category = "", latitude = latitude, longitude = longitude) - addLabel(item) - mainViewModel.saveLastMarkerPosition(item) + // latitude와 longitude 값을 Double로 명시적으로 변환하여 Location 객체를 생성 + val location = Location(place = placeName, address = roadAddressName, category = "", latitude = latitude, longitude = longitude) + addLabel(location) + mainViewModel.saveLastMarkerPosition(location) } private fun showToast(message: String) { @@ -195,11 +196,11 @@ class MainActivity : AppCompatActivity(), OnSearchItemClickListener, OnKeywordIt }) } - private fun addLabel(item: Item) { - val placeName = item.place - val roadAddressName = item.address - val latitude = item.latitude - val longitude = item.longitude + private fun addLabel(location: Location) { // 변수 이름 변경 + val placeName = location.place + val roadAddressName = location.address + val latitude = location.latitude + val longitude = location.longitude val position = LatLng.from(latitude, longitude) val styles = kakaoMap?.labelManager?.addLabelStyles( @@ -243,10 +244,10 @@ class MainActivity : AppCompatActivity(), OnSearchItemClickListener, OnKeywordIt // 아무 작업도 수행하지 않음 } - override fun onSearchItemClick(item: Item) { + override fun onSearchItemClick(location: Location) { // 변수 이름 변경 // 검색 결과 목록에서 항목을 선택했을 때의 동작을 정의 - addLabel(item) - mainViewModel.saveLastMarkerPosition(item) + addLabel(location) + mainViewModel.saveLastMarkerPosition(location) } companion object { diff --git a/app/src/main/java/campus/tech/kakao/map/viewmodel/OnKeywordItemClickListener.kt b/app/src/main/java/campus/tech/kakao/map/view/OnKeywordItemClickListener.kt similarity index 77% rename from app/src/main/java/campus/tech/kakao/map/viewmodel/OnKeywordItemClickListener.kt rename to app/src/main/java/campus/tech/kakao/map/view/OnKeywordItemClickListener.kt index 3dbd578a..a98e667e 100644 --- a/app/src/main/java/campus/tech/kakao/map/viewmodel/OnKeywordItemClickListener.kt +++ b/app/src/main/java/campus/tech/kakao/map/view/OnKeywordItemClickListener.kt @@ -1,4 +1,4 @@ -package campus.tech.kakao.map.viewmodel +package campus.tech.kakao.map.view interface OnKeywordItemClickListener { fun onKeywordItemDeleteClick(keyword: String) diff --git a/app/src/main/java/campus/tech/kakao/map/view/OnSearchItemClickListener.kt b/app/src/main/java/campus/tech/kakao/map/view/OnSearchItemClickListener.kt new file mode 100644 index 00000000..178f8eeb --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/map/view/OnSearchItemClickListener.kt @@ -0,0 +1,7 @@ +package campus.tech.kakao.map.view + +import campus.tech.kakao.map.model.Location + +interface OnSearchItemClickListener { + fun onSearchItemClick(location: Location) +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/view/SearchActivity.kt b/app/src/main/java/campus/tech/kakao/map/view/SearchActivity.kt index e0f578b5..31733a8d 100644 --- a/app/src/main/java/campus/tech/kakao/map/view/SearchActivity.kt +++ b/app/src/main/java/campus/tech/kakao/map/view/SearchActivity.kt @@ -4,42 +4,34 @@ import android.app.Activity import android.content.Intent import android.os.Bundle import android.view.View +import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.core.widget.doAfterTextChanged -import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import campus.tech.kakao.map.adapter.keyword.KeywordAdapter import campus.tech.kakao.map.adapter.search.SearchAdapter -import campus.tech.kakao.map.api.KakaoLocalApi import campus.tech.kakao.map.databinding.ActivitySearchBinding -import campus.tech.kakao.map.model.Item -import campus.tech.kakao.map.viewmodel.OnKeywordItemClickListener -import campus.tech.kakao.map.viewmodel.OnSearchItemClickListener +import campus.tech.kakao.map.model.Location import campus.tech.kakao.map.viewmodel.keyword.KeywordViewModel import campus.tech.kakao.map.viewmodel.search.SearchViewModel import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject @AndroidEntryPoint class SearchActivity : AppCompatActivity(), OnSearchItemClickListener, OnKeywordItemClickListener { private lateinit var binding: ActivitySearchBinding - private lateinit var searchViewModel: SearchViewModel - private lateinit var keywordViewModel: KeywordViewModel - private lateinit var searchAdapter: SearchAdapter - private lateinit var keywordAdapter: KeywordAdapter + + // ViewModel을 Lazy하게 제공받기 + private val searchViewModel: SearchViewModel by viewModels() + private val keywordViewModel: KeywordViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivitySearchBinding.inflate(layoutInflater) setContentView(binding.root) - // ViewModel 초기화 - searchViewModel = ViewModelProvider(this).get(SearchViewModel::class.java) - keywordViewModel = ViewModelProvider(this).get(KeywordViewModel::class.java) - // 검색 결과 RecyclerView 설정 - searchAdapter = SearchAdapter(this) + var searchAdapter = SearchAdapter(this) binding.searchResultView.apply { layoutManager = LinearLayoutManager(this@SearchActivity) adapter = searchAdapter @@ -47,7 +39,7 @@ class SearchActivity : AppCompatActivity(), OnSearchItemClickListener, OnKeyword } // 검색어 목록 RecyclerView 설정 - keywordAdapter = KeywordAdapter(this) + var keywordAdapter = KeywordAdapter(this) binding.keywordHistoryView.apply { layoutManager = LinearLayoutManager(this@SearchActivity, LinearLayoutManager.HORIZONTAL, false) adapter = keywordAdapter @@ -69,21 +61,21 @@ class SearchActivity : AppCompatActivity(), OnSearchItemClickListener, OnKeyword } // 검색 결과 관찰하여 UI 업데이트 - searchViewModel.items.observe(this) { + searchViewModel.locations.observe(this) { searchAdapter.submitList(it) binding.searchResultView.visibility = if (it.isEmpty()) View.GONE else View.VISIBLE binding.emptyView.visibility = if (it.isEmpty()) View.VISIBLE else View.GONE } } - override fun onSearchItemClick(item: Item) { + override fun onSearchItemClick(location: Location) { // 검색 항목 클릭 시 선택된 데이터를 반환하고 검색어 저장 - keywordViewModel.saveKeyword(item.place) + keywordViewModel.saveKeyword(location.place) val resultIntent = Intent().apply { - putExtra("place_name", item.place) - putExtra("road_address_name", item.address) - putExtra("latitude", item.latitude) - putExtra("longitude", item.longitude) + putExtra("place_name", location.place) + putExtra("road_address_name", location.address) + putExtra("latitude", location.latitude) + putExtra("longitude", location.longitude) } setResult(Activity.RESULT_OK, resultIntent) finish() @@ -100,4 +92,3 @@ class SearchActivity : AppCompatActivity(), OnSearchItemClickListener, OnKeyword keywordViewModel.deleteKeyword(keyword) } } - diff --git a/app/src/main/java/campus/tech/kakao/map/viewmodel/OnSearchItemClickListener.kt b/app/src/main/java/campus/tech/kakao/map/viewmodel/OnSearchItemClickListener.kt deleted file mode 100644 index df72d9f9..00000000 --- a/app/src/main/java/campus/tech/kakao/map/viewmodel/OnSearchItemClickListener.kt +++ /dev/null @@ -1,7 +0,0 @@ -package campus.tech.kakao.map.viewmodel - -import campus.tech.kakao.map.model.Item - -interface OnSearchItemClickListener { - fun onSearchItemClick(item: Item) -} diff --git a/app/src/main/java/campus/tech/kakao/map/viewmodel/keyword/KeywordViewmodel.kt b/app/src/main/java/campus/tech/kakao/map/viewmodel/keyword/KeywordViewmodel.kt index a9349aed..826d63c6 100644 --- a/app/src/main/java/campus/tech/kakao/map/viewmodel/keyword/KeywordViewmodel.kt +++ b/app/src/main/java/campus/tech/kakao/map/viewmodel/keyword/KeywordViewmodel.kt @@ -40,5 +40,4 @@ class KeywordViewModel @Inject constructor( loadKeywords() } } -} - +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/viewmodel/keyword/KeywordViewmodelFactory.kt b/app/src/main/java/campus/tech/kakao/map/viewmodel/keyword/KeywordViewmodelFactory.kt deleted file mode 100644 index 8fbc0893..00000000 --- a/app/src/main/java/campus/tech/kakao/map/viewmodel/keyword/KeywordViewmodelFactory.kt +++ /dev/null @@ -1,15 +0,0 @@ -package campus.tech.kakao.map.viewmodel.keyword - -import android.content.Context -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import campus.tech.kakao.map.repository.keyword.KeywordRepository - -class KeywordViewModelFactory(private val context: Context) : ViewModelProvider.Factory { - override fun create(modelClass: Class): T { - if (modelClass.isAssignableFrom(KeywordViewModel::class.java)) { - return KeywordViewModel(KeywordRepository.getInstance(context)) as T - } - throw IllegalArgumentException("Unknown ViewModel class") - } -} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/viewmodel/main/MainViewmodel.kt b/app/src/main/java/campus/tech/kakao/map/viewmodel/main/MainViewmodel.kt index 67af6aa0..664483fb 100644 --- a/app/src/main/java/campus/tech/kakao/map/viewmodel/main/MainViewmodel.kt +++ b/app/src/main/java/campus/tech/kakao/map/viewmodel/main/MainViewmodel.kt @@ -6,7 +6,7 @@ import android.content.SharedPreferences import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import campus.tech.kakao.map.model.Item +import campus.tech.kakao.map.model.Location import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject @@ -16,22 +16,22 @@ class MainViewModel @Inject constructor( ) : AndroidViewModel(application) { private val sharedPreferences: SharedPreferences = application.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) - private val _lastMarkerPosition = MutableLiveData() - val lastMarkerPosition: LiveData get() = _lastMarkerPosition + private val _lastMarkerPosition = MutableLiveData() + val lastMarkerPosition: LiveData get() = _lastMarkerPosition init { loadLastMarkerPosition() } - fun saveLastMarkerPosition(item: Item) { + fun saveLastMarkerPosition(location: Location) { with(sharedPreferences.edit()) { - putFloat(PREF_LATITUDE, item.latitude.toFloat()) - putFloat(PREF_LONGITUDE, item.longitude.toFloat()) - putString(PREF_PLACE_NAME, item.place) - putString(PREF_ROAD_ADDRESS_NAME, item.address) + putFloat(PREF_LATITUDE, location.latitude.toFloat()) + putFloat(PREF_LONGITUDE, location.longitude.toFloat()) + putString(PREF_PLACE_NAME, location.place) + putString(PREF_ROAD_ADDRESS_NAME, location.address) apply() } - _lastMarkerPosition.value = item + _lastMarkerPosition.value = location } private fun loadLastMarkerPosition() { @@ -42,7 +42,7 @@ class MainViewModel @Inject constructor( val roadAddressName = sharedPreferences.getString(PREF_ROAD_ADDRESS_NAME, "") ?: "" _lastMarkerPosition.value = if (placeName.isNotEmpty() && roadAddressName.isNotEmpty()) { - Item(place = placeName, address = roadAddressName, category = "", latitude = latitude, longitude = longitude) + Location(place = placeName, address = roadAddressName, category = "", latitude = latitude, longitude = longitude) } else { null } @@ -58,4 +58,4 @@ class MainViewModel @Inject constructor( private const val PREF_PLACE_NAME = "lastPlaceName" private const val PREF_ROAD_ADDRESS_NAME = "lastRoadAddressName" } -} +} \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/map/viewmodel/main/MainViewmodelFactory.kt b/app/src/main/java/campus/tech/kakao/map/viewmodel/main/MainViewmodelFactory.kt deleted file mode 100644 index 02310acd..00000000 --- a/app/src/main/java/campus/tech/kakao/map/viewmodel/main/MainViewmodelFactory.kt +++ /dev/null @@ -1,14 +0,0 @@ -package campus.tech.kakao.map.viewmodel.main - -import android.app.Application -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider - -class MainViewModelFactory(private val application: Application) : ViewModelProvider.Factory { - override fun create(modelClass: Class): T { - if (modelClass.isAssignableFrom(MainViewModel::class.java)) { - return MainViewModel(application) as T - } - throw IllegalArgumentException("Unknown ViewModel class") - } -} diff --git a/app/src/main/java/campus/tech/kakao/map/viewmodel/search/SearchViewmodel.kt b/app/src/main/java/campus/tech/kakao/map/viewmodel/search/SearchViewmodel.kt index 21a7c556..c3beb814 100644 --- a/app/src/main/java/campus/tech/kakao/map/viewmodel/search/SearchViewmodel.kt +++ b/app/src/main/java/campus/tech/kakao/map/viewmodel/search/SearchViewmodel.kt @@ -2,7 +2,7 @@ package campus.tech.kakao.map.viewmodel.search import androidx.lifecycle.* import campus.tech.kakao.map.api.KakaoLocalApi -import campus.tech.kakao.map.model.Item +import campus.tech.kakao.map.model.Location import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import javax.inject.Inject @@ -11,16 +11,16 @@ import javax.inject.Inject class SearchViewModel @Inject constructor( private val api: KakaoLocalApi ) : ViewModel() { - private val _items = MutableLiveData>() - val items: LiveData> - get() = _items + private val _locations = MutableLiveData>() + val locations: LiveData> + get() = _locations fun searchLocationData(keyword: String) { viewModelScope.launch { try { val response = api.searchKeyword("KakaoAK ${campus.tech.kakao.map.BuildConfig.KAKAO_REST_API_KEY}", keyword) - _items.value = response.documents.map { - Item( + _locations.value = response.documents.map { + Location( place = it.placeName, address = it.addressName, category = it.categoryGroupName, diff --git a/app/src/main/java/campus/tech/kakao/map/viewmodel/search/SearchViewmodelFactory.kt b/app/src/main/java/campus/tech/kakao/map/viewmodel/search/SearchViewmodelFactory.kt deleted file mode 100644 index c8b5450c..00000000 --- a/app/src/main/java/campus/tech/kakao/map/viewmodel/search/SearchViewmodelFactory.kt +++ /dev/null @@ -1,15 +0,0 @@ -package campus.tech.kakao.map.viewmodel.search - -import android.content.Context -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import campus.tech.kakao.map.api.KakaoLocalApi - -class SearchViewModelFactory(private val api: KakaoLocalApi) : ViewModelProvider.Factory { - override fun create(modelClass: Class): T { - if (modelClass.isAssignableFrom(SearchViewModel::class.java)) { - return SearchViewModel(api) as T - } - throw IllegalArgumentException("Unknown ViewModel class") - } -} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 7e3b48ae..5f852782 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,95 +1,107 @@ - + xmlns:tools="http://schemas.android.com/tools"> - - - + + + + - + tools:context=".MainActivity"> - - - + - - + android:hint="검색어를 입력하세요" + android:layout_alignParentTop="true" + android:layout_margin="16dp" + android:background="@android:drawable/editbox_background"/> - - - + android:gravity="center"> - - - \ No newline at end of file + + + + + + + + + + + + + + + \ No newline at end of file