从 Android 房间中检索双精度值导致 NullPointerException

Retrieving double value from Android Room cause NullPointerExcpetion

我有一个 android 房间 DB 和下一个 POJO

  Entity(tableName = "transactions")
    public class BoardItem {
        @PrimaryKey(autoGenerate = true)
        int itemID;
        String account;
        String date;
        String category;
        double value:
//getters and setters
    }

对于指定类别的所有值的加载总和,我尝试使用下一个DAO

@Query ("SELECT category, SUM (value) as total FROM transactions WHERE category =:transactionCategory GROUP BY category ")
double getAllTransactionInCategory(String transactionCategory);

存储库 android 房间中的下一个代码:

public class RoomTransactionsRepository {
    private final RoomTransactionsDAO mTransactionsDao;
    private final LiveData<List<TransactionBoardItem>> mAllTransactions;

    RoomTransactionsRepository(Application application){
            RoomTransactionsDatabase db = RoomTransactionsDatabase.getDatabase(application);
            mTransactionsDao = db.transactions_dao();
            mAllTransactions = mTransactionsDao.getAllTransactions();
    }

    LiveData<List<TransactionBoardItem>> getAllTransactions(){
        return mAllTransactions;
    }

    double getAllTransactionInCategory (String category){
        return mTransactionsDao.getAllTransactionInCategory (category);
    }

    //Livedata code...

但是在尝试以其他方式在文本视图中显示数据后,它会抛出下一个 NullPointerException,尽管数据库中存在此类数据(已在 Android Studio 数据库检查器中检查):

2021-05-20 21:38:06.800 6673-6673/com.example.dbexample E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.dbexample, PID: 6673
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.dbexample/com.example.dbexample.transactions.TransactionStatisticActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'double com.example.dbexample.transactions.RoomTransactionsRepository.getAllTransactionInCategory(java.lang.String)' on a null object reference
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3792)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3968)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2307)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:246)
        at android.app.ActivityThread.main(ActivityThread.java:8506)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1139)
     Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'double com.example.dbexample.transactions.RoomTransactionsRepository.getAllTransactionInCategory(java.lang.String)' on a null object reference
        at com.example.dbexample.transactions.TransactionStatisticActivity.onCreate(TransactionStatisticActivity.java:22)
        at android.app.Activity.performCreate(Activity.java:8198)
        at android.app.Activity.performCreate(Activity.java:8182)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3765)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3968) 
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2307) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:246) 

要显示我使用下一个代码:

public class StatisticActivity extends AppCompatActivity {
    RoomTransactionsRepository repository;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // init code 

         binding.totalValue.setText (String.valueOf (repository.getAllTransactionInCategory ("Food")));
    }
}

当我尝试显示我想从数据库中提取的双精度值时,我在 DAO 或存储库代码中做错了什么会导致 NullPointerException? 提前致谢!

更新:VievModel class。它单独使用 activity 将数据库中的数据动态插入 RecyclerView。

public class RoomTransactionsViewModel extends AndroidViewModel {
    private final RoomTransactionsRepository mRepo;
    private final LiveData<List<TransactionItem>> mAllTransactions;

    public RoomTransactionsViewModel(Application application){
        super(application);
        mRepo = new RoomTransactionsRepository(application);
        mAllTransactions  = mRepo.getAllTransactions();
    }

    LiveData<List<TransactionBoardItem>> getAllTransactions(){
        return mAllTransactions;
    }

    public void insert(TransactionItem item){mRepo.insertTransaction(item);}
    public void deleteAll(){mRepo.deleteAllTransactions();}
    
}

更新 2:我修改了答案,但使用略有不同的方法来解决我的问题。
第 1 步:我为要提取的行创建 POJO:

public class TransactionStatisticItem {
    //`@ColumnInfo(name = "")` should equal to name of row in your DB
    @ColumnInfo(name = "value")
    double  transactionValue;//+

   //getters

    public TransactionStatisticItem(double transactionValue, String transactionCategory) {
        this.transactionValue = transactionValue;
        this.transactionCategory = transactionCategory;
    }
    @ColumnInfo(name = "category")
    String transactionCategory;//+
}  

第 2 步:我在回答中创建了界面,但用 POJO 替换了 double class void onReturned(TransactionStatisticItem item);
第 3 步:根据答案更新存储库代码但有更改:

