Column charts with ticks

After having some issues with the Flex Forum due to spamming (not me), I went back on to answer a few quick questions. A question about creating tick lines between the interval lines on a column chart was followed up with setting the number of tick lines between the intervals. The first part of adding tick lines is fairly straight forward. When you create a ColumnChart and add LinearAxis, you can set the minorInterval value, which creates the ticks between the main intervals. After that is set, you can add an AxisRenderer to the verticalAxisRender to adjust what the tick looks like.

<mx:verticalAxisRenderers>
	<mx:AxisRenderer axis="{countAxis}" minorTickPlacement="inside" minorTickStroke="{s1}" minorTickLength="330" />
</mx:verticalAxisRenderers>

As part of the question, the user wanted to create grid lines, which to me was to stretch the tick line across the entire chart. In order to have the tick lines only show up on the chart, you need to set the placement to “inside” and set the length. Unfortunately, you can’t simply get the width of the chart and set the length of the tick line, because the chart also includes any legends and interval numbers. Also, the tick line is white, so if you want it to match the grid lines, you need to create a stroke and set that value.

Finally, the user asked if the number of tick lines could be manipulated. What I found was that the chart would take the main interval value and divided it by the minor interval value in order to show the number of ticks, including the tick at the interval value. So, I came up with the simple formula to determine the minorInterval value based on a users selection. I added the value to a change function for a droplist of 1-9. The value 0 doesn’t work, because once you set it to show the minorInterval, it will always show something.

Minor Interval Value = Major Interval Value / (Viewable Tick Count + 1)

protected function tickViewList_changeHandler(event:IndexChangeEvent):void
{
	var tickCount:int = tickViewList.selectedItem;
	var tickView:Number = countAxis.interval / (tickCount + 1);
	countAxis.minorInterval = tickView;
}

View Source

Upgrading to Apache Flex

Since I don’t keep as up-to-date with tools at work as I would like to, it came as a little bit of a surprise that Adobe had given Flex over to the Apache foundation as Apache Flex.

2011: Adobe decides to contribute Flex to the ASF

The decision is made within Adobe to incubate Flex at the ASF. Work starts on the incubation proposal, on explaining internally what this means, on the legal/IP clearance steps required to donate the code and on recruiting initial committers who will help get the project started.

Obviously, I was a little slow on the uptake. However, things really didn’t get rolling until last year.

December 19, 2012: Apache Flex becomes a top level Apache Software Foundation project
Apache Flex is voted in as a top level project at the December board meeting of the Apache Software Foundation.

And in February, Apache Flex released its first true new version of Flex with Flex 4.9. The last true version of Adobe Flex was 4.7. The Apache 4.8 version was just a migration of the project and basically the same as 4.7. Since I’ve been busy learning the new spark components in 4.6, I wanted to make sure things worked the same in Apache Flex 4.9. The install of Apache Flex was fairly simple. They have installer applications to make things simple and it connected fine to my version of Flash Builder 4.0. All I had to do was click on the application Properties and change the Flex Compiler to Apache Flex 4.9. Everything compiled and ran as expected.

I hope that Apache does well with Flex and it continues to grow and expand. I’m trying to learn how to create a BB10 application using Adobe AIR, which is supported by the Apache Flex, just like Adobe Flex did.

Color name to uint value

Filing under the “why didn’t I find this before” folder. On the Adobe Flex forums, there was a question about changing the color value of a button based on a switch statement. The user was trying to change the value to “Red”, “Yellow” or “Green”. In the button, the type for color is uint value, so the input needs to be something like “0×990000″ for “Red”. I usually just insert in the uint value for a color and not the name, since I usually have to change it. But, this question on the forum got me thinking and searching for an answer. The simple answer is the IStyleManager2, which allows you to get the uint value of a color name. Presto!

Here’s the reply to the user’s question that I have been attempting to post to the forum.


First set a declaration of the variable. I am setting the default value to Black in uint value.

<fx:Declarations> <fx:uint id="batteryStatusColor">0x000000</fx:uint></fx:Declarations>

This will allow you to bind it to your button parameter.

<s:Button id="Battery" x="791" y="513" width="124" height="46" 
label="Battery" fontSize="20" color="{batteryStatusColor}" />

