setInterval on Android?

You must Login before you can answer or comment on any questions.

I have a countdown timer that works on a setInterval timer. Despite everything working fine in the Android simulator, the timer stops virtually instantly when the app sleeps on a device.

I tried to replicate this on the simualtor by holding the power button down, but the timer still seems to work. Should the timer pause like this on the device when the phone goes to sleep?

I'm struggling with how to work this out, because the timer displays how many hours/mins/secs are left on a screen, and whenever the user wakes up the phone the timer text is all relative to when the app goes to sleep.

I guess I need to know whether there is anyway to keep this timer working when the app goes to sleep, and being able to update the text of the timer screen - or whether I need to think of another solution.

I've experimented with intents/services, but not having much joy... I guess, perhaps the only thing I can do is clear the text if the app goes to sleep and just schedule a future notificaiton when the timer is due to finish?

Any help or advice on this would be really appreciated - I'm close to pulling my hair out...

— asked 2 years ago by Chris Leyton
5 Comments
  • Here's some code to give a rough idea of where I've been trying:

    app.js

    function stopwatch(weight, hr, min, sec, thr, tmin, tsec) {
     
        tSecs = (hr*3600+min*60+sec*1);
        Ti.App.Properties.setInt('tSec',tSecs);
        Ti.API.info("Time: "+Ti.App.Properties.getInt('tSec',20));  
        Ti.API.info('sec: '+sec+'min: '+min+'hr: '+hr);
     
        if (sec <= 0) {
            sec = 59;
            min = min - 1;
            sec++;
        }else {
            min = min; 
        }
        if (min < 0) {
            min = 59; 
            hr -= 1; 
        }
        sec--;
     
        Ti.API.info('sec: '+sec+'min: '+min+'hr: '+hr);
     
        var cookTime = {'hours': hr, 'minutes': min, 'sec': sec};
        var thawTime = {'hours': thr, 'minutes': tmin, 'sec': tsec}
        var saveSlots = {'weight': weight, 'thawTime': thawTime, 'cookTime': cookTime};
     
        Ti.API.info(saveSlots['cookTime'].hours);
        Ti.API.info(saveSlots.cookTime['minutes']);
        Ti.API.info(saveSlots.cookTime['sec']);
     
        Ti.App.Properties.setString('myJSON', JSON.stringify(saveSlots));
     
        Ti.App.fireEvent("updateText");
     
        if (hr == 0 && min == 0 && sec == 0){
            clearInterval(timerInterval);
            Titanium.Media.vibrate();
            tmrStartFlag = false;
            if (!android){
                if (sleep != true){
                    Ti.App.fireEvent("ringer");
                }else{
                    var a = 0;
                    var notification = [];
                    notification[a] = Ti.App.iOS.scheduleLocalNotification({
                        alertBody : 'Turkey Cooked!',
                        alertAction : "Resume App",
                        sound:"pop.caf",
                        userInfo: {why: 'me'},
                        date : new Date(new Date().getTime())
                    });
                }
            }else{
                Ti.App.fireEvent("ringer");
            }
            clearInterval(timerInterval);       
        }
        return true; 
    }
     
    function stopTimer(){
        debugger;
        if (timerInterval != null){
            clearInterval(timerInterval);
        }
    }
     
    Ti.App.addEventListener('clockStop', stopTimer);
     
    Ti.App.addEventListener('resumed',function(e){
            Ti.API.info("app has resumed from the background");
            sleep = false;
    });
     
    Ti.App.addEventListener('pause',function(e){
            Ti.API.info("app was paused from the foreground");
            sleep = true;
    });
     
    var tmrStartFlag = false;
    var timerInterval;
    var URL = 'testservice.js';
    var totSecs;
     
    function setTimer(){
     
        debugger;
        var svSlots2 = JSON.parse(Ti.App.Properties.getString('myJSON'));
        totSecs = (svSlots2.cookTime['hours']*3600+svSlots2.cookTime['minutes']*60+svSlots2.cookTime['sec']*1);
        if (android){
            //create notification here?!?
        }else{
            var a = 0;
            var notification = [];
            notification[a] = Ti.App.iOS.scheduleLocalNotification({
                alertBody : 'Turkey Cooked!',
                alertAction : "Resume App",
                sound:"pop.caf",
                userInfo: {why: 'me'},
                date : new Date(new Date().getTime()+(totSecs*1000))
            });
        };
        tmrStartFlag = true;
     
        timerInterval = setInterval(function(){
            var svSlots = JSON.parse(Ti.App.Properties.getString('myJSON'));
     
            Ti.API.info(svSlots['weight']);
            Ti.API.info(svSlots['cookTime'].hours);
            Ti.API.info(svSlots.cookTime['minutes']);
            Ti.API.info(svSlots.cookTime['sec']);
            Ti.API.info(svSlots['thawTime'].hours);
            Ti.API.info(svSlots.thawTime['minutes']);
            Ti.API.info(svSlots.thawTime['sec']);  
            Ti.API.info('width: '+_W);
            stopwatch(svSlots.weight, svSlots.cookTime['hours'], svSlots.cookTime['minutes'], svSlots.cookTime['sec'], svSlots.thawTime['hours'], svSlots.thawTime['minutes'], svSlots.thawTime['sec']);
        }, 1000);
    }
     
    Ti.App.addEventListener('timerStart', setTimer);
    In a seperate file, in a function that calculates the data for the timer I have:

    clockwin.js

    function calculate(numPeople){
            debugger;
            if (!android){
                var service = Ti.App.iOS.registerBackgroundService({url:'bg.js'});
            }else{
     
            }
     
            if (clickLbl.visible == false){
                clickLbl.show();
            };
     
            pickerFlag = true;
            Ti.App.Properties.setBool('myBool', true);
            var weight = 0;
            cookTime = 0;
            weight = numPeople * .45;
     
            var thawTime = weight * 120;
     
            if (weight <= 4.5){
                cookTime = 5; //(weight*45)+20;
            }else if(weight <= 6.5 && weight > 4.5){
                cookTime = (weight*40);
            }else if(weight > 6.5){
                cookTime = (weight*35);
            };
     
            var cookTimeSecs = cookTime*60;
            Ti.API.info(cookTimeSecs);
            var URL = 'testservice.js';
     
            if (android){
                Ti.API.info('Starting via createService() / start()');
                var intent = Ti.Android.createServiceIntent({
                    url: 'testservice.js'
                });
                intent.putExtra('interval', 1000);
                intent.putExtra('message', 'Hi from bound service');
     
                var service = Ti.Android.createService(intent);
                service.addEventListener('start', function(e) {
                    Ti.API.info('Starting... Instance #' + e.source.serviceInstanceId + ' (bound)');
                });
                service.addEventListener('pause',function(e) {
                    Ti.API.info('Bound instance #' + e.source.serviceInstanceId + ' paused (iteration #' + e.iteration + ')');
                });
                service.addEventListener('resume',function(e) {
                    Ti.API.info ('Bound instance #' + e.source.serviceInstanceId + ' resumed (iteration #' + e.iteration + ')');
                });
                service.start();
            }
     
            thawTime = parseMinutes(thawTime);
            cookTime = parseMinutes(cookTime);
     
            saveSlots = {'weight': weight, 'thawTime': thawTime, 'cookTime': cookTime};
            Ti.App.Properties.setString('myJSON', JSON.stringify(saveSlots));       
            var retrievedJSON =Ti.App.Properties.getString('myJSON', 'myJSON not found');
     
            //so if the property/file is available we need to redo these and get the countdown running from its current state
     
            turkeySize.text = 'Size of Turkey = '+weight+'kg';
            cookingTime.text = 'Cooking Time = '+cookTime['hours']+'hrs '+cookTime['minutes']+'mins';
            thawingTime.text = 'Thawing Time = '+thawTime['hours']+'hrs '+thawTime['minutes']+'mins';
            displayLbl.text = cookTime['hours']+':'+cookTime['minutes']+':'+cookTime['sec'];
            if (android){
                timerImg.removeEventListener('click', startTimer);
            };
            timerImg.addEventListener('click',startTimer);
        }
     
        function updateLblText(){
            Ti.API.info('In updateLblText');
            var saveSlots = JSON.parse(Ti.App.Properties.getString('myJSON'));
     
            Ti.API.info(saveSlots['cookTime'].hours);
            Ti.API.info(saveSlots.cookTime['minutes']);
            Ti.API.info(saveSlots.cookTime['sec']);
            displayLbl.text = ((saveSlots.cookTime['hours']<=9) ? "0"+saveSlots.cookTime['hours'] : saveSlots.cookTime['hours']) + " : " + ((saveSlots.cookTime['minutes']<=9) ? "0" + saveSlots.cookTime['minutes'] : saveSlots.cookTime['minutes']) + " : " + ((saveSlots.cookTime['sec']<=9) ? "0" + saveSlots.cookTime['sec'] : saveSlots.cookTime['sec']);
        }
     
        Ti.App.addEventListener("updateText", updateLblText);
     
        function startTimer(){
            Ti.UI.createAlertDialog({title:'Cooking Guidelines', message:'These times are purely for guidance.\n Please ensure: \n The meat should be steaming hot all the way through.\n The thickest part of the meat should not be pink. \n Any juices should be entirely clear'}).show();
     
            clickLbl.hide();
            if (player == null){
                player = Ti.Media.createSound({
                    url:"beep.mp3",
                    preload:true    
                });
            };
            timerImg.removeEventListener('click', startTimer);
            Ti.App.addEventListener("ringer", ringAlert);
            Ti.App.fireEvent('timerStart');
        }
    And finally in testservice.js:
    var service = Ti.Android.currentService;
    var intent = service.getIntent();
    var teststring = intent.getStringExtra('message') + ' (instance ' + service.serviceInstanceId + ')';
    Ti.App.fireEvent('test_service_fire', { message: teststring});
     
    Ti.App.fireEvent('clockStop');
    Ti.App.fireEvent('timerStart');
    Any help would be really appreciated...

    — commented 2 years ago by Chris Leyton

  • As a further update, can anybody please explain this to me.

    I ran the timer with the phone connected to check logCat statements. Turned the phone off during the middle of the timer, and it ran to the end and created the notification perfectly - even with a vibration to alert the user.

    However as soon as I take the phone off the computer and try it I face the same problems, the timer just seems to stop and the notification never occurs as a result.

    I'm really struggling to get my head around this and would really appreciate some guidance.

    I have re-written a lot of the code above to try and simplify it to understand it, it's now:

    app.js

    function setTimer(){
     
        debugger;
        var svSlots2 = JSON.parse(Ti.App.Properties.getString('myJSON'));
        totSecs = (svSlots2.cookTime['hours']*3600+svSlots2.cookTime['minutes']*60+svSlots2.cookTime['sec']*1);
        Ti.API.info('TOTAL SECONDS: '+totSecs);
        if (android){
            //create notification here?!?
            var now = new Date().getTime()
            var delta = new Date( now + (totSecs * 1000) );
            var deltaMS = delta - now;
     
            var intent = Ti.Android.createServiceIntent({
                url : 'ExampleService.js'
            });
            intent.putExtra('interval', deltaMS);
            intent.putExtra('message' , 'This is that little extra');
            Ti.Android.startService(intent);
        }else{
            var a = 0;
            var notification = [];
            notification[a] = Ti.App.iOS.scheduleLocalNotification({
                alertBody : 'Turkey Cooked!',
                alertAction : "Resume App",
                sound:"pop.caf",
                userInfo: {why: 'me'},
                date : new Date(new Date().getTime()+(totSecs*1000))
            });
        };
        tmrStartFlag = true;
     
        timerInterval = setInterval(stopwatch, 1000);
    }
     
    Ti.App.addEventListener('timerStart', setTimer);
    ExampleService.js
    Ti.API.info('IN THE BACKGROUND');
    if(!Ti.App.Properties.hasProperty('notificationCount')) {
        Ti.App.Properties.setInt('notificationCount', 0);
    } else {
        Ti.App.Properties.removeProperty('notificationCount');
     
        var activity = Ti.Android.currentActivity();
        var intent = Ti.Android.createIntent({
            action : Ti.Android.ACTION_MAIN,
            // you can use className or url to launch the app
            // className and packageName can be found by looking in the build folder
            // for example, mine looked like this
            // build/android/gen/com/appcelerator/test/Test7Activity.java
            // className : 'com.appcelerator.test.Test7Activity',
     
            // if you use url, you need to make some changes to your tiapp.xml
            url : 'app.js',
            flags : Ti.Android.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | Ti.Android.FLAG_ACTIVITY_SINGLE_TOP
        });
        intent.addCategory(Titanium.Android.CATEGORY_LAUNCHER);
     
        var pending = Ti.Android.createPendingIntent({
            activity : activity,
            intent : intent,
            type : Ti.Android.PENDING_INTENT_FOR_ACTIVITY,
            flags : Ti.Android.FLAG_ACTIVITY_NO_HISTORY
        });
     
        var notification = Ti.Android.createNotification({
            contentIntent : pending,
            contentTitle : 'Test',
            contentText : 'test',
            tickerText : 'This is a test',
            // "when" will only put the timestamp on the notification and nothing else.
            // Setting it does not show the notification in the future
            when : new Date().getTime(),
            icon : Ti.App.Android.R.drawable.appicon,
            flags : Titanium.Android.ACTION_DEFAULT | Titanium.Android.FLAG_AUTO_CANCEL | Titanium.Android.FLAG_SHOW_LIGHTS
        });
     
        Ti.Android.NotificationManager.notify(1, notification);
     
        var service = Ti.Android.currentService;
        var serviceIntent = service.getIntent();
     
        // this will display that custom extra that we added when we created the intent
        // intent.putExtra('message' , 'This is that little extra');
        var teststring = serviceIntent.getStringExtra('message');
        Ti.API.info('Extra!: ' + teststring);
     
        Ti.Android.stopService(serviceIntent);
    }

    — commented 2 years ago by Chris Leyton

  • I just cant seem to get this notification to fire when the phone enters into sleep mode - any ideas, please?

    — commented 2 years ago by Chris Leyton

  • Show 2 more comments

