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

Android开发中RecyclerView无法展示RoomDatabase已存数据问题求助

问题描述

Android开发过程中尝试通过RecyclerView展示RoomDatabase内myExpense表的数据,目前已确认数据已成功存入表中,但RecyclerView始终无法正常展示内容,以下为相关实现代码。


相关实现代码

1. Expense实体类

@Entity(tableName = "myExpense")
public class Expense {
    @PrimaryKey(autoGenerate = true)
    public int uid;

    @ColumnInfo(name = "Amount")
    public String amount;

    @ColumnInfo(name = "Date")
    @TypeConverters({Converters.class})
    public Date date;

    @ColumnInfo(name = "ExpenseType")
    public String expenseType;

    @ColumnInfo(name = "PaymentType")
    public String paymentType;

    public Expense(int uid, String amount, Date date, String expenseType, String paymentType) {
        this.uid = uid;
        this.amount = amount;
        this.date = date;
        this.expenseType = expenseType;
        this.paymentType = paymentType;
    }

    public int getUid() {
        return uid;
    }

    public void setUid(int uid) {
        this.uid = uid;
    }

    public String getAmount() {
        return amount;
    }

    public void setAmount(String amount) {
        this.amount = amount;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public String getExpenseType() {
        return expenseType;
    }

    public void setExpenseType(String expenseType) {
        this.expenseType = expenseType;
    }

    public String getPaymentType() {
        return paymentType;
    }

    public void setPaymentType(String paymentType) {
        this.paymentType = paymentType;
    }
}

2. ExpenseDao数据访问接口

@Dao
public interface ExpenseDao {
    @Insert
    void insertExpense(Expense expense);

    @Query("SELECT * FROM myExpense")
    List<Expense> getAllExpenses();

    @Query("SELECT SUM(Amount) From myExpense")
    float getAllExpenseSum();
}

3. ExpenseAdapter列表适配器

public class ExpenseAdapter extends RecyclerView.Adapter<ExpenseAdapter.myViewHolder> {

    List<Expense> ExpenseList;
    Context context;

    public ExpenseAdapter(Context context,List<Expense> ExpenseList) {
        this.context=context;
        this.ExpenseList=ExpenseList;
    }

    @NonNull
    @Override
    public myViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.expense_adapter, parent, false);
        return new myViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull myViewHolder holder, int position) {
        Date date = ExpenseList.get(position).getDate();
        DateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy HH:mm", Locale.UK);
        String stringDate = dateFormat.format(date);

        holder.ExpenseAmountTxt.setText(ExpenseList.get(position).getAmount());
        holder.ExpenseTypeTxt.setText(ExpenseList.get(position).getExpenseType());
        holder.PaymentTypeTxt.setText(ExpenseList.get(position).getPaymentType());
        holder.ExpenseDateTxt.setText(stringDate);

        String ExpenseType = (String) holder.ExpenseTypeTxt.getText();
        if (ExpenseType.equals("Oil")) {
            holder.ExpenseIcon.setImageResource(R.drawable.petrol_icon);
        } else if (ExpenseType.equals("Maintainance")) {
            holder.ExpenseIcon.setImageResource(R.drawable.maintenance);
        } else if (ExpenseType.equals("Food")) {
            holder.ExpenseIcon.setImageResource(R.drawable.food);
        } else if (ExpenseType.equals("Other")) {
            holder.ExpenseIcon.setImageResource(R.drawable.othersicon);
        }
    }

    @Override
    public int getItemCount() {
        return ExpenseList.size();
    }

    public class myViewHolder extends RecyclerView.ViewHolder {
        TextView ExpenseAmountTxt,ExpenseTypeTxt,PaymentTypeTxt,ExpenseDateTxt;
        ImageView ExpenseIcon;

        public myViewHolder(@NonNull View itemView) {
            super(itemView);
            ExpenseAmountTxt = itemView.findViewById(R.id.ExpensetxtViewAmount);
            ExpenseTypeTxt = itemView.findViewById(R.id.ExpenseType);
            PaymentTypeTxt = itemView.findViewById(R.id.ExpenseAmountType);
            ExpenseDateTxt = itemView.findViewById(R.id.ExpensedateText);
        }
    }
}

