Twitter: raymondcamden


Address: Lafayette, LA, USA

Updating Death Clock for Flex 4.6

10-21-2011 7,307 views Mobile, Flex 4 Comments

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.

4 Comments

  • Commented on 10-21-2011 at 3:01 PM
    Ray, one question: since the Flex/AIR stuff is packaged into the .apk file, does that mean you could deploy Android apps based on 4.6 code today (as in, there's nothing that needs to be updated on the handset/Android OS side for the 4.6 stuff to work)?
  • Commented on 10-21-2011 at 3:14 PM
    My understanding is that the AIR stuff can be packaged in, not "is" packaged in. So I may be able to ship this now. Still gonna wait though.
  • Richard Turner-Jones #
    Commented on 10-21-2011 at 6:04 PM
    Windows "wingdings" font capital N is a skull and cross bones.
    Type it in Fireworks, under the Text menu select "Convert to Paths" and there you have a vector skull.
  • Commented on 10-22-2011 at 11:45 AM
    omg that's cool. Never used Fireworks believe it or not. ;)

Post Reply

Please refrain from posting large blocks of code as a comment. Use Pastebin or Gists instead. Text wrapped in asterisks (*) will be bold and text wrapped in underscores (_) will be italicized.

Leave this field empty