//To retrieve data asynchronous from DB you can use : ExecutorService (java) / Coroutines (kotlin) or AsyncTask (deprecated in API level 30)
 public static final ExecutorService singleThreadExecutor =
            Executors.newSingleThreadExecutor();

    void getAllTransactionInCategory (String category, TransactionStatisticListener listener){
            singleThreadExecutor.execute(() -> listener.onReturned(mTransactionsDao.getAllTransactionInCategory (category)));
        }

第 4 步:在 ViewModel 中,我添加了下一行

 void getAllTransactionInCategory (String category, TransactionStatisticListener listener){
        mRepo.getAllTransactionInCategory (category, listener);
    }

第 5 步:最后在 Activity 中添加下一个代码:

RoomTransactionsViewModel model = new ViewModelProvider (this).get(RoomTransactionsViewModel.class);

        model.getAllTransactionInCategory("Food", item -> {
            binding.totalValue.setText (String.valueOf (item.getTransactionValue ()));
            binding.category.setText (String.valueOf (item.getTransactionCategory ()));
        });

最后,我能够检索到下一个示例数据:“食品”类别中所有交易的总和。

binding.totalValue.setText (String.valueOf (repository.getAllTransactionInCategory ("Food")));

为了遵循 Google MVVM 准则,您不应从 activity 访问存储库,因为它被视为数据库的一个层,不应由视图处理,而应由 ViewModel

另一个问题是您 return 在不使用侦听器的情况下从数据库中获取双精度值;由于从数据库中获取值需要一些时间,因此 getAllTransactionInCategory() 不会 return 预期值。这可以通过使用侦听器接口来解决,或者 returning a LiveData<Double> 并在 activity.

中观察它

应用:

因此,建议通过 ViewModel:

访问它

道:return一个LiveData<Double>

@Query ("SELECT category, SUM (value) as total FROM transactions WHERE category =:transactionCategory GROUP BY category ")
LiveData<Double> getAllTransactionInCategory(String transactionCategory);

视图模型:

public class RoomTransactionsViewModel extends AndroidViewModel {
   
   // ...
    
    
    LiveData<Double> getAllTransactionInCategory (String category){
        return mRepo.getAllTransactionInCategory (category);
    }
}

存储库:

public class RoomTransactionsRepository {

    //.....
    
    LiveData<Double> getAllTransactionInCategory (String category){
        return mTransactionsDao.getAllTransactionInCategory (category);
    }

并观察 activity 中的 LiveData:

public class StatisticActivity extends AppCompatActivity {
    RoomTransactionsRepository repository;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // init code 
        
        
        RoomTransactionsViewModel model = new ViewModelProvider(this).get(RoomTransactionsViewModel.class);
        
        
        model.getAllTransactionInCategory("Food").observe(this, new Observer<Double>() {
            @Override
            public void onChanged(Double value) {
                binding.totalValue.setText (value);
            }
        });
        
      //  binding.totalValue.setText (String.valueOf (model.getAllTransactionInCategory ("Food")));
        
        
        
    }
}

更新:

"This can be solved by using a listener interface" can you elaborate, please

要使用侦听器,您需要一个具有回调方法的接口,该方法接受您想要从 Room return 接收的类型,在本例中为 double

public interface TransactionListener {
     void onReturned(double count);
}

然后在房间return的值如下时使用这个接口的实现:

道:

@Query ("SELECT category, SUM (value) as total FROM transactions WHERE category =:transactionCategory GROUP BY category ")
double getAllTransactionInCategory(String transactionCategory);

视图模型:

public class RoomTransactionsViewModel extends AndroidViewModel {
   
   // ...
    
    
    void getAllTransactionInCategory (String category, TransactionListener listener){
        mRepo.getAllTransactionInCategory (category, listener);
    }
}

存储库:

public class RoomTransactionsRepository {

    //.....
    
    void getAllTransactionInCategory (String category, TransactionListener listener){
 
        listener.onReturned(mTransactionsDao.getAllTransactionInCategory (category));
    }

Activity:

public class StatisticActivity extends AppCompatActivity {
    RoomTransactionsRepository repository;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // init code 
        
        
        RoomTransactionsViewModel model = new ViewModelProvider(this).get(RoomTransactionsViewModel.class);
        
            
        model.getAllTransactionInCategory("Food", new TransactionListener() {
            @Override
            public void onReturned(double count) {
                binding.totalValue.setText (String.valueOf (count));
            }
        });
        
    }
}