Updating Death Clock for Flex 4.6

Adobe has been talking lately about the next update to Flex and Flash Builder, version 4.6. There’s a lot of cool stuff planned, but some of the things that interest me most are the new UI components. You can read a good article on them here, What’s new in Flex 4.6 SDK and Flash Builder 4.6. I thought it might be interesting to update one of my own application, the Death Clock mobile app, to use some of these new components.

When I built the Death Clock application, one of the first things I ran into was handling the UI for picking gender and the month of your birth. Dropdowns just don’t work well on a mobile device. I ended up using a hybrid approach that used states and groups of radio buttons. You can read more about this in my original blog post from … wow… exactly one year ago. Man - I’d love to say I planned that but it was completely random! Anyway - be sure to look at that post for an example of the UI.

For my new application, I decided to get rid of this work around and make use of the new ToggleSwitch and DateSpinner classes. I thought the ToggleSwitch would be a good replacement for the gender selection and the date spinner would - obviously - replace the date drop downs. Here’s what the Flex code looks like for the original version, again, with the work around:

<?xml version=“1.0” encoding=“utf-8”?> <s:View xmlns:fx=“http://ns.adobe.com/mxml/2009" xmlns:s=“library://ns.adobe.com/flex/spark” title=“Death Clock” initialize=“init()” xmlns:mx=“library://ns.adobe.com/flex/mx” backgroundColor=“0x000000”> <fx:Declarations> <mx:NumberValidator id=“dayValidator” source=“{dayBorn}” property=“text” allowNegative=“false” minValue=“1” maxValue=“31” invalidCharError=“Day Born must be a number.” integerError=“Day Born must be a number.” negativeError=“Day Born must be a positive number.” lowerThanMinError=“Day Born must be over 0.” exceedsMaxError=“Day Born must be lower than 31.”/>

    &lt;mx:NumberValidator id="yearValidator" source="{yearBorn}" property="text" allowNegative="false" minValue="1900" maxValue="2020" 
                        invalidCharError="Year Born must be a number." integerError="Year Born must be a number." negativeError="Year Born must be a positive number." 
                        lowerThanMinError="Year Born must be over 1900." exceedsMaxError="Year Born must be lower than 2020." /&gt;

    &lt;s:RadioButtonGroup id="monthRBG" change="setMonthLabel()"/&gt;
    &lt;s:RadioButtonGroup id="genderRBG" change="setGenderLabel()"/&gt;
&lt;/fx:Declarations&gt;

&lt;fx:Style&gt;
    @namespace s "library://ns.adobe.com/flex/spark";
    @namespace mx "library://ns.adobe.com/flex/mx";

    #errorDiv {
        font-weight: bold;
        color: red;
    }
&lt;/fx:Style&gt;

&lt;fx:Script&gt;
    &lt;![CDATA[
        import mx.collections.ArrayCollection;
        import mx.events.ValidationResultEvent;

        [Bindable] private var months:ArrayCollection;      
        [Bindable] private var days:ArrayCollection;
        [Bindable] private var years:ArrayCollection;

        private var selectedMonth:int;
        private var selectedGender:int;

        private function setGenderLabel():void {
            if(genderRBG.selection) { 
                genderButton.label = genderRBG.selection.label;
                selectedGender = int(genderRBG.selection.value);
            }
            toggleGenderState();
        }

        private function setMonthLabel():void {
            if(monthRBG.selection) { 
                monthButton.label = monthRBG.selection.label;
                selectedMonth = int(monthRBG.selection.value);
            }
            toggleMonthState();
        }

        private function toggleGenderState():void {
            if(this.currentState == 'normal') this.currentState='selectedGender';       
            else this.currentState='normal';
        }

        private function toggleMonthState():void {
            if(this.currentState == 'normal') this.currentState='selectedMonth';        
            else this.currentState='normal';
        }

        private function init():void {
            this.currentState = 'normal';

        }

        private function doClock():void {

            var validation:ValidationResultEvent;
            validation = dayValidator.validate();
            if(validation.type == "invalid") {
                errorDiv.text = validation.message;
                return;         
            }
            validation = yearValidator.validate();
            if(validation.type == "invalid") {
                errorDiv.text = validation.message;
                return;         
            }

            errorDiv.text = '';

            var bDay:Date = new Date();
            bDay.fullYear = int(yearBorn.text);

            bDay.month = selectedMonth;
            bDay.date = int(dayBorn.text);

            var now:Date = new Date();

            //Life expectancy will be 75.6 for men, 80.8 for women (http://en.wikipedia.org/wiki/List_of_countries_by_life_expectancy)
            //for the .6 and .8 we just guestimate based on 60 and 80% of 365
            //date math idea from: http://blog.flexexamples.com/2007/08/24/date-math-for-lazy-people/
            var deathDate:Date = new Date(bDay.time);
            if(selectedGender == 0) {
                deathDate["fullYear"] += 72;
                deathDate["date"] += 219;
            } else {
                deathDate["fullYear"] += 78;
                deathDate["date"] += 292;
            }

            //are you dead already?
            if(deathDate.getTime() &lt; now.getTime()) {            
                errorDiv.text = 'Sorry, but you are already dead. Have a nice day.';
                return;
            }

            var timeLeft:Number = Math.round((deathDate.time - now.time)/1000);
            trace('death is '+deathDate.toString()+ ' v='+deathDate.time);
            trace('Now is ' +now.toString()+ ' v='+now.time);
            trace('diff is '+timeLeft);
            //trace(bDay.toString()+'\n'+deathDate.toString()+'\n'+timeLeft.toString());

            navigator.pushView(Counter,{deathDate:deathDate,timeLeft:timeLeft});

        }
    ]]&gt;