Then, in your function, make sure you convert the String value of the color to a uint

private function changeBatteryStatusColor(batteryStatus:int):void {
	var tmpColorString:String = "Black";
	switch(batteryStatus)
	{
		case 1:                                                                                                                 
			tmpColorString = "Red";                                                                                          
			break;
		case 2:                                                                                                                 
			tmpColorString = "Green";
			break;
		default:                                                                                                                 
			tmpColorString = "Yellow";                                                                                                    
			break;                   	
	}   
	
	var newStyleManger:IStyleManager2 = StyleManager.getStyleManager(this.moduleFactory);	
	batteryStatusColor = newStyleManger.getColorName(tmpColorString);
}

The key part is the IStyleManager call that converts the String value of the color to a uint.

Continuos Vibrate and Music Alert until Confirmation : update

I had recently thought I solved my issue with creating a continuous vibrate and music alert for the BlackBerry application that I created for work. Unfortunately, when I put the fix on to the phone, the volume level of the music was very low, even though I had the volume set to 100%. So, it was back to the drawing board to try and figure out how to get this working. The main issue was determining if the music had finished and then starting it again until the user pressed a key.

After messing around with NotificationsEngineListener, Event and the Notifications Demo for what seemed like forever and continuing to get thread errors and other issues, it was a simple step back and regroup that helped me see the light. Instead of trying to kick off an event and a listener for the Notification, all I had to do was immediately kick off the Notification.

NotificationsManager.triggerImmediateEvent(bbhAttribute.NOTIFICATION_MESSAGEPAGE, 0, null, null);

I was already checking to see when the music had finished to determine if I needed to stop or continue. So, the simple step was to put the triggerImmediateEvent in the check.

public void audioDone(int reason)
{
	System.out.println("audioDone function");
	
	switch(reason) {
		case REASON_KEY_PRESSED:
			//System.out.println("Key Pressed.  Stopping Alert");
			stopAlert();
			break;
		case  REASON_COMPLETED:
			//System.out.println("Completed Called.  Stopping Alert");
			NotificationsManager.triggerImmediateEvent(bbhAttribute.NOTIFICATION_MESSAGEPAGE, 0, null, null);
			continueAlert();
			break;
		case REASON_STOP_CALLED:
			//System.out.println("Stop Called.  Do Nothing.");
			// Do nothing
			break;
	}
}

Which basically means, the initial music and vibrate will play and then when they are done and the user hasn’t clicked a button to stop it, the Notification will start before the music and vibrate plays again.

So far, this seems to be the working solution. I hope this is the final solution, because it has been a headache. I hope the BB10 method of notify users has something simple for continuous notification.

DataGrid Row Move

Another question on the Flex Forum about how to move a selected row on the DataGrid up or down, based on a button click.  The answer is straight forward, but with a catch.

When you click on the DataGrid, it will give you an index for the row you have clicked.  The first row in the DataGrid is 0.  If you wanted to simply move a row up and down the DataGrid, then you simply add or subtract 1 from the selected index.


// Move Item Up

SelectedIndex--;

// Move Item Down

SelectedIndex++;

Simple enough.  Unless you sort the DataGrid, which throws that out of wack and is pointless.  So, if you don’t plan on sorting the data, then adding a couple of buttons to move an item up and down within the DataGrid and the ArrayCollection provider, then here is the code.  It also includes checks to make sure you don’t move the item off ofthe grid.

Code for the Move Up Button


protected function moveUpBtn_clickHandler(event:MouseEvent):void
{
var upSelectedIndex:int = myDataGrid.selectedIndex;

// Move Item
if(upSelectedIndex > -1) {
if(upSelectedIndex > 0) {
// Get the selected Object
var moveObject:Object = myArrayCollection.getItemAt(upSelectedIndex);
// Get actual Object location if grid is sorted
var upActualIndex:int = myArrayCollection.getItemIndex(moveObject);
// Remove selected item at actualIndex
myArrayCollection.removeItemAt(upActualIndex);
myArrayCollection.refresh();
// Move both indexes
upActualIndex--;
upSelectedIndex--;
// insert item at actualIndex
myDataGrid.dataProvider.addItemAt(moveObject, upActualIndex);
myArrayCollection.refresh();
// select grid at selectedIndex
myDataGrid.selectedIndex = upSelectedIndex;
} else {
Alert.show("Item is currently at the top of the list.", "Move Alert",  Alert.OK, this);
}
} else {
Alert.show("No Item Selected.", "Move Alert",  Alert.OK, this);
}

}

