I'm trying to implement a progress bar for a video player. I have a callback function that I call every 250ms via setInterval() to get the current playback position of the video and calculate the new width of the progress bar.
The problem is that on android, when the user hits "back" to close the window (and in this case, exit the app), I get a nasty NullPointerException:
[ERROR] : TiApplication: (main) [51717,51717] Sending event: exception on thread: main msg:java.lang.NullPointerException; Titanium 3.3.0,2014/07/11 12:36,787cd39 [ERROR] : TiApplication: java.lang.NullPointerException [ERROR] : TiApplication: at org.appcelerator.titanium.proxy.TiViewProxy.handleMessage(TiViewProxy.java:289) [ERROR] : TiApplication: at android.os.Handler.dispatchMessage(Handler.java:98) [ERROR] : TiApplication: at android.os.Looper.loop(Looper.java:157) [ERROR] : TiApplication: at android.app.ActivityThread.main(ActivityThread.java:5356) [ERROR] : TiApplication: at java.lang.reflect.Method.invokeNative(Native Method) [ERROR] : TiApplication: at java.lang.reflect.Method.invoke(Method.java:515) [ERROR] : TiApplication: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1265) [ERROR] : TiApplication: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1081) [ERROR] : TiApplication: at dalvik.system.NativeStart.main(Native Method)I've tried to listen for the
close event and call clearInterval(), but I never seem to get to the close event. It seems that the window and its views are being torn down before the close event is fired, so the last call to my setInterval() callback throws this nasty error. The end user gets an "Unfortunately, the app has stopped" popup. Ugly.
I've narrowed this down to my use of View.size in my callback.
I've found two workarounds: one is to intercept the androidback event and clear the interval before the window is closed. That feels like a total hack, though.
A slightly better workaround is to query for the width of the view in question and store that width; in this way, my callback doesn't have to access View.size. I thought that using postlayout would be ideal, except that each time I change the size of the progress indicator, postlayout is fired on the progress bar that contains it, so it was constantly firing, and in fact, it would fire during the window teardown as well, throwing the NullPointerException
Am I missing something obvious? Is there a better workaround?
Simplified code below. I ripped out the video stuff, and all this code does is run a progress bar across the screen for 60 seconds. Launch it on android, and exit while the bar is moving, and you'll see the problem.
var win = Titanium.UI.createWindow({ title : 'Test', backgroundColor : '#fff' }); var v_progress_bar = Ti.UI.createView ({ left: 10, right: 10, height: 10, backgroundColor: '#333' }); var v_progress_indicator = Ti.UI.createView ({ left: 0, width: 0, top: 0, bottom: 0, backgroundColor: '#2F7CC1' }); v_progress_bar.add (v_progress_indicator); win.add (v_progress_bar); var current_fill_width = 0; var duration = 60000; var start_time = new Date().getTime (); var progress_bar_width = 0; function update_position () { var now = new Date().getTime (); var elapsed = now - start_time; if (elapsed > duration) { elapsed = duration; } // don't try to access size from within setInterval(); when the window is closed // on android, you'll get a nasty NullPointerException, even if you try to remove // the event listener on window close var fill_width = parseInt (elapsed / duration * progress_bar_width); if (fill_width != current_fill_width) { Ti.API.info ("setting progress_bar_width..."); v_progress_indicator.setWidth (fill_width); current_fill_width = fill_width; } } // get the size once at startup, and get it any time the orientation changes... // if we didn't remove the event listener, it would get triggered every time // we resized the progress indicator, which would mean we'd get triggered // during the window close; accessing the size property of a view during // the window close will cause a NullPointer Exception. function on_new_size (e) { Ti.API.info ("checking progress_bar_width..."); progress_bar_width = v_progress_bar.size.width; Ti.API.info ("progress bar width: " + progress_bar_width); win.removeEventListener ('postlayout', on_new_size); } win.addEventListener ('postlayout', on_new_size); Ti.Gesture.addEventListener('orientationchange',function(e) { win.addEventListener ('postlayout', on_new_size); }); var up_int = setInterval (update_position, 250); win.addEventListener ('close', function (e) { Ti.API.info ('win closed.'); clearInterval (up_int); }); win.open();