&lt;/fx:Script&gt;

&lt;s:states&gt;
    &lt;s:State name="normal"/&gt;
    &lt;s:State name="selectedMonth"/&gt;
    &lt;s:State name="selectedGender"/&gt;
&lt;/s:states&gt;

&lt;s:layout&gt;
    &lt;s:VerticalLayout paddingTop="10" paddingLeft="5" paddingRight="5" /&gt;
&lt;/s:layout&gt;

&lt;!--
The idea of button groups to handle drop downs is from Dirk Eismann (DEismann@herrlich-ramuschkat.de)
--&gt;
&lt;s:HGroup width="100%" height.selectedGender="100%"&gt;
    &lt;s:Label text="Gender: " width="50%" /&gt;
    &lt;s:Button id="genderButton" label="Male" click="toggleGenderState()" includeIn="normal,selectedMonth" /&gt;
    &lt;s:Scroller includeIn="selectedGender" height="100%" width="100%"&gt;            
        &lt;s:Group&gt;         
            &lt;s:layout&gt;
                &lt;s:VerticalLayout/&gt;
            &lt;/s:layout&gt;
            &lt;s:RadioButton label="Male" value="0" selected="true" groupName="genderRBG"/&gt;
            &lt;s:RadioButton label="Female" value="1" groupName="genderRBG"/&gt;
        &lt;/s:Group&gt;
    &lt;/s:Scroller&gt;

&lt;/s:HGroup&gt;

&lt;s:HGroup width="100%" height.selectedMonth="100%"&gt;
    &lt;s:Label text="Month Born: " width="50%" /&gt;
    &lt;s:Button id="monthButton" label="January" click="toggleMonthState()" includeIn="normal,selectedGender" /&gt;
    &lt;s:Scroller includeIn="selectedMonth" height="100%" width="100%"&gt;         
        &lt;s:Group &gt;            
            &lt;s:layout&gt;
                &lt;s:VerticalLayout/&gt;
            &lt;/s:layout&gt;
            &lt;s:RadioButton label="January" value="0" selected="true" groupName="monthRBG"/&gt;
            &lt;s:RadioButton label="February" value="1"  groupName="monthRBG"/&gt;
            &lt;s:RadioButton label="March" value="2" groupName="monthRBG"/&gt;
            &lt;s:RadioButton label="April" value="3" groupName="monthRBG"/&gt;
            &lt;s:RadioButton label="May" value="4" groupName="monthRBG"/&gt;
            &lt;s:RadioButton label="June" value="5" groupName="monthRBG"/&gt;
            &lt;s:RadioButton label="July" value="6" groupName="monthRBG"/&gt;
            &lt;s:RadioButton label="August" value="7" groupName="monthRBG"/&gt;
            &lt;s:RadioButton label="September" value="8" groupName="monthRBG"/&gt;
            &lt;s:RadioButton label="October" value="9" groupName="monthRBG"/&gt;
            &lt;s:RadioButton label="November" value="10" groupName="monthRBG"/&gt;
            &lt;s:RadioButton label="December" value="11" groupName="monthRBG"/&gt;
        &lt;/s:Group&gt;
    &lt;/s:Scroller&gt;

