基于Firebase的Android投票应用开发技术求助
嘿,我来帮你搞定这个投票APP的核心功能!咱们一步步解决一人一票、计数累加和实时更新百分比的问题:
1. 先调整Firebase数据库结构
你目前的数据库设计只是存了"+"符号,完全没法统计票数。咱们改成一个结构化的节点,比如命名为event_vote,里面包含三个子节点:
yesCount: 初始值0(存储YES的总票数)noCount: 初始值0(存储NO的总票数)votedUsers: 空对象(用来记录已投票的用户ID,防止重复投票)
你可以直接在Firebase控制台手动创建这个结构,也可以通过代码初始化。
2. 实现一人一票机制
新手友好的方案是用Firebase匿名登录——不需要用户注册,就能获得唯一的用户ID,避免同一用户重复投票。
首先在你的Location Activity里初始化Firebase Auth:
private FirebaseAuth mAuth; private String currentUserId; private Button btn_yes, btn_no; private TextView comY, comN; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.fragment_blank); // 初始化控件 btn_yes = findViewById(R.id.btnYes); btn_no = findViewById(R.id.btnNo); comY = findViewById(R.id.com_yes); comN = findViewById(R.id.com_no); // 初始化Firebase Auth,执行匿名登录 mAuth = FirebaseAuth.getInstance(); mAuth.signInAnonymously().addOnCompleteListener(this, task -> { if (task.isSuccessful()) { // 获取当前用户的唯一ID currentUserId = mAuth.getCurrentUser().getUid(); // 初始化投票监听和按钮状态检查 initVoteRealTimeListener(); checkIfUserVoted(); } else { Toast.makeText(Location.this, "登录失败:" + task.getException().getMessage(), Toast.LENGTH_SHORT).show(); } }); }
3. 投票计数累加(用原子操作避免并发问题)
直接用setValue会覆盖原有数据,必须用Firebase的runTransaction来保证票数累加的原子性——防止多个用户同时投票导致计数错误。
YES按钮点击逻辑:
btn_yes.setOnClickListener(v -> { DatabaseReference voteRef = FirebaseDatabase.getInstance().getReference("event_vote"); // 先检查用户是否已投票 voteRef.child("votedUsers").child(currentUserId).addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot snapshot) { if (!snapshot.exists()) { // 执行票数累加事务 voteRef.child("yesCount").runTransaction((mutableData) -> { Long count = mutableData.getValue(Long.class); mutableData.setValue(count == null ? 1 : count + 1); return Transaction.success(mutableData); }, (error, committed, currentData) -> { if (committed) { // 记录用户已投票 voteRef.child("votedUsers").child(currentUserId).setValue("yes"); // 禁用按钮防止重复点击 btn_yes.setEnabled(false); btn_no.setEnabled(false); Toast.makeText(Location.this, "投票成功!", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(Location.this, "投票失败,请重试", Toast.LENGTH_SHORT).show(); } }); } else { Toast.makeText(Location.this, "你已经投过票啦!", Toast.LENGTH_SHORT).show(); } } @Override public void onCancelled(@NonNull DatabaseError error) { Toast.makeText(Location.this, error.getMessage(), Toast.LENGTH_SHORT).show(); } }); });
NO按钮点击逻辑(和YES几乎一致,只需要修改对应的节点):
btn_no.setOnClickListener(v -> { DatabaseReference voteRef = FirebaseDatabase.getInstance().getReference("event_vote"); voteRef.child("votedUsers").child(currentUserId).addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot snapshot) { if (!snapshot.exists()) { voteRef.child("noCount").runTransaction((mutableData) -> { Long count = mutableData.getValue(Long.class); mutableData.setValue(count == null ? 1 : count + 1); return Transaction.success(mutableData); }, (error, committed, currentData) -> { if (committed) { voteRef.child("votedUsers").child(currentUserId).setValue("no"); btn_yes.setEnabled(false); btn_no.setEnabled(false); Toast.makeText(Location.this, "投票成功!", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(Location.this, "投票失败,请重试", Toast.LENGTH_SHORT).show(); } }); } else { Toast.makeText(Location.this, "你已经投过票啦!", Toast.LENGTH_SHORT).show(); } } @Override public void onCancelled(@NonNull DatabaseError error) { Toast.makeText(Location.this, error.getMessage(), Toast.LENGTH_SHORT).show(); } }); });
4. 实时更新投票占比
监听event_vote节点的变化,实时计算百分比并更新TextView:
private void initVoteRealTimeListener() { DatabaseReference voteRef = FirebaseDatabase.getInstance().getReference("event_vote"); voteRef.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot snapshot) { Long yesCount = snapshot.child("yesCount").getValue(Long.class); Long noCount = snapshot.child("noCount").getValue(Long.class); // 处理初始值为null的情况 long yes = yesCount == null ? 0 : yesCount; long no = noCount == null ? 0 : noCount; long total = yes + no; // 计算百分比,避免除以0的情况 double yesPercent = total == 0 ? 0 : (yes * 100.0) / total; double noPercent = total == 0 ? 0 : (no * 100.0) / total; // 更新TextView,保留1位小数 comY.setText(String.format("%.1f %%", yesPercent)); comN.setText(String.format("%.1f %%", noPercent)); } @Override public void onCancelled(@NonNull DatabaseError error) { Toast.makeText(Location.this, "监听投票数据失败:" + error.getMessage(), Toast.LENGTH_SHORT).show(); } }); }
5. 初始化时检查用户是否已投票
防止用户重新打开APP后可以再次投票:
private void checkIfUserVoted() { DatabaseReference votedRef = FirebaseDatabase.getInstance().getReference("event_vote") .child("votedUsers").child(currentUserId); votedRef.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot snapshot) { if (snapshot.exists()) { btn_yes.setEnabled(false); btn_no.setEnabled(false); Toast.makeText(Location.this, "你已经投过票啦!", Toast.LENGTH_SHORT).show(); } else { btn_yes.setEnabled(true); btn_no.setEnabled(true); } } @Override public void onCancelled(@NonNull DatabaseError error) { Toast.makeText(Location.this, error.getMessage(), Toast.LENGTH_SHORT).show(); } }); }
6. 修正Firebase数据库规则
原来的规则太开放,任何人都能修改所有数据,咱们改成更安全的规则:
{ "rules": { "event_vote": { "yesCount": { ".read": true, ".write": "auth != null" // 只有登录用户能修改票数 }, "noCount": { ".read": true, ".write": "auth != null" }, "votedUsers": { "$uid": { ".read": true, ".write": "$uid === auth.uid" // 每个用户只能修改自己的投票记录 } } } } }
内容的提问来源于stack exchange,提问作者Hansi
相关产品推荐
相关产品推荐