Code for the Move Down Button


protected function moveDownBtn_clickHandler(event:MouseEvent):void
{
var downSelectedIndex:int = myDataGrid.selectedIndex;

if(downSelectedIndex > -1) {
// Move Item
if(downSelectedIndex < myArrayCollection.length -1) {
// Get the selected Object
var moveObject:Object = myArrayCollection.getItemAt(downSelectedIndex);
// Get actual Object location if grid is sorted
var downActualIndex:int = myArrayCollection.getItemIndex(moveObject);
// Remove selected item at actualIndex
myArrayCollection.removeItemAt(downActualIndex);
myArrayCollection.refresh();
// Move both indexes
downActualIndex++;
downSelectedIndex++;
// insert item at actualIndex
myDataGrid.dataProvider.addItemAt(moveObject, downActualIndex);
myArrayCollection.refresh();
// select grid at selectedIndex
myDataGrid.selectedIndex = downSelectedIndex;
} else {
Alert.show("Item is currently at the bottom of the list.", "Move Alert",  Alert.OK, this);
}
} else {
Alert.show("No Item Selected.", "Move Alert",  Alert.OK, this);
}

}

I did modify the DataGrid, so that the selectioMode is “singleRow”, which simplifies the selectedIndex information. I also set the selectedIndex for the row after the move, so the user can continue to move the row up and down and not have to select it again. Here is the example and source.

UPDATE: Had to set the Down Move to look for the Length of the ArrayCollection -1, because the DataGrid row starts at 0, it is one less than the total length of the ArrayCollection.

View Source

BB10 AIR Getting Started Completely

I have been meaning to get started on some BlackBerry 10 development, so I loaded the SDK for AIR and Flash Builder 4.6. With Flash Builder 4.6, you are supposedly able to create an iOS, Android or BlackBerry (tablet and BB10) application all at the same time. However, when you build for the least common denominator, you lose functionality for a specific platform. Because of this (and that I really don’t need to develop for iOS or Android at the moment) I started an application only for the BlackBerry.

While attempting to install VMWare for the virtual machine that allows for running the device simulators, I ran in to an install error. Apparently the key to this was to stop the installation right after you start, before it asks you to begin the final step and the actual install. At this point, the temporary files are extracted, which you should move to a new temporary folder location. When the files are there, stop the first install and then install using the msi file in the new location. I would give a screen shot of the point to stop the install, but I don’t want to uninstall it to get back to that point.

The good news with the BB10 simulators, is that I don’t have to configure a virtual machine, unlike how I had to do with the PlayBook simulator. Instead, I just go to the BlackBerry 10 Simulator program and run it to display the virtual machine simulator. Once it is running, then the Flash Builder can detect it so it can be set as one of options for debugging or playing. My only gripe is, the virtual machine image is much larger than real life and my normal display. I had to turn one of my monitors in a portrait position to run the simulators without the need for scrolling.

If I wanted to create a basic application, then I could have continued on my way with a View Navigator and such. However, I wanted to make sure I was utilizing all the features I could, which means using the native extensions and libraries. In the documentation for Configuring AIR, they show how to add the native extensions and that is pretty straight forward.

AIR setup Native Extensions

I did this and moved on. But, I wasn’t getting anywhere on how to create my screens. So, I imported the Hello World application located in samples directory of the installed SDK. When I did the import, it was full of errors that it couldn’t find qnx libraries. After making sure I did everything in the instructions, I did a search trying to find out why the library was missing. I found something on the support forum about looking for the correct path of the swc files. Not knowing if they libraries were setup correctly, I removed the ones that were there and then added the ones I could find under the frameworks.

AIR_setup_2

That’s all it took…..besides correcting the old library paths with new library paths changed since version 1 of the SDK.  I probably won’t need all the libraries, but it doesn’t hurt to have them available at the start.

And so begins the road of banging my head on my desk trying to figure out what is NOT documented for creating an application.

Column Chart with Item Renderer – updated