&lt;/s:HGroup&gt;

&lt;s:HGroup width="100%"&gt;
    &lt;s:Label text="Day Born: " width="50%" /&gt;
    &lt;s:TextInput id="dayBorn" width="50%" text="1" /&gt;
&lt;/s:HGroup&gt;

&lt;s:HGroup width="100%"&gt;
    &lt;s:Label text="Year Born: " width="50%" /&gt;
    &lt;s:TextInput id="yearBorn" width="50%" text="1973" /&gt;
&lt;/s:HGroup&gt;

&lt;s:Button id="runClockBtn" label="Begin" width="100%" bottom="5" click="doClock()" /&gt;

&lt;s:Label id="errorDiv" width="100%" /&gt;

</s:View>

That’s right at 190 lines. Not bad for my first Flex mobile application. Now let’s look at the new version.

<?xml version=“1.0” encoding=“utf-8”?> <s:View xmlns:fx=“http://ns.adobe.com/mxml/2009" xmlns:s=“library://ns.adobe.com/flex/spark” title=“Death Clock” backgroundColor=“0x000000” xmlns:mx=“library://ns.adobe.com/flex/mx”> <fx:Declarations>

&lt;/fx:Declarations&gt;

&lt;fx:Style&gt;
@namespace s "library://ns.adobe.com/flex/spark";
@namespace mx "library://ns.adobe.com/flex/mx";

#errorDiv {
    font-weight: bold;
    color: red;
}
&lt;/fx:Style&gt;


&lt;fx:Script&gt;
&lt;![CDATA[
    import mx.collections.ArrayCollection;

    private function doClock():void {

        var bDay:Date = dob.selectedDate;

        var now:Date = new Date();

        //Life expectancy will be 75.6 for men, 80.8 for women (http://en.wikipedia.org/wiki/List_of_countries_by_life_expectancy)
        //for the .6 and .8 we just guestimate based on 60 and 80% of 365
        //date math idea from: http://blog.flexexamples.com/2007/08/24/date-math-for-lazy-people/
        var deathDate:Date = new Date(bDay.time);
        if(!selectedGender.selected) {
            deathDate["fullYear"] += 72;
            deathDate["date"] += 219;
        } else {
            deathDate["fullYear"] += 78;
            deathDate["date"] += 292;
        }
        deathDate["fullYear"] += 78;
        deathDate["date"] += 292;

        //are you dead already?
        if(deathDate.getTime() &lt; now.getTime()) {            
            errorDiv.text = 'Sorry, but you are already dead. Have a nice day.';
            return;
        }

        var timeLeft:Number = Math.round((deathDate.time - now.time)/1000);
        trace('death is '+deathDate.toString()+ ' v='+deathDate.time);
        trace('Now is ' +now.toString()+ ' v='+now.time);
        trace('diff is '+timeLeft);
        //trace(bDay.toString()+'\n'+deathDate.toString()+'\n'+timeLeft.toString());

        navigator.pushView(Counter,{deathDate:deathDate,timeLeft:timeLeft});
    }
]]&gt;
&lt;/fx:Script&gt;

&lt;s:layout&gt;
    &lt;s:VerticalLayout paddingTop="10" paddingLeft="5" paddingRight="5" /&gt;
&lt;/s:layout&gt;

&lt;s:HGroup width="100%"&gt;
    &lt;s:Label text="Gender: " width="30%" /&gt;
    &lt;s:ToggleSwitch id="selectedGender" skinClass="skins.GenderToggleSkin" width="70%"  /&gt;
&lt;/s:HGroup&gt;

&lt;s:HGroup width="100%"&gt;
    &lt;s:Label text="Birth Date:" width="30%"  /&gt;
    &lt;s:DateSpinner id="dob" width="70%" /&gt;
&lt;/s:HGroup&gt;

&lt;s:Button id="runClockBtn" label="Begin" width="100%" bottom="5" click="doClock()" /&gt;

&lt;s:Label id="errorDiv" width="100%" /&gt;

</s:View>

78 lines. Bit nicer, right? Here’s a screen shot showing the new UI controls.

I just need to change that “Begin” button to a skull or something and it will be perfect.

Like This?

If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can also subscribe to the email feed to get notified of new posts.