You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:34:22