Okay, it didn’t take me long to get back and do some more fiddling with the Column Chart Item Renderer. I went ahead and changed the static ArrayCollection to a dynamic one, because I was trying to figure out something with Themes and needed a button and the button should do something. The ArrayCollection is fairly simple for this chart, so all I did was create a couple of random values from 0 to 100 and made the “date” value the counter time 10. I set it up so that you can press a button and create new data, which is helpful to see the changes to the columns.

// Create some random data for Yesterday and blank data for today
private function dataCreation():void {
	currentArray.removeAll();

	for(var h:int = 0; h < 4; h++) {
		// Generate Random Data
		var randomClose:Number = Math.floor(Math.random() * 100);
		var randomOpen:Number = Math.floor(Math.random() * 100);
		// create a temporary object and add the three values
		var tmpObject:Object = new Object();
		tmpObject.date = (h + 1) * 10;
		tmpObject.close = randomClose;
		tmpObject.open = randomOpen;

		// insert new tmpObject
		currentArray.addItem(tmpObject);
	}

	// Refresh the data
	currentArray.refresh();
}

After that was fixed, I went ahead to modify the ItemRenderer again, but this time to use a GradientFill and a BitMapFill. I had to reduce what was defined for the rectangle, to just a Graphic item as a holder.

s:Graphic id="graphicHolder" />

And then I declared three different rectangles, one for each type of fill. Again, this was to simplify things and also to make sure I was doing the right thing for each fill. As you can see in the Gradient Fill, I had to set the three GradientEntry items to create the look I need. You could do this all dynamically, but I don’t think most people will try different Gradients for each column. Which is why my Gradient fill only changes the Start, Mid and End color of the Gradient.

<fx:Declarations>
	<s:Rect id="imageRect">
		<s:stroke>
			<s:SolidColorStroke id="rectImageStroke" />
		</s:stroke>
		<s:fill>
			<s:BitmapFill id="imageRectFill" fillMode="repeat" scaleX=".70" scaleY=".70" smooth="true" />
		</s:fill>
	</s:Rect>

	<s:Rect id="gradientRect">
		<s:stroke>
			<s:SolidColorStroke id="rectLinGradStroke" />
		</s:stroke>
		<s:fill>
			<s:LinearGradient id="rectLinGrad">
				<s:entries>
					<s:GradientEntry id="linGradColorStart" ratio="0" alpha=".5" />
					<s:GradientEntry id="linGradColorMid" ratio=".25" alpha=".5" />
					<s:GradientEntry id="linGradColorEnd" ratio=".66" alpha=".5" />
				</s:entries>
			</s:LinearGradient>
		</s:fill>
	</s:Rect>

	<s:Rect id="solidRect">
		<s:stroke>
			<s:SolidColorStroke id="rectSolidStroke" />
		</s:stroke>
		<s:fill>
			<s:SolidColor id="rectSolidColor" />
		</s:fill>
	</s:Rect>	
</fx:Declarations>

From here, it was just setting the color for the Gradients and the appropriate image for the BitMap Fills. Except that it took me a while to figure out the correct syntax for the Image. I couldn’t declare it. I couldn’t just Embed it as the source. I had to Embed it as a Bindable Class and then set the BitmapAsset to that Class and finally make it the source for the BitMapFill.

// Embed the Red Apple Image
[Embed(source="../assets/images/red-apple.png")]
[Bindable]
public var imgRedApple:Class;

// Red Image Fill
var imgRedAppleObj:BitmapAsset = new imgRedApple() as BitmapAsset;
imageRectFill.source = imgRedAppleObj;

I simplified the code so that different columns do only two fills. I had columns doing all three fills, but decided that was too much code and could get really confusing. With the example below;

  • If the 1st column has more than 45, then the Red Apple Image Fill is used, else the Red Gradient Fill
  • If the 2nd column has more than 45, then the Blue Gradient Fill is used, else the Blue Solid Fill
  • If the 3rd column has more than 45, then the Green Apple Image Fill is used, else the Green Solid Fill
  • If the 4th column has more than 45, then the Purple Solid Fill is used, else the Purple Gradient Fill

I created a second file for this example, so that the first example and this example could be viewed separately.
View Source

Column Chart with Item Renderer

