Skip to content

Gym mode: Null check crash when saving log (setState called without mounted check) #1152

@vhhr7

Description

@vhhr7

Bug description

When saving a workout log in gym mode, the app crashes with:

Error: Null check operator used on a null value
#0  State.setState (package:flutter/src/widgets/framework.dart:1219)
#1  _LogFormWidgetState.build.<anonymous closure> (log_page.dart:653)

The log is saved correctly on the server (POST /api/v2/workoutlog/ returns 201), but the crash makes the user think it failed. On retry, duplicate log entries are created.

Steps to reproduce

  1. Open a routine in gym mode
  2. Enter reps/weight and tap Save
  3. App crashes immediately after the log is saved

Root cause

In lib/widgets/routines/forms/log_form.dart, the async save closure calls setState() in try/catch/finally after await addLog(...) without checking if (mounted) first. If the widget unmounts while the async operation is in flight, Flutter throws on setState of a disposed widget.

Additionally, setState(() { _isSaving = false; }) is called redundantly in both the try block and the finally block, so it always runs twice on success.

Current code (v1.10.2, log_form.dart)

try {
  await provider.Provider.of<RoutinesProvider>(context, listen: false).addLog(logToSave!);
  // ...
  if (mounted) {
    ScaffoldMessenger.of(context).showSnackBar(...); // ✅ mounted check here
  }
  widget.controller.nextPage(...);
  setState(() { _isSaving = false; });              // ❌ no mounted check
} on WgerHttpException {
  setState(() { _isSaving = false; });              // ❌ no mounted check, redundant with finally
  rethrow;
} finally {
  setState(() { _isSaving = false; });              // ❌ no mounted check
}

Proposed fix

Remove the redundant setState calls in try and catch, keep only the finally block wrapped with if (mounted):

try {
  await provider.Provider.of<RoutinesProvider>(context, listen: false).addLog(logToSave!);
  // ...
  if (mounted) {
    ScaffoldMessenger.of(context).showSnackBar(...);
  }
  widget.controller.nextPage(...);
  // setState removed here — finally handles it
} on WgerHttpException {
  rethrow; // setState removed — finally handles it
} finally {
  if (mounted) {                                    // ✅ mounted check added
    setState(() { _isSaving = false; });
  }
}

Environment

  • App version: 1.10.1 (build 180) and 1.10.2 (bug present in both)
  • Platform: Android
  • Server: self-hosted wger instance

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions