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."/>

<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." />

<s:RadioButtonGroup id="monthRBG" change="setMonthLabel()"/>
<s:RadioButtonGroup id="genderRBG" change="setGenderLabel()"/>
</fx:Declarations>

<fx:Style>
@namespace s "library://ns.adobe.com/flex/spark";
@namespace mx "library://ns.adobe.com/flex/mx";

#errorDiv {
font-weight: bold;
color: red;
}
</fx:Style>

<fx:Script>
<![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() < 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});

}
]]>
</fx:Script>

<s:states>
<s:State name="normal"/>
<s:State name="selectedMonth"/>
<s:State name="selectedGender"/>
</s:states>

<s:layout>
<s:VerticalLayout paddingTop="10" paddingLeft="5" paddingRight="5" />
</s:layout>

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

</s:HGroup>

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

</s:HGroup>

<s:HGroup width="100%">
<s:Label text="Day Born: " width="50%" />
<s:TextInput id="dayBorn" width="50%" text="1" />
</s:HGroup>

<s:HGroup width="100%">
<s:Label text="Year Born: " width="50%" />
<s:TextInput id="yearBorn" width="50%" text="1973" />
</s:HGroup>

<s:Button id="runClockBtn" label="Begin" width="100%" bottom="5" click="doClock()" />

<s:Label id="errorDiv" width="100%" />
</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>

</fx:Declarations>

<fx:Style>
@namespace s "library://ns.adobe.com/flex/spark";
@namespace mx "library://ns.adobe.com/flex/mx";

#errorDiv {
font-weight: bold;
color: red;
}
</fx:Style>

<fx:Script>
<![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() < 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});
}
]]>
</fx:Script>

<s:layout>
<s:VerticalLayout paddingTop="10" paddingLeft="5" paddingRight="5" />
</s:layout>

<s:HGroup width="100%">
<s:Label text="Gender: " width="30%" />
<s:ToggleSwitch id="selectedGender" skinClass="skins.GenderToggleSkin" width="70%" />
</s:HGroup>

<s:HGroup width="100%">
<s:Label text="Birth Date:" width="30%" />
<s:DateSpinner id="dob" width="70%" />
</s:HGroup>

<s:Button id="runClockBtn" label="Begin" width="100%" bottom="5" click="doClock()" />

<s:Label id="errorDiv" width="100%" />

</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.