When doing the Column Chart with fillFunction for a question in the Flex Forum, the poster said that he was trying to do something a little different. Instead of a simple fill, he wanted the fill to be semi-transparent with a solid border. When I looked at the iFill function, it didn’t have a stroke option and it doesn’t seem that the standard column item deals with the column border. My guess was, that an item renderer should be used for the column instead. Within the item render, you can create a rectangle graphic and then adjust the border and fill parameters. Since this seemed like an interesting exercise, I decided to make myself an example (the best way I learn something).

I started out using the simple array data found in other examples. I will probably update this to something more dynamic later.

[Bindable]
public var SMITH:ArrayCollection = new ArrayCollection([{date:10, close:41.87, open: 30},{date:20, close:45.74, open: 40}, 
{date:30, close:42.77, open: 60}, {date:40, close:48.06, open: 50}]);

And then I attached this to a ColumnChart and create a single ColumnSeries with the “date” on the xAxis and the “close” count on the yAxis. I then added the itemRenderer parameter, which popups a window for creating the component.

<mx:ColumnChart id="myColumnChart1" dataProvider="{SMITH}" >
	<mx:horizontalAxis>
		<mx:CategoryAxis categoryField="date" />
	</mx:horizontalAxis>
	<mx:series>
		<mx:ColumnSeries id="columnSeries1" xField="date" yField="close" itemRenderer="components.myColumnRenderer" />
	</mx:series>
</mx:ColumnChart>

In the ItemRender, the way to kick everything off is to override the set data function. You have to check to see if the value is null first, or you get errors when the chart is initially loading. Once there is data in the value, then you can start altering the renderer for the column.

override public function set data(value:Object):void {
	super.data = value;

	// Check to see if the data property is null.
	if (value== null) {
		return;
	} else {
		// data display and other stuff start here
	}
}

To create the background rectangle, you first have to start with a Graphic and then add the Rect and the parts of the Rect that you want to alter. You could just add the Rect and then alter the stroke and fill dynamically, but having them already defined makes it a little easier. I am only doing a Solid stroke and fill, if you wanted to do gradient or mix gradient and solid, then you definitely would want to do that dynamically. The Graphic is first, since it is on the bottom. Anything you want to display above the rectangle graphic goes afterward in the code.

<s:Graphic>
	<s:Rect id="columnRect">
		<s:stroke>
			<s:SolidColorStroke id="rectSolidColorStroke" />
		</s:stroke>
		<s:fill>
			<s:SolidColor id="rectSolidColor" />
		</s:fill>
	</s:Rect>
</s:Graphic>

Once you are ready to alter the column, you have to create the rectangle graphic for the background and you need to set the width and height to the super call for the column. If you don’t do this, then the rectangle is size 0 and you can’t see anything.

columnRect.width = super.width;
columnRect.height = super.height;

Since you are passing in the column data, you can modify the look of each column either by the xValue or the yValue. In this case, I wanted to change each column based on the yValue. I am only doing a simple solid fill and solid stroke, so I only need to change one color. I declared the colors to make it easier to set the uint values. Finally, I adjust the stroke weight and the fill alpha. These could also be changed based on the column data.

switch(value.xValue) {
	case 10:
		// Red Color
		columnFillColor = colorRed;
		columnStrokeColor = colorRed;
		break;
	case 20:
		// Blue Color
		columnFillColor = colorBlue;
		columnStrokeColor = colorBlue;
		break;
	case 30:
		// Green Color
		columnFillColor = colorGreen;
		columnStrokeColor = colorGreen;
		break;
	case 40:
		// Purple Color
		columnFillColor = colorPruple;
		columnStrokeColor = colorPruple;
		break;
}

rectSolidColorStroke.color = columnFillColor;
rectSolidColorStroke.weight = 2;
rectSolidColor.color = columnStrokeColor;
rectSolidColor.alpha = .3;

This example didn’t take very long to make and I will probably try to add some other feature to this simple chart.
View Source

Column Chart with Fill Function

When you have a column chart, sometimes you want to change the column colors based on the data. A question in the Flex Forum asked how to alter the column color on each column. To do this, yo need to use the fillFunction on a ColumnSeries to set the color of the fill. One issue with doing this, is how it affects the chart legend.

