2주차 배달앱 강의 스터디 수업 내용
1주차 복습 내용
http://javaexpert.tistory.com/971?category=720102
2주차 클론 수업 내용
Category
데이터 클래스 구현List<Category>
구현- 리사이클러뷰를 그리드 레이아웃 매니저로 구현 및 데이터 연결
- GPS 기능 켜져있는지 체크
- 유저 위치 가져오기 위해 퍼미션 작업
- Presenter 함수 구현
- Address Object Data 클래스 구현
- 퍼미션 승인 후 트래킹 진행
- 좌표를 가져온 후 네이버 지오코딩 api 를 통해 자세한 동을 가져온다.
- Retrofit Api 구현
- converter-moshi 를 통해 JSON -> Address Class 로 변환
- RxJava Converter 구현
- RxJava Subscribe 를 통해 화면에 푸시
- 최종적으로 화면에 동이름을 뿌리도록 진행
Category
데이터 클래스 구현
- resId 는 아이템에 들어갈 이미지 drawable 위치
- background 는 아이템 뒤에 들어갈 백그라운드 이미지 ( drawable xml 이미지)
- type 은 서브 페이지로 넘길때 필요한 파라미터
data class Category (
val no: Int,
val resId: Int,
val background: Int = R.drawable.bdt_btn_white,
val title: String = "",
val type: String = ""
)
리사이클러뷰를 그리드 레이아웃 매니저로 구현
content_main.xml
에서RecyclerView
를 구현
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/mainRecyclerView"
android:clipToPadding="false"
android:padding="@dimen/item_offset"
/>
List<Category>
구현해서 리스트에 데이터로 연결
- 메인화면에 표시될 배열 미리 정의
val items = arrayOf(
Category(0, R.drawable.bdt_home_a_ctgr_all, title = "전체"),
Category(1, R.drawable.bdt_home_a_ctgr_seasonal, R.drawable.bdt_btn_brown, title = "계절메뉴"),
Category(2, R.drawable.bdt_home_a_ctgr_chicken, title = "치킨"),
Category(3, R.drawable.bdt_home_a_ctgr_chinese, title = "중식"),
Category(4, R.drawable.bdt_home_a_ctgr_pizza, title = "피자"),
Category(5, R.drawable.bdt_home_a_ctgr_bossam, title = "족발,보쌈"),
Category(6, R.drawable.bdt_home_a_ctgr_burger, title = "도시락"),
Category(7, R.drawable.bdt_home_a_ctgr_japanese, title = "일식")
)
리사이클러뷰를 그리드 레이아웃 매니저로 구현 및 데이터 연결
...
private fun initRecyclerView() {
mainRecyclerView.layoutManager = GridLayoutManager(getContext(), 2)
mainRecyclerView.adapter = MainRecylerViewAdapter(items, getContext(), ::handleItem)
val itemDecoration = ItemOffsetDecoration(getContext(), R.dimen.item_offset)
mainRecyclerView.addItemDecoration(itemDecoration)
}
GPS 기능 켜져있는지 체크
- 기능이 꺼져있는 경우 강제로 GPS 설정화면으로 이동시킴
fun checkGPS() {
val service = getSystemService(Context.LOCATION_SERVICE) as LocationManager
val enabled = service
.isProviderEnabled(LocationManager.GPS_PROVIDER)
if (!enabled) {
val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
startActivity(intent)
}
}
유저 위치 가져오기 위한 퍼미션 체크
private fun checkPermission() {
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
updateMyLocation()
} else {
ActivityCompat.requestPermissions(this,arrayOf(android.Manifest.permission.ACCESS_FINE_LOCATION), 1);
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
if (permissions.size == 1 &&
permissions[0] == android.Manifest.permission.ACCESS_FINE_LOCATION &&
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return
}
}else {
updateMyLocation()
}
}
위치 가져온 후 내 위치 트래킹
- 정상적으로 트래킹 후 Presenter로 한국 주소를 얻기 위해 좌표를 보냄
fun updateMyLocation() {
locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
val criteria = Criteria()
// 정확도
criteria.setAccuracy(Criteria.NO_REQUIREMENT);
// 전원 소비량
criteria.setPowerRequirement(Criteria.NO_REQUIREMENT);
// 고도, 높이 값을 얻어 올지를 결정
criteria.setAltitudeRequired(false);
// provider 기본 정보(방위, 방향)
criteria.setBearingRequired(false);
// 속도
criteria.setSpeedRequired(false);
// 위치 정보를 얻어 오는데 들어가는 금전적 비용
criteria.setCostAllowed(true);
provider = locationManager.getBestProvider(criteria, false);
val location = locationManager.getLastKnownLocation(provider);
if(location != null) {
Log.d("KTH","${location.latitude},${location.longitude}")
mPresenter.getAddr(lng = location.longitude, lat = location.latitude)
}
}
Presenter 구현
- 좌표를
repository
에 다시 보내서Observable<Address>
형태로 리턴 받음 compositeDisposable
는 메모리릭을 위해 구독된disposable
을detach
때 제거를 해준다.
override fun getAddr(lng: Double, lat: Double) {
val disposable = repository.convertAddr(lng, lat)
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
onNext = {
mView?.updateAddress(it.items[0].addressDetail.dongmyun)
},
onError = {
it.printStackTrace()
}
)
compositeDisposable.add(disposable)
}
Adress 데이터 클래스 구현
- 서버로 부터 결과값을 가져온 후
Address
데이터로 변환하기 위해 필요하다.
import com.squareup.moshi.Json
data class ResponseAddress(
@field:Json(name = "result") val result : Address
)
data class Address (
@field:Json(name = "total") val total : Int,
@field:Json(name = "items") val items : List<AddressItem>
)
data class AddressItem (
@field:Json(name = "address") val address : String,
@field:Json(name = "addrdetail") val addressDetail : AddressDetail
)
data class AddressDetail (
@field:Json(name = "country") val country : String,
@field:Json(name = "sido") val sido : String,
@field:Json(name = "sigugun") val sigugun : String,
@field:Json(name = "dongmyun") val dongmyun : String,
@field:Json(name = "rest") val rest : String
)
Source 클래스 구현
- Repository 에서 구현될 모델 클래스들의 공통적인 함수 모음
interface Source {
fun getConvertedAddr(lat : Double, lng : Double ) : Observable<Address>
}
- FirebaseRepository by Source
class FirebaseRepository : Source{
val firestoreApp by lazy {
FirebaseFirestore.getInstance()
}
override fun getConvertedAddr(lat: Double, lng: Double) : Observable<Address> {
TODO("not implemented") //To change body of created functions use File | Settings | File
}
}
- NetworkRepository by Source
object NetworkRepsitory : Source {
override fun getConvertedAddr(lng: Double, lat: Double): Observable<Address> {
//129.075090,35.179632
return ApiManager.getAddressFromLatLng(lng, lat)
}
}
- RepositoryImpl 를 통해서 Presenter와 통신한다.
- NetworkRepository 에서 주소를 변환하도록 지시
class RepositoryImpl {
fun convertAddr(lng: Double, lat: Double): Observable<Address> = NetworkRepsitory.getConvertedAddr(lng, lat)
}
Retrofit API Manager
- Retrofit Builder 구현
object BaseApiManager {
fun initRetrofit(Server : String): Retrofit {
val interceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
val client = OkHttpClient.Builder().apply {
networkInterceptors().add(Interceptor { chain ->
val original = chain.request()
val request = original.newBuilder()
.method(original.method(), original.body())
.build()
chain.proceed(request)
})
addInterceptor(interceptor)
}
return Retrofit.Builder().baseUrl(Server)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(BaseApiManager.createMoshiConverter())
.client(client.build())
.build()
}
private fun createMoshiConverter(): MoshiConverterFactory = MoshiConverterFactory.create()
중요한 부분
RxJava2CallAdapterFactory
를 통해서 컨버팅 된 결과물을Observable
를 스트림으로 바꿔준다.BaseApiManager.createMoshiConverter()
을 통해 GSON 컨버팅을 진행한다.
return Retrofit.Builder().baseUrl(Server) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(BaseApiManager.createMoshiConverter()) .client(client.build()) .build()
네이버 지코코딩을 통해 좌표 -> 주소로 변환
- subscribeOn 은 RxJava 구독 시작시 쓰레드를 결정시킨다.
- Network 를 사용시 io 쓰레드 이용해서 통신
- 결과물은
Observable<Address>
으로 리턴된다.
object ApiManager {
private const val BASE_SERVER: String = "https://openapi.naver.com/v1/map/"
var retrofit: Retrofit
init {
retrofit = BaseApiManager.initRetrofit(BASE_SERVER)
}
fun getAddressFromLatLng(lng: Double, lat: Double) : Observable<Address> {
val addressService = retrofit.create(AddressService::class.java)
return addressService.getAddress(query = "$lat,$lng")
.map{ it.result } //ResponseAddress -> Address
.subscribeOn(Schedulers.io())
}
}
API 인터페이스를 통해서 통신 처리
- ResponseAddress -> Address -> AddressItem -> AddressDetail 으로 오브젝트들을 변환시킨다.
interface AddressService {
/*
위도 : latitude
경도 : longtitude
"https://openapi.naver.com/v1/map/reversegeocode?encoding=utf-8&coordType=latlng&query=";
*/
@Headers(
"X-Naver-Client-Id:네이버API",
"X-Naver-Client-Secret:네이버API"
)
@GET("reversegeocode?encoding=utf-8&coordType=latlng")
fun getAddress(@Query("query") query : String) : Observable<ResponseAddress>
}
Presenter로 통해서 결과값을 UI 로 처리
observeOn(AndroidSchedulers.mainThread())
을 통해서 해당 밑으로는Android UI Thread
로 처리mView
를 통해서updateAddress
해줌으로써 데이터를 가져와서 UI 로 올려보내는 작업은 끝났다.
override fun getAddr(lng: Double, lat: Double) {
val disposable = repository.convertAddr(lng, lat)
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
onNext = {
mView?.updateAddress(it.items[0].addressDetail.dongmyun)
},
onError = {
it.printStackTrace()
}
)
compositeDisposable.add(disposable)
}
RecyclerViewAdapter
이벤트 리스너 구현
- Adapter에 리스너 붙이는 작업
- Kotlin 확장함수를 통해서
ViewHolder
클래스를 이용해서listen
함수 구현
fun <T : RecyclerView.ViewHolder> T.listen(event: (position: Int, type: Int) -> Unit): T {
itemView.setOnClickListener {
event.invoke(adapterPosition, itemViewType)
}
return this
}
- Adapter 부분
- clickListener는 MainActivity에서 콜백 구현
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainRecylerViewAdapter.ViewHolder {
val view = LayoutInflater.from(context).inflate(R.layout.main_recyclerview_item, parent, false)
return ViewHolder(view).listen{ pos, type ->
clickListener(items[pos])
}
}
- MainActivity
- MainActivity 에서 아이템 클릭시 리스트 화면으로 넘어가기 위해 필요
...
mainRecyclerView.adapter = MainRecylerViewAdapter(items, getContext(), ::handleItem)
...
fun handleItem(item: Category) {
startActivity(
Intent(this, ListActivity::class.java).apply {
this.putExtra("item", item)
}
)
}
이상으로 2주차 부산 배달앱 클론앱 스터디 내용이었습니다.
다음주에는 3주차로 리스트 화면 상세 구현 및 등록 등을 진행할 예정입니다.
'스터디 > Android' 카테고리의 다른 글
Android 스크린샷 adb를 통해서 가져오기 (0) | 2018.10.31 |
---|---|
[Android] 배달앱 클론 3주차 수업 정리 (0) | 2018.10.21 |
Android Webview FileUpload os별 설정 (0) | 2018.10.10 |
[Android] 배달앱 클론 스터디 1주차 (0) | 2018.10.10 |