Firestore中Campaign编辑删除功能异常:文档存在却提示无文档
问题
开发Android应用时,使用CreateCampaignActivity创建Campaign并存储到Firestore后,在EditCampaignActivity中执行编辑和删除操作均失败。执行编辑操作时,Firestore提示不存在要更新的文档,但该文档实际存在于Firestore数据库中。
相关代码
1. 创建Campaign的Activity代码
class CreateCampaignActivity : AppCompatActivity() { private lateinit var startDateTimeTextView: TextView private lateinit var endDateTimeTextView: TextView private lateinit var titleEditText: TextInputEditText private lateinit var db: FirebaseFirestore private lateinit var candidate1Text: TextInputEditText private lateinit var candidate2Text: TextInputEditText private lateinit var candidate3Text: TextInputEditText private lateinit var addCampaignButton : Button override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_create_campaign) supportActionBar!!.setDisplayHomeAsUpEnabled(true) supportActionBar!!.title = "Create New Campaign" titleEditText = findViewById(R.id.textTitle) startDateTimeTextView = findViewById(R.id.start_date_time) endDateTimeTextView = findViewById(R.id.end_date_time) addCampaignButton = findViewById(R.id.btnAddCampaign) candidate1Text = findViewById(R.id.textCandidate1) candidate2Text = findViewById(R.id.textCandidate2) candidate3Text = findViewById(R.id.textCandidate3) db = FirebaseFirestore.getInstance() addCampaignButton.setOnClickListener { val title = titleEditText.text.toString().trim() val startDateTime = startDateTimeTextView.text.toString().trim() val endDateTime = endDateTimeTextView.text.toString().trim() val candidate1Name = candidate1Text.text.toString().trim() val candidate2Name = candidate2Text.text.toString().trim() val candidate3Name = candidate3Text.text.toString().trim() if (title.isEmpty() || startDateTime.isEmpty() || endDateTime.isEmpty() || candidate1Name.isEmpty() || candidate2Name.isEmpty() || candidate3Name.isEmpty()) { Toast.makeText(this@CreateCampaignActivity, "Please fill up empty fields", Toast.LENGTH_SHORT).show() } else if (!isEndTimeValid(startDateTime, endDateTime)) { Toast.makeText(this@CreateCampaignActivity, "The end time must be after start time", Toast.LENGTH_SHORT).show() } else { val format = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US) val startTimestamp = Timestamp(format.parse(startDateTime)!!) val endTimestamp = Timestamp(format.parse(endDateTime)!!) val campaign = Campaign( id = UUID.randomUUID().toString(), title = title, startDateTime = startTimestamp, endDateTime = endTimestamp, candidate1 = Candidate( id = UUID.randomUUID().toString(), name = candidate1Name ), candidate2 = Candidate( id = UUID.randomUUID().toString(), name = candidate2Name ), candidate3 = Candidate(id = UUID.randomUUID().toString(), name = candidate3Name) ) db.collection("campaigns") .add(campaign) .addOnSuccessListener { documentReference -> campaign.id = documentReference.id Log.d( TAG, "Campaign successfully added to Firestore with ID: ${documentReference.id}" ) Toast.makeText(this@CreateCampaignActivity, "Campaign successfully added", Toast.LENGTH_SHORT).show() titleEditText.setText("") startDateTimeTextView.text = "" endDateTimeTextView.text = "" candidate1Text.setText("") candidate2Text.setText("") candidate3Text.setText("") finish() } .addOnFailureListener { e -> Log.w(TAG, "Error adding campaign to Firestore", e) Toast.makeText(this@CreateCampaignActivity, "Error adding campaign", Toast.LENGTH_SHORT).show() } } startDateTimeTextView.setOnClickListener { showStartDateTimeDialog() } endDateTimeTextView.setOnClickListener { showEndDateTimeDialog() } } } private fun showStartDateTimeDialog() { val calendar = Calendar.getInstance() val dateSetListener = DatePickerDialog.OnDateSetListener { _, year, month, day -> calendar.set(Calendar.YEAR, year) calendar.set(Calendar.MONTH, month) calendar.set(Calendar.DAY_OF_MONTH, day) val timeSetListener = TimePickerDialog.OnTimeSetListener { _, hour, minute -> calendar.set(Calendar.HOUR_OF_DAY, hour) calendar.set(Calendar.MINUTE, minute) startDateTimeTextView.text = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()).format(calendar.time) } TimePickerDialog( this@CreateCampaignActivity, timeSetListener, calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), true ).show() } DatePickerDialog( this@CreateCampaignActivity, dateSetListener, calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH) ).show() } private fun showEndDateTimeDialog() { val calendar = Calendar.getInstance() val dateSetListener = DatePickerDialog.OnDateSetListener { _, year, month, day -> calendar.set(Calendar.YEAR, year) calendar.set(Calendar.MONTH, month) calendar.set(Calendar.DAY_OF_MONTH, day) val timeSetListener = TimePickerDialog.OnTimeSetListener { _, hour, minute -> calendar.set(Calendar.HOUR_OF_DAY, hour) calendar.set(Calendar.MINUTE, minute) endDateTimeTextView.text = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()).format(calendar.time) } TimePickerDialog( this@CreateCampaignActivity, timeSetListener, calendar.get(Calendar.HOUR_OF_DAY),calendar.get(Calendar.MINUTE), true ).show() } DatePickerDialog( this@CreateCampaignActivity, dateSetListener, calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH) ).show() } private fun isEndTimeValid(startDateTime: String, endDateTime: String): Boolean { val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()) val startDate = sdf.parse(startDateTime) val endDate = sdf.parse(endDateTime) return endDate?.after(startDate) ?: false } companion object { private const val TAG = "CreateCampaignActivity" } }
2. 编辑Campaign的Activity代码
class EditCampaignActivity : AppCompatActivity() { private lateinit var datetimeTextView: TextView private lateinit var titleEditText: TextInputEditText private lateinit var db: FirebaseFirestore private lateinit var candidate1Text: TextInputEditText private lateinit var candidate2Text: TextInputEditText private lateinit var candidate3Text: TextInputEditText private lateinit var editCampaign : Button private lateinit var delCampaign: Button override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_edit_campaign) supportActionBar!!.setDisplayHomeAsUpEnabled(true) supportActionBar!!.title = "Edit Campaign" titleEditText = findViewById(R.id.textTitle) datetimeTextView = findViewById(R.id.DateTime) delCampaign = findViewById(R.id.btndelCampaign) editCampaign = findViewById(R.id.btnSaveCampaign) db = FirebaseFirestore.getInstance() candidate1Text = findViewById(R.id.textCandidate1) candidate2Text = findViewById(R.id.textCandidate2) candidate3Text = findViewById(R.id.textCandidate3) intent.getStringExtra("CAMPAIGN_ID") val campaign = intent.getParcelableExtra<Campaign>("CAMPAIGN_DATA") if (campaign != null) { intent.putExtra("CAMPAIGN_ID", campaign.id) } if (campaign != null) { titleEditText.setText(campaign.title) } if (campaign != null) { datetimeTextView.text = campaign.endDateTime.toDate().toString() } if (campaign != null) { candidate1Text.setText(campaign.candidate1.name) } if (campaign != null) { candidate2Text.setText(campaign.candidate2.name) } if (campaign != null) { candidate3Text.setText(campaign.candidate3.name) } //Update function editCampaign.setOnClickListener { val updatedTitle = titleEditText.text.toString() val updatedCandidate1 = candidate1Text.text.toString() val updatedCandidate2 = candidate2Text.text.toString() val updatedCandidate3 = candidate3Text.text.toString() campaign?.apply { title = updatedTitle endDateTime = Timestamp.now() candidate1.name = updatedCandidate1 candidate2.name = updatedCandidate2 candidate3.name = updatedCandidate3 } if (campaign != null) { db.collection("campaigns").document(campaign.id) .update(mapOf( "title" to updatedTitle, "endDateTime" to Timestamp.now(), "candidate1" to mapOf( "name" to updatedCandidate1 ), "candidate2" to mapOf( "name" to updatedCandidate2 ), "candidate3" to mapOf( "name" to updatedCandidate3 ) )) .addOnSuccessListener { Toast.makeText(this, "Campaign updated", Toast.LENGTH_SHORT).show() } .addOnFailureListener { Toast.makeText(this, "Error updating campaign: $it", Toast.LENGTH_SHORT).show() } } AlertDialog.Builder(this) .setTitle("Edit Campaign") .setMessage("Are you sure you want to save the changes to this campaign?") .setPositiveButton("Yes") { _, _ -> finish() } .setNegativeButton("No", null) .show() } // perform delete function delCampaign.setOnClickListener { AlertDialog.Builder(this) .setTitle("Delete Campaign") .setMessage("Are you sure you want to delete this campaign?") .setPositiveButton("Yes") { _, _ -> if (campaign != null) { deleteCampaign(campaign) } } .setNegativeButton("No", null) .show() } } private fun deleteCampaign(campaign: Campaign) { db.collection("campaigns").document(campaign.id).delete() .addOnSuccessListener { finish() } .addOnFailureListener { e -> Toast.makeText(this, "Error deleting campaign: ${e.message}", Toast.LENGTH_SHORT).show() } } }
问题原因
核心问题是Campaign的ID与Firestore文档ID不匹配:
- 创建Campaign时,先给
campaign.id赋值UUID,再调用add()方法存入Firestore。add()会自动生成文档ID,但此时文档内的id字段仍是你生成的UUID。 - 你在
addOnSuccessListener里将本地campaign.id改为Firestore生成的文档ID,但该修改仅在内存中,未同步到Firestore文档,导致Firestore中存储的Campaign对象id字段依然是UUID。 - 从Firestore查询Campaign并传入EditCampaignActivity后,
campaign.id是UUID,用这个ID调用document(campaign.id)查找文档时,实际Firestore文档的ID是自动生成的字符串,自然找不到对应文档,导致编辑、删除失败。
解决方案
有两种可行方案:
方案一:用UUID作为Firestore文档ID(推荐)
创建Campaign时直接指定文档ID为UUID,确保文档ID与Campaign的id字段一致:
// 生成UUID作为文档ID和Campaign的id val campaignId = UUID.randomUUID().toString() val campaign = Campaign( id = campaignId, title = title, startDateTime = startTimestamp, endDateTime = endTimestamp, candidate1 = Candidate( id = UUID.randomUUID().toString(), name = candidate1Name ), candidate2 = Candidate( id = UUID.randomUUID().toString(), name = candidate2Name ), candidate3 = Candidate(id = UUID.randomUUID().toString(), name = candidate3Name) ) // 用document()指定ID,再调用set()存入 db.collection("campaigns") .document(campaignId) .set(campaign) .addOnSuccessListener { Log.d(TAG, "Campaign successfully added to Firestore with ID: $campaignId") Toast.makeText(this@CreateCampaignActivity, "Campaign successfully added", Toast.LENGTH_SHORT).show() // 清空字段、返回前页等逻辑不变 titleEditText.setText("") startDateTimeTextView.text = "" endDateTimeTextView.text = "" candidate1Text.setText("") candidate2Text.setText("") candidate3Text.setText("") finish() } .addOnFailureListener { e -> Log.w(TAG, "Error adding campaign to Firestore", e) Toast.makeText(this@CreateCampaignActivity, "Error adding campaign", Toast.LENGTH_SHORT).show() }
方案二:将Firestore文档ID同步到文档的id字段
如果坚持用add()自动生成文档ID,需在添加成功后将修改后的campaign对象写回Firestore,确保文档内的id字段与文档ID一致:
db.collection("campaigns") .add(campaign) .addOnSuccessListener { documentReference -> campaign.id = documentReference.id // 新增这行,将修改后的campaign写回文档 documentReference.set(campaign) Log.d(TAG, "Campaign successfully added to Firestore with ID: ${documentReference.id}") Toast.makeText(this@CreateCampaignActivity, "Campaign successfully added", Toast.LENGTH_SHORT).show() // 清空字段、返回前页等逻辑不变 titleEditText.setText("") startDateTimeTextView.text = "" endDateTimeTextView.text = "" candidate1Text.setText("") candidate2Text.setText("") candidate3Text.setText("") finish() } .addOnFailureListener { e -> Log.w(TAG, "Error adding campaign to Firestore", e) Toast.makeText(this@CreateCampaignActivity, "Error adding campaign", Toast.LENGTH_SHORT).show() }
额外优化点
- EditCampaignActivity中,
intent.getStringExtra("CAMPAIGN_ID")和intent.putExtra("CAMPAIGN_ID", campaign.id)均为无效代码,直接删除即可,已通过CAMPAIGN_DATA传递完整的Campaign对象。 - EditCampaignActivity中编辑时直接将
endDateTime设为Timestamp.now(),若需保留修改时间的功能,建议参考CreateCampaignActivity添加日期时间选择器。
内容的提问来源于stack exchange,提问作者danielteatsy
相关产品推荐
相关产品推荐

