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.