4. Income_Expense Fragment页面

public class Income_Expense extends Fragment {
    BottomBar bottomBar;
    RoomAdapter roomAdapter;
    ExpenseAdapter expenseAdapter;
    RecyclerView recyclerView;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.income_expense, container, false);
        bottomBar = view.findViewById(R.id.bottomBar);
        recyclerView = view.findViewById(R.id.myrecView2);
        recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));

        bottomBar.setOnTabSelectListener(new OnTabSelectListener() {
            @Override
            public void onTabSelected(int tabId) {
                if (tabId == R.id.Expenses) {
                    AppDatabase db = Room.databaseBuilder(getContext(), AppDatabase.class, "myexpense").allowMainThreadQueries().build();
                    ExpenseDao expenseDao = db.expenseDao();
                    List<Expense> expenses = expenseDao.getAllExpenses();
                    expenseAdapter = new ExpenseAdapter(getContext(), expenses);
                    recyclerView.setAdapter(expenseAdapter);
                    expenseAdapter.notifyDataSetChanged();
                }
            }
        });
        return view;
    }
}

5. AppDatabase数据库定义类

@Database(entities = {Income.class,Expense.class}, version = 2, exportSchema = false)
@TypeConverters({Converters.class})
public abstract class AppDatabase extends RoomDatabase {
    public abstract UserDao userDao();
    public abstract ExpenseDao expenseDao();
}

附:开发者已确认myExpense表中存在已成功存入的有效数据


问题原因及修复方案

代码存在3处直接导致列表无法正常展示的错误:

  • 重复创建Room数据库实例,查询的是空数据集
    每次点击Tab都调用Room.databaseBuilder()生成新的数据库连接实例,和之前写入数据时用的实例完全独立,查询到的永远是空列表。必须将AppDatabase改为单例模式,整个应用生命周期内只初始化一次实例。
    直接在AppDatabase类中添加如下单例实现:

    private static volatile AppDatabase INSTANCE;
    public static AppDatabase getInstance(Context context) {
        if (INSTANCE == null) {
            synchronized (AppDatabase.class) {
                if (INSTANCE == null) {
                    INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
                                    AppDatabase.class, "myexpense")
                            .allowMainThreadQueries()
                            .build();
                }
            }
        }
        return INSTANCE;
    }
    

    后续所有需要操作数据库的位置,直接调用AppDatabase.getInstance(getContext())获取实例即可,禁止重复调用databaseBuilder。

  • ViewHolder中图标控件未绑定,触发空指针崩溃
    myViewHolder构造方法中只绑定了4个TextView控件,ExpenseIcon没有执行findViewById赋值,onBindViewHolder阶段直接调用setImageResource会直接抛出空指针异常,中断RecyclerView的渲染流程,导致列表无法显示。
    修复方式:找到item布局中消费类型图标的id,在ViewHolder构造方法中补充绑定代码:

    ExpenseIcon = itemView.findViewById(R.id.替换为布局中图标的实际id);
    
  • 数据加载逻辑触发时机缺失
    数据查询和适配器绑定逻辑只写在了Tab选中回调中,一方面多数BottomBar组件在页面首次加载默认选中Tab时,不会触发onTabSelected回调,导致根本不会执行查询逻辑;另一方面每次加载都新建Adapter实例,很容易造成列表状态错乱。
    修复方式:在RecyclerView设置完LayoutManager后,主动执行一次费用数据加载逻辑;同时给Adapter新增数据更新方法,后续数据变化时直接替换数据集再调用notifyDataSetChanged()刷新即可,不需要反复创建新的Adapter实例。


其他优化建议
  • allowMainThreadQueries()仅适合本地调试阶段使用,正式环境必须移除,改为协程、RxJava等异步方式操作数据库,避免数据量大时阻塞主线程造成ANR。
  • 金额字段Amount当前为String类型,直接调用SQL的SUM函数容易触发类型转换异常,建议将该字段改为Float或Int等数值类型存储。
  • Java变量命名遵循小驼峰规范,不要使用首字母大写的形式命名变量,避免和类名、常量名混淆,减少低级错误出现概率。

内容的提问来源于stack exchange,提问作者Naveed

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 18:57:24