If you use the fills property or the fillFunction to define the fills of chart items, and you want a legend, you must manually create the Legend object for that chart.

But, this just gives me the opportunity to test out two different charting options.

I alter the stacked column chart that I created to answer another question on the forum. Since the column chart in this example is created dynamically, I had to add the fillFunction parameter when creating the column series for the green and red apple columns.

columnSeries1.fillFunction = greenAppleFillFunction;

Then I just created a simple function to change the color of the column if the count was less than 50.

private function greenAppleFillFunction(element:ChartItem, index:Number):IFill {
	var item:ColumnSeriesItem = ColumnSeriesItem(element);
	var count:Number = Number(item.yValue);

	if (count < 50) {
		return scFadeGreen;
	} else {
		return scGreen;,
	}
}

To make things easier, I had already defined my SolidColor values, this makes it easier to keep it consistent throughout the chart. In this example, I am only getting the yValue, but you could also get the xValue, if you wanted to change the column colors based on that.

Next I need to update the legend to reflect the new fillFunctions, which is fairly straight forward and I can also use the defined SolidColor parameters.

<mx:Legend  direction="horizontal" >
	<mx:LegendItem label="Red Apples" fill="{scRed}" />
	<mx:LegendItem label="Red under 50" fill="{scFadeRed}" />
	<mx:LegendItem label="Yellow Apples" fill="{scYellow}" />
	<mx:LegendItem label="Green Apples" fill="{scGreen}" />
	<mx:LegendItem label="Green under 50" fill="{scFadeGreen}" />
</mx:Legend>

I also added a Refresh Data button so that the data can be cycled and the column color changes can be seen.
View Source

Continuos Vibrate and Music Alert until Confirmation

The main phone application that I support at work gets data pushed to it. Part of the data is a Message Page to notify the technician that they have a service call assigned or needing attention. Due to the nature of the service calls, the technician needs this alert to continually go off until they acknowledge the alert. A while back I changed the pop up message to a global message, which makes the pop up show on all screens, regardless if the user is in the application.

public static void displayGlobalMessage(final String gMessage)
{
	UiApplication.getUiApplication().invokeLater( new Runnable() {
			public void run() {
				UiEngine uie = Ui.getUiEngine();
				alertDialogListener  closeListener = new alertDialogListener();
				
				Dialog screen = new Dialog(Dialog.D_OK, gMessage, Dialog.OK,
											Bitmap.getPredefinedBitmap(Bitmap.EXCLAMATION),
											Manager.VERTICAL_SCROLL);
				screen.setDialogClosedListener(closeListener);
				uie.pushGlobalScreen(screen, 1, UiEngine.GLOBAL_MODAL);
			}
	});
}

Unfortunately, when I created this option, I some how broke my continuous alert that was previously setup. After many attempts to get it to work again, including using a TimerTask, I finally got the alert to work again.

The first part is creating the AlertListener class that I will use to handled starting the music and vibrate. This was the most important part, because stopping and starting the alert depends on what is to be called. It took a very long time to figure out when to turn off the overall alert (continueAlert) and when to turn on and off the music and vibrate (audioFinished). This is handled in the audioDone function, because the music plays longer than the vibration (which is why nothing is done on vibrateDone and buzzerDone).

public class SDAAlertListener implements AlertListener
{
    private boolean continueAlert;
    private Object continueAlertLock;
    private boolean audioFinished;
    private Object audioFinishedLock;
    
    public SDAAlertListener()
    {
    	//System.out.print("Added Continuos Alert Listener");
        continueAlert = true;
        continueAlertLock = new Object();
        audioFinished = false;
        audioFinishedLock = new Object();
    }
    
    public void startAlert()
    {
    	//System.out.println("Starting Contiuous Alert");
    	SetContinueAlertStatus(true);
    	SetAudioDoneStatus(false); 
    	
    	Alert.startAudio(music.myFogHornTune, 8);
    	Alert.startVibrate(500);
    }
    
    public void continueAlert()
    {
    	//System.out.println("Contiuous Alert to Continue");
    	SetContinueAlertStatus(true);
    	SetAudioDoneStatus(false); 
    	
    	Alert.startAudio(music.myFogHornTune, 8);
    	Alert.startVibrate(500);
    }
    
