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

