Android/Side Projects

당근마켓 앱 개발하기

화요밍 2022. 2. 21. 21:51
728x90
반응형
Side Proeject - Carrot Market
중고 거래 앱 당근 마켓의 간단한 기능을 구현한 프로젝트

 

 

GitHub - hwayeon351/CarrotMarket

Contribute to hwayeon351/CarrotMarket development by creating an account on GitHub.

github.com


학습 회고

오늘은 AddArticleActivity에서 새 아이템 등록을 하면 Firebase Realtime Database에 데이터를 전송하는 로직을 구현하였다.


오늘 공부한 내용

  • 사진 가져오기

이미지 등록하기 버튼을 클릭하면 사진 앱에서 사진을 선택할 수 있는 기능을 구현하였다.

 

1. 권한 체크하기

ContextCompat의 checkSelfPermission()을 호출해서 현재 READ_EXTERNAL_STORAGE 권한이 앱에 부여되었는지 아닌지를 확인한다. 이전에 이미 권한이 부여되었다면 Content Provider를 통해 사진 앱에 접근하여 사진을 가져온다.

 

2. 권한이 부여되지 않았다면 교육용 UI 띄우기

사용자가 이전에 권한을 부여하지 않았다면 왜 이 앱에서 권한이 필요한지에 대해 설명하는 교육용 UI를 띄워주어 권한을 얻을 수 있도록 해야한다.

shouldShowRequestPermissionRationale을 통해서 교육용 UI를 띄워야하는지의 여부를 체킹하고 필요하다면 교육용 UI를 띄운다.

private fun showPermissionContextPopup() {
    AlertDialog.Builder(this)
        .setTitle("권한이 필요합니다.")
        .setMessage("사진을 가져오기 위해 필요합니다.")
        .setPositiveButton("동의", {_, _ ->
            requestPermissions(arrayOf(android.Manifest.permission.READ_EXTERNAL_STORAGE), 1010)
        })
        .create()
        .show()
}

 

3. 권한이 요청하기

requestPermissions()를 호출해서 READ_EXTERNAL_STORAGE 권한을 사용자에게 요청한다.

findViewById<Button>(R.id.imageAddButton).setOnClickListener {
    when {
        ContextCompat.checkSelfPermission(
            this,
            android.Manifest.permission.READ_EXTERNAL_STORAGE
        ) == PackageManager.PERMISSION_GRANTED -> {
            startContentProvider()
        }
        shouldShowRequestPermissionRationale(android.Manifest.permission.READ_EXTERNAL_STORAGE) -> {
            showPermissionContextPopup()
        }

        else -> {
            requestPermissions(arrayOf(android.Manifest.permission.READ_EXTERNAL_STORAGE), 1010)
        }
    }
}

 

Content Provider를 통해 사진을 불러오기 위해서 암묵적 인텐트를 생성한다.

ACTION_GET_CONTENT 인텐트 필터를 인텐트에 씌우고 이미지 타입의 데이터만 가져오기 위해 타입을 설정한다.

선택된 사진 데이터를 결과로 가져오기 위해 startActivityForResult() 함수를 호출한다.

이때, onActivityResult() 함수에서 어떤 요청에 대한 결과인지를 구별하기 위해 RequestCode를 2020으로 설정해주었다.

private fun startContentProvider() {
    val intent = Intent(Intent.ACTION_GET_CONTENT)
    intent.type = "image/*"
    startActivityForResult(intent, 2020)
}

 

사용자가 사진을 정상적으로 선택한다면 Activity에 requestCode로 RESULT_OK가 넘어온다.

requestCode가 startContentProvider()에서 설정한 2020이라면 사진 데이터가 data에 담긴다.

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)

    if (resultCode != Activity.RESULT_OK) return

    when (requestCode) {
        2020 -> {
            val uri = data?.data
            if(uri != null) {
                findViewById<ImageView>(R.id.photoImageView).setImageURI(uri)
                selectedUri = uri
            } else {
                Toast.makeText(this, "사진을 가져오지 못했습니다.", Toast.LENGTH_SHORT).show()
            }
        }
        else -> {
            Toast.makeText(this, "사진을 가져오지 못했습니다.", Toast.LENGTH_SHORT).show()
        }
    }
}

 

 

 

  • Firebase Realtime Database에 데이터 전송하기

등록하기 버튼을 클릭하면 새 아이템을 최종적으로 등록하고 해당 데이터를 Firebase Realtime Database로 전송하고, 선택한 이미지가 존재한다면 Firebase Storage로 전송한다.

findViewById<Button>(R.id.submitButton).setOnClickListener {
    val title = findViewById<EditText>(R.id.titleEditText).text.toString()
    val price = findViewById<EditText>(R.id.priceEditText).text.toString()
    val sellerId = auth.currentUser?.uid.orEmpty()

    if (title.isEmpty() || price.isEmpty()) {
        Toast.makeText(this, "제목 및 가격 정보를 입력해주세요.", Toast.LENGTH_SHORT).show()
        return@setOnClickListener
    }

    showProgress()

    if (selectedUri != null) {
        val photoUri = selectedUri ?: return@setOnClickListener
        uploadPhoto(photoUri,
            successHandler = { uri ->
                uploadArticle(sellerId, title, price, uri)
            },
            errorHandler = {
                Toast.makeText(this, "사진 업로드에 실패했습니다.", Toast.LENGTH_SHORT).show()
                hideProgress()
            }
        )
    } else {
        uploadArticle(sellerId, title, price, "")
    }

}

 

FirebaseStorage의 레퍼런스에 파일 경로를 만들어주고 putFile() 함수를 호출해서 파일을 저장시킨다.

저장된 이후에는 FirebaseStorage의 레퍼런스에 파일 경로와 파일 이름을 통해 해당 파일이 존재하는 위치를 가리키고 downloadUrl로  받아올 수 있다.

private fun uploadPhoto(uri: Uri, successHandler: (String) -> Unit, errorHandler: () -> Unit) {
    val fileName = "${System.currentTimeMillis()}.png"
    storage.reference.child("article/photo").child(fileName)
        .putFile(uri)
        .addOnCompleteListener {
            if (it.isSuccessful) {
                storage.reference.child("article/photo").child(fileName).downloadUrl
                    .addOnSuccessListener { uri ->
                        successHandler(uri.toString())
                    }.addOnFailureListener {
                        errorHandler()
                    }
            } else {
                errorHandler()
            }
        }

}

 

최종적으로 Firebase Realtime Database에 새로 추가된 아이템을 데이터베이스 레퍼런스의 적절한 위치에 push() 하여 전송한다.

private fun uploadArticle(sellerId: String, title: String, price: String, imageUrl: String) {
    val model = ArticleModel(sellerId, title, System.currentTimeMillis(), price, imageUrl)
    articleDB.push().setValue(model)
    hideProgress()
    Toast.makeText(this, "아이템이 등록되었습니다.", Toast.LENGTH_SHORT).show()
    finish()
}

 

 

 

728x90
반응형

'Android > Side Projects' 카테고리의 다른 글

Airbnb 앱 개발하기  (0) 2022.02.23
당근마켓 앱 개발하기  (0) 2022.02.22
당근마켓 앱 개발하기  (0) 2022.02.20
당근마켓 앱 개발하기  (1) 2022.02.19
Tinder 앱 개발하기  (0) 2022.02.18