    public void stopAlert()
    {
    	//System.out.println("Stopping Contiuous Alert");
    	SetAudioDoneStatus(true); 
    	SetContinueAlertStatus(false);
    	
    	Alert.stopAudio();
        Alert.stopBuzzer();
    }
    
    public boolean GetContinueAlertStatus()
    {
        synchronized(continueAlertLock)
        {
            //System.out.println("Getting continueAlertStatus.");
            return continueAlert;
        }
    }
    
    public boolean GetAudioDoneStatus()
    {
        synchronized(audioFinishedLock)
        {
            //System.out.println("Getting buzzerFinished.");
            return audioFinished;
        }
    }
    
    public void SetContinueAlertStatus(boolean value)
    {
    	synchronized(continueAlertLock)
        {
        	//System.out.println("Setting ContinueAlert to " + value);
        	continueAlert = value;
        }
    }
    
    public void SetAudioDoneStatus(boolean value)
    {
        synchronized(audioFinishedLock)
        {
            //System.out.println("Setting audioFinished to " + value);
            audioFinished = value;
        }
    }
    
    public void audioDone(int reason)
    {
    	System.out.println("audioDone function");
    	
    	switch(reason) {
	    	case REASON_KEY_PRESSED:
	    		//System.out.println("Key Pressed.  Stopping Alert");
	    		stopAlert();
	    		break;
	    	case  REASON_COMPLETED:
	    		//System.out.println("Completed Called.  Stopping Alert");
	    		continueAlert();
	    		break;
	    	case REASON_STOP_CALLED:
	    		//System.out.println("Stop Called.  Do Nothing.");
	    		// Do nothing
	    		break;
    	}
    }
    
    public void buzzerDone(int reason)
    {
        // Do nothing.
    }
    
    public void vibrateDone(int reason)
    {
        // Do nothing.
    }
}

The next step is to create an instance of the listener on the main screen, so it is always there. This was one of the issues I was having, because I was adding the listener when the Message Page came in and then removing it after the alert was done. By creating the single listener, I only worry about starting and stopping the music and vibrate.

// Before the main screen is created
public static SDAAlertListener _sdaAlertListener = new SDAAlertListener();
// After the application is created and is only called once
((Application) UiApplication.getUiApplication()).addAlertListener(StartScreen._sdaAlertListener);

Next, I handled the Message Page by sending it to the GlobalMessage display function and creating a loop to check for when the Alert should be played again. The loop checks the Continue Alert Status to see if the button click hasn’t turned it off. Then it checks to see if the audio is done playing. If the Continue Alert is true and the Audio Done is true, then the startAlert function is called again to replay the music and vibration. Otherwise a break stops the loop.

/* Global Dialog display instead of screen pop */
bbhUtil.displayGlobalMessage("New Message Page " + MessagePagingInfo.getCurrentMessage());     
// Start Continuous paging section 
boolean alertStatus = true;
StartScreen._sdaAlertListener.startAlert();
// Start loop 
while (true)
{ 
	alertStatus = StartScreen._sdaAlertListener.GetContinueAlertStatus();
	if(alertStatus)
	{
		if(StartScreen._sdaAlertListener.GetAudioDoneStatus()) {
			//System.out.println("Audio Done Status is True");
			StartScreen._sdaAlertListener.startAlert();
		} else {
			break;
		}
	}
	else
	{ 
		System.out.println("Paging Alert Stop.");
		break;
	}                               
}

Finally, the Global Message display has a close listener attached to it. This listener calls the Alert stop function when the user clicks the “OK” button.

public class alertDialogListener implements DialogClosedListener {
	public void dialogClosed(Dialog dialog, int choice) {
		// Check for OK click and then stop Timer
		//System.out.println("Output Choice: " + choice);
   		 switch (choice) {
   		 	case Dialog.OK : 
   		 		synchronized(UiApplication.getEventLock())
   		 		{
   		 			//System.out.println("Calling Alert Listener Stop");
   		 			StartScreen._sdaAlertListener.stopAlert();
                }
        		break;
            case Dialog.CANCEL : 
            	Status.show("Cancel msg"); 
            	break;
        }
	}
}

This also allows for multiple Message Page alerts, since it turns on and off the same listener. I just wish I hadn’t taken so long in figuring it out.