Twitter: raymondcamden


Address: Lafayette, LA, USA

Updating Death Clock for Flex 4.6

10-21-2011 7,507 views Mobile, Flex

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:

view plain print about
1<?xml version="1.0" encoding="utf-8"?>
2<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
3        xmlns:s="library://ns.adobe.com/flex/spark" title="Death Clock" initialize="init()" xmlns:mx="library://ns.adobe.com/flex/mx" backgroundColor="0x000000">

4    <fx:Declarations>
5        <mx:NumberValidator id="dayValidator" source="{dayBorn}" property="text" allowNegative="false" minValue="1" maxValue="31"
6                            invalidCharError="Day Born must be a number." integerError="Day Born must be a number." negativeError="Day Born must be a positive number."
7                            lowerThanMinError="Day Born must be over 0." exceedsMaxError="Day Born must be lower than 31."/
>

8        
9        <mx:NumberValidator id="yearValidator" source="{yearBorn}" property="text" allowNegative="false" minValue="1900" maxValue="2020"
10                            invalidCharError="Year Born must be a number." integerError="Year Born must be a number." negativeError="Year Born must be a positive number."
11                            lowerThanMinError="Year Born must be over 1900." exceedsMaxError="Year Born must be lower than 2020." /
>

12        
13        <s:RadioButtonGroup id="monthRBG" change="setMonthLabel()"/>
14        <s:RadioButtonGroup id="genderRBG" change="setGenderLabel()"/>
15    </fx:Declarations>
16    
17    <fx:Style>
18        @namespace s "library://ns.adobe.com/flex/spark";
19        @namespace mx "library://ns.adobe.com/flex/mx";
20        
21        #errorDiv {
22            font-weight: bold;
23            color: red;
24        }
25    </fx:Style>
26    
27    <fx:Script>
28        <![CDATA[
29            import mx.collections.ArrayCollection;
30            import mx.events.ValidationResultEvent;
31            
32            [Bindable] private var months:ArrayCollection;        
33            [Bindable] private var days:ArrayCollection;
34            [Bindable] private var years:ArrayCollection;
35            
36            private var selectedMonth:int;
37            private var selectedGender:int;
38            
39            private function setGenderLabel():void {
40                if(genderRBG.selection) {
41                    genderButton.label = genderRBG.selection.label;
42                    selectedGender = int(genderRBG.selection.value);
43                }
44                toggleGenderState();
45            }
46            
47            private function setMonthLabel():void {
48                if(monthRBG.selection) {
49                    monthButton.label = monthRBG.selection.label;
50                    selectedMonth = int(monthRBG.selection.value);
51                }
52                toggleMonthState();
53            }
54            
55            private function toggleGenderState():void {
56                if(this.currentState == 'normal') this.currentState='selectedGender';        
57                else this.currentState='normal';
58            }
59            
60            private function toggleMonthState():void {
61                if(this.currentState == 'normal') this.currentState='selectedMonth';        
62                else this.currentState='normal';
63            }
64            
65            private function init():void {
66                this.currentState = 'normal';
67                
68            }
69            
70            private function doClock():void {
71                
72                var validation:ValidationResultEvent;
73                validation = dayValidator.validate();
74                if(validation.type == "invalid") {
75                    errorDiv.text = validation.message;
76                    return;            
77                }
78                validation = yearValidator.validate();
79                if(validation.type == "invalid") {
80                    errorDiv.text = validation.message;
81                    return;            
82                }
83                
84                errorDiv.text = '';
85                
86                var bDay:Date = new Date();
87                bDay.fullYear = int(yearBorn.text);
88                
89                bDay.month = selectedMonth;
90                bDay.date = int(dayBorn.text);
91                
92                var now:Date = new Date();
93                
94                //Life expectancy will be 75.6 for men, 80.8 for women (http://en.wikipedia.org/wiki/List_of_countries_by_life_expectancy)
95                //for the .6 and .8 we just guestimate based on 60 and 80% of 365
96                //date math idea from: http://blog.flexexamples.com/2007/08/24/date-math-for-lazy-people/
97                var deathDate:Date = new Date(bDay.time);
98                if(selectedGender == 0) {
99                    deathDate["fullYear"] += 72;
100                    deathDate["date"] += 219;
101                } else {
102                    deathDate["fullYear"] += 78;
103                    deathDate["date"] += 292;
104                }
105                
106                //are you dead already?
107                if(deathDate.getTime() < now.getTime()) {            
108                    errorDiv.text = 'Sorry, but you are already dead. Have a nice day.';
109                    return;
110                }
111                
112                var timeLeft:Number = Math.round((deathDate.time - now.time)/1000);
113                trace('death is '+deathDate.toString()+ ' v='+deathDate.time);
114                trace('Now is ' +now.toString()+ ' v='+now.time);
115                trace('diff is '+timeLeft);
116                //trace(bDay.toString()+'\n'+deathDate.toString()+'\n'+timeLeft.toString());
117                
118                navigator.pushView(Counter,{deathDate:deathDate,timeLeft:timeLeft});
119                
120            }
121        ]]>

122    </fx:Script>
123    
124    <s:states>
125        <s:State name="normal"/>
126        <s:State name="selectedMonth"/>
127        <s:State name="selectedGender"/>
128    </s:states>
129    
130    <s:layout>
131        <s:VerticalLayout paddingTop="10" paddingLeft="5" paddingRight="5" />
132    </s:layout>
133    
134    <!--
135    The idea of button groups to handle drop downs is from Dirk Eismann (DEismann@herrlich-ramuschkat.de)
136    -->

