-
Notifications
You must be signed in to change notification settings - Fork 33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
전남대 Android_장수민_3주차 과제(1단계) #48
Changes from all commits
44bbc68
b538030
6e780ef
1fabc74
ff6a47d
05d398d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,19 @@ | ||
# android-map-search | ||
|
||
### 기능 요구 사항 | ||
이전 단계와 기능은 비슷하나 **카카오로컬 API**를 사용한다. | ||
|
||
- 검색어를 입력하면 검색 결과가 15개 이상 표시된다. | ||
- 검색 결과 목록은 세로 스크롤이 된다. | ||
- 입력한 검색어는 X를 눌러서 삭제할 수 있다. | ||
- 검색 결과 목록에서 하나의 항목을 선택할 수 있다. | ||
- 선택된 항목은 검색어 저장 목록에 추가된다. | ||
- 저장된 검색어 목록은 가로 스크롤이 된다. | ||
- 저장된 검색어는 X를 눌러서 삭제할 수 있다. | ||
- 저장된 검색어는 앱을 재실행하여도 유지된다. | ||
|
||
### 프로그래밍 요구 사항 | ||
- 검색 데이터는 카카오로컬 API를 사용한다. | ||
- 카카오 API 사용을 위한 앱 키를 외부에 노출하지 않는다. | ||
- 가능한 MVVM 아키텍처 패턴을 적용하도록 한다. | ||
- 코드 컨벤션을 준수하며 프로그래밍한다. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,10 @@ | ||
package campus.tech.kakao.map | ||
|
||
import android.os.Bundle | ||
import android.os.Handler | ||
import android.os.Looper | ||
import android.text.Editable | ||
import android.text.TextWatcher | ||
import android.util.Log | ||
import android.view.LayoutInflater | ||
import android.view.View | ||
import androidx.activity.viewModels | ||
|
@@ -18,15 +19,14 @@ class MainActivity : AppCompatActivity() { | |
} | ||
|
||
private val placeAdapter: PlaceAdapter by lazy { | ||
PlaceAdapter(placeList, | ||
PlaceAdapter(locationList, | ||
LayoutInflater.from(this@MainActivity), | ||
object : | ||
PlaceAdapter.OnItemClickListener { | ||
override fun onItemClick(position: Int) { | ||
val item = placeAdapter.getItem(position) | ||
val searchHistory = SearchHistory(item.name) | ||
val searchHistory = SearchHistory(item.placeName) | ||
viewModel.saveSearchHistory(searchHistory) | ||
Log.d("실행", "저장") | ||
} | ||
} | ||
) | ||
|
@@ -41,12 +41,10 @@ class MainActivity : AppCompatActivity() { | |
val item = viewModel.searchHistoryList.value?.get(position) | ||
if (item != null) { | ||
mainBinding.search.setText(item.searchHistory) | ||
Log.d("실행", "검색창") | ||
} | ||
} | ||
override fun onXMarkClick(position: Int) { | ||
viewModel.deleteSearchHistory(position) | ||
Log.d("실행", "삭제") | ||
} | ||
} | ||
) | ||
|
@@ -57,6 +55,7 @@ class MainActivity : AppCompatActivity() { | |
|
||
private var placeList: List<Place> = emptyList() | ||
|
||
private var locationList: List<Document> = emptyList() | ||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
mainBinding = ActivityMainBinding.inflate(layoutInflater) | ||
|
@@ -91,15 +90,21 @@ class MainActivity : AppCompatActivity() { | |
|
||
private fun setupSearchEditText(mainBinding: ActivityMainBinding) { | ||
val searchEditText = mainBinding.search | ||
val handler = Handler(Looper.getMainLooper()) | ||
val delayMillis = 800L | ||
|
||
searchEditText.addTextChangedListener(object : TextWatcher { | ||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} | ||
|
||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} | ||
|
||
override fun afterTextChanged(s: Editable?) { | ||
val searchText = searchEditText.text.toString() | ||
viewModel.getSearchResult(searchText) | ||
val searchText = s.toString().trim() | ||
|
||
handler.removeCallbacksAndMessages(null) | ||
handler.postDelayed({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 만약 불필요한 네트워크 콜을 줄이고, 버퍼를 주고싶은 것이라면 postDelayed대신 텍스트 변경 이벤트가 throttleing되도록 만들어 보세요. 코루틴을 활용하여 적용해보시면 좋을 것 같네요 :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 넵...!! 알려주셔서 감사합니다! |
||
viewModel.getPlace(searchText) | ||
}, delayMillis) | ||
} | ||
}) | ||
} | ||
|
@@ -110,7 +115,7 @@ class MainActivity : AppCompatActivity() { | |
}) | ||
viewModel.getSearchHistoryList() | ||
|
||
viewModel.placeList.observe(this@MainActivity, Observer { | ||
viewModel.locationList.observe(this@MainActivity, Observer { | ||
placeAdapter.setData(it) | ||
mainBinding.emptyMainText.visibility = if (it.isNullOrEmpty()) View.VISIBLE else View.GONE | ||
}) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,14 @@ | ||
package campus.tech.kakao.map | ||
|
||
import android.content.Context | ||
import android.util.Log | ||
import androidx.lifecycle.LiveData | ||
import androidx.lifecycle.MutableLiveData | ||
import androidx.lifecycle.ViewModel | ||
import campus.tech.kakao.map.RetrofitInstance.retrofitService | ||
import retrofit2.Call | ||
import retrofit2.Callback | ||
import retrofit2.Response | ||
|
||
class MainViewModel(context: Context) : ViewModel() { | ||
private val dbHelper: DBHelper = DBHelper(context) | ||
|
@@ -12,6 +17,7 @@ class MainViewModel(context: Context) : ViewModel() { | |
|
||
private var _placeList = MutableLiveData<List<Place>>() | ||
private val _searchHistoryList = MutableLiveData<List<SearchHistory>>() | ||
private var _locationList = MutableLiveData<List<Document>>() | ||
|
||
init { | ||
_searchHistoryList.value = getSearchHistory() | ||
|
@@ -23,6 +29,9 @@ class MainViewModel(context: Context) : ViewModel() { | |
val placeList: LiveData<List<Place>> | ||
get() = _placeList | ||
|
||
val locationList: LiveData<List<Document>> | ||
get() = _locationList | ||
|
||
fun insertPlace(place: Place) { | ||
dbHelper.insert(db, place) | ||
} | ||
|
@@ -75,4 +84,33 @@ class MainViewModel(context: Context) : ViewModel() { | |
preferenceManager.deleteArrayListItem(Constants.SEARCH_HISTORY_KEY, position) | ||
getSearchHistoryList() | ||
} | ||
|
||
fun getPlace(query: String) { | ||
if (query.isEmpty()) { | ||
_locationList.value = emptyList() | ||
} else { | ||
retrofitService.getPlaces("KakaoAK "+BuildConfig.KAKAO_REST_API_KEY, query).enqueue(object : Callback<Location> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 뷰모델이 직접 retrofitService에 접근하는 대신, 이러한 데이터 조작 역할을 repository 컴포넌트를 만들어 위임해보세요. 이전에 전달드렸던 안드로이드 공식 문서의 아키텍쳐 부분을 참고하여 수정해주세요. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 넵! 수정하겠습니다! |
||
override fun onResponse( | ||
call: Call<Location>, | ||
response: Response<Location> | ||
) { | ||
if (response.isSuccessful) { | ||
val body = response.body() | ||
if (body != null) { | ||
_locationList.postValue(body.documents) | ||
Log.d("성공", ""+ body.documents) | ||
} else { | ||
_locationList.postValue(emptyList()) | ||
} | ||
} else { | ||
Log.d("태그",response.code().toString()) | ||
} | ||
} | ||
|
||
override fun onFailure(call: Call<Location>, t: Throwable) { | ||
Log.d("error", ""+ t) | ||
} | ||
}) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,42 @@ | ||
package campus.tech.kakao.map | ||
|
||
import com.google.gson.annotations.SerializedName | ||
data class Place ( | ||
val name: String, | ||
val address: String, | ||
val category: String, | ||
) | ||
|
||
// API | ||
data class Location ( | ||
@SerializedName("documents") val documents: List<Document>, | ||
@SerializedName("meta") val meta: Meta | ||
) | ||
|
||
data class Document ( | ||
@SerializedName("address_name") val addressName: String, | ||
@SerializedName("category_group_code") val categoryGroupCode: String, | ||
@SerializedName("category_group_name") val categoryGroupName: String, | ||
@SerializedName("category_name") val categoryName: String, | ||
@SerializedName("distance") val distance: String, | ||
@SerializedName("id") val id: String, | ||
@SerializedName("phone") val phone: String, | ||
@SerializedName("place_name") val placeName: String, | ||
@SerializedName("place_url") val placeUrl: String, | ||
@SerializedName("road_address_name") val roadAddressName: String, | ||
@SerializedName("x") val x: String, | ||
@SerializedName("y") val y: String | ||
) | ||
|
||
data class Meta( | ||
@SerializedName("is_end") val isEnd: Boolean, | ||
@SerializedName("pageable_count") val pageableCount: Int, | ||
@SerializedName("same_name") val sameName: SameName, | ||
@SerializedName("total_count") val totalCount: Int | ||
) | ||
|
||
data class SameName( | ||
@SerializedName("keyword") val keyword: String, | ||
@SerializedName("region") val region: List<String>, | ||
@SerializedName("selected_region") val selectedRegion: String | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package campus.tech.kakao.map | ||
|
||
import retrofit2.Retrofit | ||
import retrofit2.converter.gson.GsonConverterFactory | ||
|
||
object RetrofitInstance { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 추후 DI 개념을 학습하게 되신다면, 어디에 선언해야 하는지에 대한 고민에 답을 얻으실 수 있으실거에요. 현재로서는 뷰모델에 선언보다는 object로 선언하는 것이 더 좋아보입니다 :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 네! 코드리뷰 해주셔서 감사합니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 혹시 제가 컨플릭을 고쳤는데... 괜찮은 걸까요!?!! 제가 하면 안되는 것은 아니였겠죠...?? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아니요! 컨플릭트 해결해주셔야 제가 머지가 가능해서요. ㅎㅎ PR은 머지하도록 하겠습니다. |
||
val retrofitService = Retrofit.Builder() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 만약 여러 쓰레드에서 동시에 retrofitService에 접근하게 된다면 어떻게 될까요? 이에 대해서 생각해보시고 적절한 해결방법을 코드에 적용해보세요. :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아... 생각을 못 하고 있었습니다!! 적절한 방법을 찾아보겠습니다! |
||
.baseUrl("https://dapi.kakao.com/v2/local/search/") | ||
.addConverterFactory(GsonConverterFactory.create()) | ||
.build() | ||
.create(RetrofitService::class.java) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package campus.tech.kakao.map | ||
|
||
import com.google.gson.internal.GsonBuildConfig | ||
import retrofit2.Call | ||
import retrofit2.http.GET | ||
import retrofit2.http.Header | ||
import retrofit2.http.Query | ||
|
||
interface RetrofitService { | ||
@GET("keyword") | ||
fun getPlaces( | ||
@Header("Authorization") authorization: String = "KakaoAK "+BuildConfig.KAKAO_REST_API_KEY , | ||
@Query("query") query: String | ||
): Call<Location> | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
locationList, placeList이 MainActivity에서 선언되어야 할 필요가 있을까요? 불필요한 필드 선언은 코드를 복잡하게 만듭니다. :)
만약 불필요하다면 코드를 지워주세요~!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵!! 처음에 추가해 둔 placeList 보고 같이 선언해 둔 건데 그럴 필요가 없었던 것 같습니다! 2단계 진행하면서 지우도록 하겠습니다!