flutter-plugins icon indicating copy to clipboard operation
flutter-plugins copied to clipboard

[Pedometer 4.0.1] stepCountStream can hang on iOS

Open chadpav opened this issue 1 year ago • 0 comments

Device / Emulator and OS

  • Device: iPhone 13 Pro
  • OS: iOS 16.4 or 17.0

Describe the bug

When my app starts I need to query for the current pedometer reading. The only way to do this with the plugin API is grab the first value off the stream: final stepCount = await Pedometer.stepCountStream.first

Normally this works fine but I've found that this call never returns in some edge cases. Leaving my app to hang on start.

To Reproduce

  1. Reboot the phone
  2. Do not move it, leave it docked or laying on your desk
  3. Call await Pedometer.stepCountStream.first -- app hangs
  4. pick up phone and walk around
  5. API call returns a result and from now on you get instant results from this API call

Expected behavior

  • The api should always return a response

Actual behavior

stepCountStream does not return any response until the device starts moving

Flutter doctor

n/a

Additional information

I stepped into the swift native code and determined that this is expected/undocumented behavior of CMPedometer after a reboot.

...
pedometer.startUpdates...
...

does not return on the callback but if I replace that code with this

                pedometer.queryPedometerData(from: dateOfLastReboot, to: Date()) { pedometerData, error in
                    guard let pedometerData = pedometerData, error == nil else { return }
                    
                    DispatchQueue.main.async {
                        self.handleEvent(count: pedometerData.numberOfSteps.intValue)
                    }
                }

it works as you would expect. I get an immediate response on the callback regardless of reboot.

Workaround

For those interested, I added a timeout to the Dart Future to get around this for now. This throws an exception which can be handled in a try/catch:

try {
      final stepCount = await Pedometer.stepCountStream.first.timeout(const Duration(milliseconds: 100));
      return Result.value(stepCount.steps);
    } catch (e) {
      return Result.error(e);
    }

P.S. - after seeing how this timeout works on anything that returns a Dart Future I think it's quite elegant already. Maybe I should be doing this for any API calls? I'll leave this bug report here but my solution works for my purposes.

chadpav avatar Oct 25 '23 16:10 chadpav