137    <s:HGroup width="100%" height.selectedGender="100%">
138        <s:Label text="Gender: " width="50%" />
139        <s:Button id="genderButton" label="Male" click="toggleGenderState()" includeIn="normal,selectedMonth" />
140        <s:Scroller includeIn="selectedGender" height="100%" width="100%">            
141            <s:Group>            
142                <s:layout>
143                    <s:VerticalLayout/>
144                </s:layout>
145                <s:RadioButton label="Male" value="0" selected="true" groupName="genderRBG"/>
146                <s:RadioButton label="Female" value="1" groupName="genderRBG"/>
147            </s:Group>
148        </s:Scroller>
149        
150    </s:HGroup>
151    
152    <s:HGroup width="100%" height.selectedMonth="100%">
153        <s:Label text="Month Born: " width="50%" />
154        <s:Button id="monthButton" label="January" click="toggleMonthState()" includeIn="normal,selectedGender" />
155        <s:Scroller includeIn="selectedMonth" height="100%" width="100%">            
156            <s:Group >            
157                <s:layout>
158                    <s:VerticalLayout/>
159                </s:layout>
160                <s:RadioButton label="January" value="0" selected="true" groupName="monthRBG"/>
161                <s:RadioButton label="February" value="1" groupName="monthRBG"/>
162                <s:RadioButton label="March" value="2" groupName="monthRBG"/>
163                <s:RadioButton label="April" value="3" groupName="monthRBG"/>
164                <s:RadioButton label="May" value="4" groupName="monthRBG"/>
165                <s:RadioButton label="June" value="5" groupName="monthRBG"/>
166                <s:RadioButton label="July" value="6" groupName="monthRBG"/>
167                <s:RadioButton label="August" value="7" groupName="monthRBG"/>
168                <s:RadioButton label="September" value="8" groupName="monthRBG"/>
169                <s:RadioButton label="October" value="9" groupName="monthRBG"/>
170                <s:RadioButton label="November" value="10" groupName="monthRBG"/>
171                <s:RadioButton label="December" value="11" groupName="monthRBG"/>
172            </s:Group>
173        </s:Scroller>
174        
175    </s:HGroup>
176    
177    <s:HGroup width="100%">
178        <s:Label text="Day Born: " width="50%" />
179        <s:TextInput id="dayBorn" width="50%" text="1" />
180    </s:HGroup>
181    
182    <s:HGroup width="100%">
183        <s:Label text="Year Born: " width="50%" />
184        <s:TextInput id="yearBorn" width="50%" text="1973" />
185    </s:HGroup>
186    
187    <s:Button id="runClockBtn" label="Begin" width="100%" bottom="5" click="doClock()" />
188    
189    <s:Label id="errorDiv" width="100%" />
190</s:View>

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

view plain print about
1<?xml version="1.0" encoding="utf-8"?>
2<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
3        xmlns:s="library://ns.adobe.com/flex/spark" title="Death Clock" backgroundColor="0x000000" xmlns:mx="library://ns.adobe.com/flex/mx">

4    <fx:Declarations>
5                
6    </fx:Declarations>
7
8    <fx:Style>
9    @namespace s "library://ns.adobe.com/flex/spark";
10    @namespace mx "library://ns.adobe.com/flex/mx";
11    
12    #errorDiv {
13        font-weight: bold;
14        color: red;
15    }
16    </fx:Style>
17
18    
19    <fx:Script>
20    <![CDATA[
21        import mx.collections.ArrayCollection;
22                    
23        private function doClock():void {
24                
25            var bDay:Date = dob.selectedDate;
26            
27            var now:Date = new Date();
28                
29            //Life expectancy will be 75.6 for men, 80.8 for women (http://en.wikipedia.org/wiki/List_of_countries_by_life_expectancy)
30            //for the .6 and .8 we just guestimate based on 60 and 80% of 365
31            //date math idea from: http://blog.flexexamples.com/2007/08/24/date-math-for-lazy-people/
32            var deathDate:Date = new Date(bDay.time);
33            if(!selectedGender.selected) {
34                deathDate["fullYear"] += 72;
35                deathDate["date"] += 219;
36            } else {
37                deathDate["fullYear"] += 78;
38                deathDate["date"] += 292;
39            }
40            deathDate["fullYear"] += 78;
41            deathDate["date"] += 292;
42
43            //are you dead already?
44            if(deathDate.getTime() < now.getTime()) {            
45                errorDiv.text = 'Sorry, but you are already dead. Have a nice day.';
46                return;
47            }
48                
49            var timeLeft:Number = Math.round((deathDate.time - now.time)/1000);
50            trace('death is '+deathDate.toString()+ ' v='+deathDate.time);
51            trace('Now is ' +now.toString()+ ' v='+now.time);
52            trace('diff is '+timeLeft);
53            //trace(bDay.toString()+'\n'+deathDate.toString()+'\n'+timeLeft.toString());
54            
55            navigator.pushView(Counter,{deathDate:deathDate,timeLeft:timeLeft});
56        }
57    ]]>

58    </fx:Script>
59    
60    <s:layout>
61        <s:VerticalLayout paddingTop="10" paddingLeft="5" paddingRight="5" />
62    </s:layout>
63
64    <s:HGroup width="100%">
65        <s:Label text="Gender: " width="30%" />
66        <s:ToggleSwitch id="selectedGender" skinClass="skins.GenderToggleSkin" width="70%" />
67    </s:HGroup>
68
69    <s:HGroup width="100%">
70        <s:Label text="Birth Date:" width="30%" />
71        <s:DateSpinner id="dob" width="70%" />
72    </s:HGroup>
73    
74    <s:Button id="runClockBtn" label="Begin" width="100%" bottom="5" click="doClock()" />
75    
76    <s:Label id="errorDiv" width="100%" />
77
78</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.