1

In my Codename One app I resort to the following iOS native code to know if the battery is charging or full:

-(BOOL)isCharging{
    [[UIDevice currentDevice] setBatteryMonitoringEnabled:YES];

    if ( ([[UIDevice currentDevice] batteryState] == UIDeviceBatteryStateCharging)
            || ([[UIDevice currentDevice] batteryState] == UIDeviceBatteryStateFull) ) {
        return YES;
    }
    else {
        return NO;
    }
}

I the Codename One part I poll every 1000 ms if the battery is charging. It works perfectly on Android. However on iOS the initial state (ie when the app is launched) is kept and it does not get updated even when the battery state changes (plugged / unplugged and vice versa).

So if I start the app with the cable plugged isCharging returns YES (true in java) but if I unpluggef the cable isCharging keeps returning YES. If I close the app and launch it with the unplugged cable, isCharging returns NO and never goes to YES when I plug the cable in although the iOS toolbar on the the upper left corner shows a charging battery.

Please note : the tests are conducted on an iPhone 4

What can I do to make the method update its value when the battery state changes ?

Any help appreciated,

HelloWorld
  • 2,275
  • 18
  • 29
  • The solution in swift is provided [here](https://stackoverflow.com/questions/31391373/how-to-monitor-battery-level-and-state-changes-using-swift) – ahmed Nov 02 '17 at 21:43
  • Check that you invoke this code in the iOS thread this is often the source of weird behavior https://www.codenameone.com/blog/tip-use-native-edt.html – Shai Almog Nov 03 '17 at 05:06
  • Thank you all for your help. Ahmed: the framework I use (Codename One) requires Objective-C code. Shai: In the simulator (with native Java SE code) I sometimes get an EDT violation. So you may be right. I'll give the blog posting a read! – HelloWorld Nov 03 '17 at 08:32

2 Answers2

2

In iOS you subscribe to the system notifications. I put mine in my app delegate.

    func applicationDidBecomeActive(_ application: UIApplication) {
        NotificationCenter.default.addObserver(self, selector: #selector(AppDelegate.batteryChangeLevel), name: NSNotification.Name.UIDeviceBatteryLevelDidChange, object: UIDevice.current)
        NotificationCenter.default.addObserver(self, selector: #selector(AppDelegate.batteryChangeState), name: NSNotification.Name.UIDeviceBatteryStateDidChange, object: UIDevice.current)
    }

From there you can check states react accordingly.

UIDevice.current.batteryLevel
UIDevice.current.batteryState

IIRC, it sends a notification every % power change and whenever the device changes being plugged into power.

Be sure to unsubscribe in:

func applicationWillResignActive(_ application: UIApplication) {

    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
    // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.

    NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIDeviceBatteryLevelDidChange, object: nil)

    NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIDeviceBatteryStateDidChange, object: nil)

}
solenoid
  • 954
  • 1
  • 9
  • 20
  • Thanks Solenoid for your answer! Actually I simply wrapped the code in a block to make it run in a background thread. I'll give the app delegate a read! – HelloWorld Nov 03 '17 at 09:40
  • Why `applicationWillResignActive `? You mean we should stop watching if an incoming call blocks the window? Would it be better to unregister it in `applicationWillTerminate`? – kakyo Nov 29 '19 at 03:50
0

Thank you all for you answers. Indeed as Shai pointed out the native code was not run on the UI thread. The following code signals to the UI thread that the battery status check (done in the background block) is over :

-(BOOL)isCharging{

    // The variable will be used in a block
    // so it must have the __block in front
    __block BOOL charging = NO;

    // We run the block on the background thread and then returns to the
    // UI thread to tell the check is over.
    dispatch_async(dispatch_get_main_queue(), ^{
        // If monitoring is already enabled we dont enable it again
        if ( ![[UIDevice currentDevice] isBatteryMonitoringEnabled]) {
            [[UIDevice currentDevice] setBatteryMonitoringEnabled:YES];
        }

        if ( ([[UIDevice currentDevice] batteryState] == UIDeviceBatteryStateCharging)
            || ([[UIDevice currentDevice] batteryState] == UIDeviceBatteryStateFull) ) {
            charging = YES;
        }
        else {
            charging = NO;
        }
    });

    return charging;

}

Now the app behaves correctly!

HelloWorld
  • 2,275
  • 18
  • 29