Kotlin结合io fotoapparat库实现拍照后更新Firebase存储与Firestore
问题修复方案
核心问题梳理
- Fotoapparat拍照后未配置结果回调,无法获取拍摄得到的位图数据
- 现有
onActivityResult是系统相机的回调逻辑,和Fotoapparat库的调用逻辑完全不兼容,不会被触发 - 创建收据文档得到的
docId没有持久化存储,上传照片时找不到需要更新的目标文档 - 调用的上传方法名和仓库层实际定义的方法名不匹配,上传逻辑根本没有执行
- 拍照按钮未绑定点击事件,无法触发拍照流程
具体修复步骤
1. 持久化存储当前收据ID
在ScanViewModel中新增变量存储最新创建的收据文档ID:
// ScanViewModel.kt val currentReceiptId = MutableStateFlow<String?>(null)
修改新增Firestore文档的逻辑,创建成功后保存ID:
binding.button.setOnClickListener { val biedronka = "biedronka" val price = "20" val img = " " val identificator = " " val from = biedronka val value = price val image = img val idd = identificator val db = Firebase.firestore val data = hashMapOf( "from" to from, "value" to value, "image" to image, "id" to idd ) db.collection("receipts") .add(data) .addOnSuccessListener { documentReference -> Log.d(SCAN_DEBUG, "DocumentSnapshot written with ID: ${documentReference.id}") // 保存收据ID到ViewModel scanVm.currentReceiptId.value = documentReference.id db.collection("receipts") .document(documentReference.id) .update("id", documentReference.id) .addOnSuccessListener { } } .addOnFailureListener { e -> Log.w(SCAN_DEBUG, "Error adding document", e) } }
2. 修正Fotoapparat拍照逻辑,添加结果回调
修改takePhoto方法,拿到位图后直接调用上传逻辑,同时给拍照按钮绑定点击事件:
override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, ): View { _binding = FragmentScanBinding.inflate(inflater, container, false) createFotoapparat() cameraStatus = CameraState.BACK flashState = FlashState.OFF fotoapparatState = FotoapparatState.OFF binding.fabSwitchCamera.setOnClickListener { switchCamera() } binding.fabFlash.setOnClickListener { changeFlashState() } // 新增:拍照按钮绑定点击事件 binding.fabTakePhoto.setOnClickListener { takePhoto() } return binding.root } private fun takePhoto() { if (hasNoPermissions()) { requestPermission() }else{ fotoapparat ?.takePicture() ?.toBitmap() // 新增:拍照结果回调 ?.whenAvailable { photoResult -> photoResult?.bitmap?.let { bitmap -> val stream = ByteArrayOutputStream() val compressSuccess = bitmap.compress(Bitmap.CompressFormat.JPEG, 90, stream) val byteArray = stream.toByteArray() if (compressSuccess) { val receiptId = scanVm.currentReceiptId.value ?: return@let // 调用仓库层上传方法 scanVm.uploadReceiptPhoto(byteArray, receiptId) } } } } }
3. 修正仓库层上传逻辑,传入动态docId
将全局docId改为参数传递,避免空值问题:
fun uploadReceiptPhoto(bytes: ByteArray, docId: String) { storage.getReference("receipts") .child("${docId}.jpg") .putBytes(bytes) .addOnCompleteListener{ Log.d(REPO_DEBUG, "COMPLETE UPLOAD PHOTO") } .addOnSuccessListener { getReceiptPhotoDownloadUrl(it.storage, docId) } .addOnFailureListener { Log.d(REPO_DEBUG, it.message.toString()) } } private fun getReceiptPhotoDownloadUrl(storage: StorageReference, docId: String) { storage.downloadUrl .addOnSuccessListener { updateReceiptPhoto(it.toString(), docId) } .addOnFailureListener { Log.d(REPO_DEBUG, it.message.toString()) } } private fun updateReceiptPhoto(url: String, docId: String) { cloud.collection("receipts") .document(docId) .update("image", url) .addOnSuccessListener { Log.d(REPO_DEBUG, "UPDATE RECEIPT PHOTO SUCCESS") } .addOnFailureListener { Log.d(REPO_DEBUG, it.message.toString()) } }
4. 删除无效代码
原有onActivityResult方法是系统相机的回调逻辑,和Fotoapparat完全无关,可以直接删除。
5. 检查Firebase权限
确保Firebase Storage和Firestore的安全规则允许当前用户读写,测试阶段可临时使用以下规则:
Storage规则
rules_version = '2'; service firebase.storage { match /b/{bucket}/o { match /{allPaths=**} { allow read, write: if request.auth != null; } } }
Firestore规则
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /{allPaths=**} { allow read, write: if request.auth != null; } } }
功能验证完成后请修改为更严格的权限规则,避免数据泄露。
内容的提问来源于stack exchange,提问作者MaksChojniak1
相关产品推荐
相关产品推荐

