# 결과물 미리보기
파이어베이스에 data를 업로드하는 기능과, 이미지에 접근해서 파일을 가져오는 것까지 구현했다. 아직은 auth를 통한 로그인 단계와, firestore을 사용해서 이미지를 저장시키는 기능은 구현되지 않았다.
저번시간에 생겼던 비동기처리 문제로 인해서 리스너가 사실상 실시간으로 들어오지 않는다. 이건 프로젝트를 다 만들고나서 확실하게 처리해봐야 할 것 같다. 안된다면 코루틴이라도 해서 이번 프로젝트에 완성 시켜야 할 것 같다. 학교에서 알파프로젝트를 하는데 결국 거기서도 이거랑 비슷한 문제가 발생 했기 때문이다..
# 구현 순서
- 물품 추가 xml 레이아웃 만들기
- 물품 추가 액티비티 만들기
- 갤러리 접근 권한 구현하기
- 갤러리에서 파일 가져오기
- 파이어베이스에 데이터 업로드하기
# 물품 추가 xml 레이아웃 만들기
물품 등록을 위한 액티비티를 구성한다. 해당 액티비티로 넘어가기 위해서는 홈프래그먼트에 버튼을 하나 추가해줘야하는데, 리사이클러뷰는 스크롤 되기 때문에 항상 같은곳에 위치할 수 있는 FloatingActionButton을 추가해준다.
app:tint="@color/white" // '+'아이콘 색 지정
android:src="@drawable/ic_baseline_add_24" // '+' 벡터 이미지
android:backgroundTint="@color/carrot_color" // 배경색
# 갤러리 접근 권한 구현하기
꽤 오래만에 접근권한을 다시 사용했다. 블로그 정리해놓은게 정말 빛을 발휘하는 순간이었다. 까먹은 개념이 많았는데, 내가 정리한 글을 다시보면서 쉽게 이해 할 수 있었다.
먼저 이미지 추가 버튼을 누르면 접근 권한을 체크하는 분기점을 거친다.
private fun initAddImageButton() {
addImageButton.setOnClickListener {
Log.d(TAG,"addImageButton called!!")
when {
ContextCompat.checkSelfPermission(this,
Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
-> {
// 권한이 존재하는 경우
// TODO 이미지를 가져옴
getImageFromAlbum()
}
shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE) -> {
// 권한이 거부 되어 있는 경우
showPermissionContextPopup()
}
else -> {
// 처음 권한을 시도했을 때 띄움
requestPermissions(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),REQUEST_FIRST)
}
}
}
}
각각의 분기점을 만들어주고, 권한이 거부되어 있는 경우에는 다이얼로그는 간단하게 커스텀하여, requestPermissions(...) 을 다시 묻도록 한다. 권한을 요청했을때, 권한이 맞다면 앨범에 접근하게 될 것이다.
# 앨범에서 이미지 Uri 가져오기
intent를 통해서 이미지 파일들을 가져올 수 있도록 한다. 그리고 startActivityForResult를 통해 intent를 열어주고, 그 결과값에 따라 처리할 수 있도록 override 함수를 만들어 준다.
private fun getImageFromAlbum() {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "image/*"
startActivityForResult(intent,REQUEST_GET_IMAGE)
}
rueqstCode가 올바르게 왔다면, uri를 정상적으로 가져온 것이기 때문에, imageView를 해당 uir로 set해준다. 그러고 나서 전역에 uri값을 등록해주고, 나중에도 이 값을 사용 할 수 있도록 만들어 준다.
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if(resultCode != Activity.RESULT_OK) {
Toast.makeText(this,"잘못된 접근입니다",Toast.LENGTH_SHORT).show()
return
}
when(requestCode){
REQUEST_GET_IMAGE -> {
val selectedImageURI : Uri? = data?.data
if( selectedImageURI != null ) {
val imageView = findViewById<ImageView>(R.id.addImageView)
imageView.setImageURI(selectedImageURI)
imageURI = selectedImageURI
}else {
Toast.makeText(this,"이미지를 가져오지 못했습니다1",Toast.LENGTH_SHORT).show()
}
}
else -> {
Toast.makeText(this,"이미지를 가져오지 못했습니다2",Toast.LENGTH_SHORT).show()
}
}
}
# 파이어 베이스에 업로드하기
private fun initSubmitItemButton() {
submitButton.setOnClickListener {
val titleEditText = findViewById<EditText>(R.id.addTitleEditText).text.toString()
val priceEditText = findViewById<EditText>(R.id.addPrcieEditText).text.toString()
val uid = auth.uid.orEmpty()
if( titleEditText.isNotEmpty() && priceEditText.isNotEmpty()){
val articleDB = db.child(DB_ARTICLE)
val articleModel = ArticleModel(uid,titleEditText,System.currentTimeMillis(),"${priceEditText}원","")
articleDB.push().setValue(articleModel)
finish()
}
}
}
여러번 했기 때문에 꽤 익숙해 졌다. articleDB.push().setValue(articleModel)는 아직은 로그인 uid값이 없기때문에 push를 통해 임의의 key값을 만들어 주고 그 안에다가 모델을 넣어주도록 한다.
즉 파란색으로 선그어놓은 부분이 push로 생긴 무작위 key값이 된다.
# 코드 스타일링
코드에 오타를 줄이고 가독성을 높이기 위해 companion object를 적극 활용한다.
companion object {
const val REQUEST_FIRST = 1000
const val REQUEST_GET_IMAGE = 2000
}
또 여러 코틀린 파일에서 쓰일 거 같으면 class로 분리하여 사용한다.
// DBkey.kt
class DBkey {
companion object {
const val DB_ARTICLE = "Article"
}
}
# 버그버그
미리보기에서 설명했듯이 동기처리 방식에 있어서 버그가 발생한다. 파이어베이스에서 값을 가져오는 로직과, 어댑터에 보내줄 list을 clear해주는 과정이 비동기 과정에서 엇갈려 버리기 때문에 발생하는 버그이다. 버그의 원인은 확실히 알 수 있으니, 조금만 시간을 보탠다면 확실히 해결할 수 있을 것 같다.
# 마치며
블로그로 정리하는게 시간을 두배 세배로 쓰게 되지만, 이해하는데도 좋고, 나만의 아카이브가 생기는 것 같다는 걸 오늘 조금 느꼈다!