patzly / grocy-android

ERP beyond your fridge, now on your phone – An awesome companion app for grocy
https://patrickzedler.com/grocy/
GNU General Public License v3.0
905 stars 85 forks source link

Crash when loading recipes with NaN recipe_pos #565

Closed Whhoesj closed 2 years ago

Whhoesj commented 2 years ago

I have a crash when the app is trying to load my recipes. Apparently, I have 2 recipes with an ingredient with the amount "NaN". I'm not sure how that happened, but the app can't handle it.

This is probably something that should be fixed in Grocy. But Grocy seems to handle it just fine by displaying a 0 in the web UI for the ingredient amount.

Some details: Grocy version: 3.3.1 App version: 2.3.0 (compiled from commit 03939f0).

One of the recipe positions causing this error:

  {
    "id": "122",
    "recipe_id": "25",
    "product_id": "94",
    "amount": "NaN",
    "note": "",
    "qu_id": "2",
    "only_check_single_unit_in_stock": "0",
    "ingredient_group": "",
    "not_check_stock_fulfillment": "0",
    "row_created_timestamp": "2020-05-04 12:50:40",
    "variable_amount": "",
    "price_factor": "1.0"
  }

The stacktrace from the crash:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: xyz.zedler.patrick.grocy, PID: 13620
    io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException: The exception was not handled due to missing onError handler in the subscribe() method call. Further reading: https://github.com/ReactiveX/RxJava/wiki/Error-Handling | android.database.sqlite.SQLiteConstraintException: NOT NULL constraint failed: recipe_pos_table.amount (code 1299 SQLITE_CONSTRAINT_NOTNULL)
        at io.reactivex.rxjava3.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:717)
        at io.reactivex.rxjava3.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:714)
        at io.reactivex.rxjava3.internal.subscribers.LambdaSubscriber.onError(LambdaSubscriber.java:79)
        at io.reactivex.rxjava3.internal.operators.flowable.FlowableDoFinally$DoFinallySubscriber.onError(FlowableDoFinally.java:89)
        at io.reactivex.rxjava3.internal.operators.flowable.FlowableObserveOn$BaseObserveOnSubscriber.checkTerminated(FlowableObserveOn.java:209)
        at io.reactivex.rxjava3.internal.operators.flowable.FlowableObserveOn$ObserveOnSubscriber.runAsync(FlowableObserveOn.java:394)
        at io.reactivex.rxjava3.internal.operators.flowable.FlowableObserveOn$BaseObserveOnSubscriber.run(FlowableObserveOn.java:176)
        at io.reactivex.rxjava3.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:123)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loopOnce(Looper.java:201)
        at android.os.Looper.loop(Looper.java:288)
        at android.app.ActivityThread.main(ActivityThread.java:7870)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
     Caused by: android.database.sqlite.SQLiteConstraintException: NOT NULL constraint failed: recipe_pos_table.amount (code 1299 SQLITE_CONSTRAINT_NOTNULL)
        at android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method)
        at android.database.sqlite.SQLiteConnection.executeForLastInsertedRowId(SQLiteConnection.java:940)
        at android.database.sqlite.SQLiteSession.executeForLastInsertedRowId(SQLiteSession.java:790)
        at android.database.sqlite.SQLiteStatement.executeInsert(SQLiteStatement.java:89)
        at androidx.sqlite.db.framework.FrameworkSQLiteStatement.executeInsert(FrameworkSQLiteStatement.java:51)
        at androidx.room.EntityInsertionAdapter.insertAndReturnIdsList(EntityInsertionAdapter.java:243)
        at xyz.zedler.patrick.grocy.dao.RecipePositionDao_Impl$3.call(RecipePositionDao_Impl.java:88)
        at xyz.zedler.patrick.grocy.dao.RecipePositionDao_Impl$3.call(RecipePositionDao_Impl.java:83)
        at io.reactivex.rxjava3.internal.operators.single.SingleFromCallable.subscribeActual(SingleFromCallable.java:43)
        at io.reactivex.rxjava3.core.Single.subscribe(Single.java:4813)
        at io.reactivex.rxjava3.internal.operators.mixed.FlowableConcatMapSingle$ConcatMapSingleSubscriber.drain(FlowableConcatMapSingle.java:260)
        at io.reactivex.rxjava3.internal.operators.mixed.FlowableConcatMapSingle$ConcatMapSingleSubscriber.onNext(FlowableConcatMapSingle.java:136)
        at io.reactivex.rxjava3.internal.operators.flowable.FlowableFromArray$ArraySubscription.slowPath(FlowableFromArray.java:163)
        at io.reactivex.rxjava3.internal.operators.flowable.FlowableFromArray$BaseArraySubscription.request(FlowableFromArray.java:91)
        at io.reactivex.rxjava3.internal.operators.mixed.FlowableConcatMapSingle$ConcatMapSingleSubscriber.onSubscribe(FlowableConcatMapSingle.java:125)
        at io.reactivex.rxjava3.internal.operators.flowable.FlowableFromArray.subscribeActual(FlowableFromArray.java:39)
        at io.reactivex.rxjava3.core.Flowable.subscribe(Flowable.java:15747)
        at io.reactivex.rxjava3.internal.operators.mixed.FlowableConcatMapSingle.subscribeActual(FlowableConcatMapSingle.java:61)
        at io.reactivex.rxjava3.core.Flowable.subscribe(Flowable.java:15747)
E/AndroidRuntime:     at io.reactivex.rxjava3.core.Flowable.subscribe(Flowable.java:15693)
        at io.reactivex.rxjava3.internal.operators.flowable.FlowableSubscribeOn$SubscribeOnSubscriber.run(FlowableSubscribeOn.java:82)
        at io.reactivex.rxjava3.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:65)
        at io.reactivex.rxjava3.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:56)
        at java.util.concurrent.FutureTask.run(FutureTask.java:264)
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:307)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1137)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637)
        at java.lang.Thread.run(Thread.java:1012)
I/Process: Sending signal. PID: 13620 SIG: 9
Disconnected from the target VM, address: 'localhost:34943', transport: 'socket'

I'm not familiar enough with rxjava to propose a proper a fix, but this fixed the issue in my case:

Index: app/src/main/java/xyz/zedler/patrick/grocy/helper/DownloadHelper.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/app/src/main/java/xyz/zedler/patrick/grocy/helper/DownloadHelper.java b/app/src/main/java/xyz/zedler/patrick/grocy/helper/DownloadHelper.java
--- a/app/src/main/java/xyz/zedler/patrick/grocy/helper/DownloadHelper.java (revision 03939f0412db311d47be20852a04f48b84530d6c)
+++ b/app/src/main/java/xyz/zedler/patrick/grocy/helper/DownloadHelper.java (date 1660580115733)
@@ -2645,6 +2645,12 @@
                     if (debug) {
                       Log.i(tag, "download RecipePositions: " + recipePositions);
                     }
+                    for (int i = 0; i < recipePositions.size(); i++) {
+                      RecipePosition recipePos = recipePositions.get(i);
+                      if (Double.isNaN(recipePos.getAmount())) {
+                        recipePos.setAmount(0);
+                      }
+                    }
                     Single.concat(
                             appDatabase.recipePositionDao().deleteRecipePositions(),
                             appDatabase.recipePositionDao().insertRecipePositions(recipePositions)
dominiczedler commented 2 years ago

Closed with 4362ed1.

Thanks for your fix!