3 Answers

I've been using the Android Interval service to work around any issues with timers.

It has been working well in production over the last few months. I just put all of the code needed to calculate duration in the service.js then fire an app event.

Not sure if it will help with your exact scenario but has worked on my projects.

var serviceInterval = 6000;
var intent = Ti.Android.createServiceIntent({url:_serviceUrl});
intent.putExtra('interval', serviceInterval);
var service = Ti.Android.createService(intent);
service.addEventListener('start', function(e) {
Ti.API.info('Service Started');
});
 
service.start();

Chris, you're trying too hard. If your app is asleep, there's no need for it to continue timer processing or update a screen. In the app "pause" event, kill your timer and stop updating the screen. In the app "resume" event, immediately update the screen again, and restart your timer.

— answered 1 year ago by Shawn Lipscomb
answer permalink
1 Comment
  • Hi guys - thanks for the update. Just to update the scenario, the app was released and was essentially a timer to calculate how long a turkey needs to be cooked for Christmas dinner. So I needed the timer to consistently run, even when the phone was in standby.

    I really struggled with this and in the end (due to time constraints) went to plan B, by creating an event in the system's default calendar - which set an alarm when the turkey was cooked.

    I was advised during development that I would have to write a native Java module to use AlarmManager - which was beyond my scope and I had a deadline fast approaching.

    Thanks again for all of these updates, great to see the Titanium community really pulling together.

    — commented 1 year ago by Chris Leyton

Android Timer (even when the phone goes to sleep)

When the phone goes to sleep, the app is “paused” and therefore, the timer is paused. To solve this issue on Android, I added the WAKE_LOCK permission to tiapp.xml:

()
    <android xmlns:android="http://schemas.android.com/apk/res/android">
        <manifest android:versionName="1.0.0">
            <uses-permission android:name="android.permission.WAKE_LOCK" />
        </manifest>
        <services>
            <service type="interval" url="myservice.js"/>
        </services>
    </android>
()
I am using an interval service that updates a progress bar; for an example, please look at ‘examples/android_services.js’ in the Kitchen Sink.

Iphone Timer

I use the ildeTimerDisabled

Ti.App.idleTimerDisabled = true; // to disable going to sleep (iPhone only) 
Ti.App.idleTimerDisabled = false; // to re-enable it
Hope this helps.

Your Answer

Think you can help? Login to answer this question!