Mobile Device Development
This article presents you with a mobile phone application. The application is a simple interface to a task management database. It allows you to see what tasks are on the system, their status, and the details of the tasks. This simple application and code is for demonstration purposes only.
Requirements for Development
To develop mobile applications with .NET, you must download and install the following software components:
Windows 2000 Professional/Server/Advanced Server OS
.NET framework Software Development Kit (SDK) Beta 2 (http://www.gotdotnet.com)
.NET Mobile Web SDK Beta 2 (http://www.gotdotnet.com)
Your favorite WAP simulator (The author uses the UP.SDK from http://www.Phone.com)
SQL Server 2000
You also must create a database on SQL Server called "dnaMobileData" and restore the dna01_bkup.dat file to it. If you create a database with a different name, you will have to change the connection string values in the source code.
This article uses slightly different terminology than what you may be used to when referring to ASP.NET Web Forms. This is due to the nature of mobile device development, specifically in the areas of WAP/WML, which is the code generated by this article.
Basically, when referring to a Web Form in mobile device terms, it is called a card because a mobile device web page can have multiple forms or cards. The web page itself is referred to as a deck because it holds a collection of one or more cards.
Sample Application Walkthrough
To begin, a menu is displayed. This menu is actually a list of links used as menu options. Once one of the links is selected, a list of tasks relating to that option is displayed. The task details are presented to the user of the device. All the information is gathered from a SQL Server database and all pages are dynamically generated. That's all there is to it. You will see, however, code examples showing basic presentation, navigation, and data access.
Card 1Default Card
When you access the application for the first time, you see a card that displays a menu. This menu lists the current totals of All Tasks, Tasks Not Started, Tasks In Progress, and Tasks Completed. The totals are generated using a Database query. The first screen of the application is shown in Figure 1.
Figure 1 The first screen of the application
Now that you know what the applications initial card is going to look like, it's time to see the code for it. Listing 1, shows the presentation code for the card:
Listing 1 Presentation code for the card
01 <%--Default card to display on mobile device--%> 02 <Mobile:Form ID="cardHome" RUNAT="server"> 03 <strong> 04 <Mobile:Label RUNAT="server" > 05 Simple Task Manager 06 </Mobile:Label> 07 </strong> 08 <br/> 09 <Mobile:Link RUNAT="server" ID="linkListAll" 10 NavigateUrl="#cardListAll"> </Mobile:link> 11 <Mobile:Link RUNAT="server" ID="linkListNotStarted" 12 NavigateUrl="#cardListNotStarted"> </Mobile:link> 13 <Mobile:Link RUNAT="server" ID="linkListInProgress" 14 NavigateUrl="#cardListInProgress"></Mobile:link> 15 <Mobile:Link RUNAT="server" ID="linkListComplete" 16 NavigateUrl="#cardListComplete"></Mobile:link> 17 </Mobile:Form>
Line 01, is a server-side comment, which is not rendered to the mobile device. It is best to use server-side comments as opposed to client-side HTML comments to keep the size of a card to a minimum. A mobile device has to work with a limited bandwidth connection, which is very rarely much faster than 15k. Line 02 starts the definition of the card. It is recommended that you give your mobile forms an ID because the ID can be used for navigational purposes (more on this and the runat=server element later in the article). The latter is required to ensure that the control is processed server-sidethe same way as a regular ASP.NET Web Form.
On line 04, a Label control is used to display the title of the application. The Label control is used in one of two ways: either to display static text, or to act as a placeholder for static text. On lines 09-17 is a group of Link controls. These controls are used for navigational purposes. Note that the control itself does not have any display text because the control's label text is generated at runtime, using the Page_Load event. The next element of note is the Target element, which can be used to hold the URL of another page, or as done here, the name of a card in the deck being processed (prefixed by a # symbol). The card must exist in the source code file being processed.
The next bit of code to look at is the Page_Load subroutine (see Listing 2). This is called once the mobile device receives the deck of cards from a web server. This subroutine is pretty basic. All it does is call another subroutine that is used to populate all of the controls, for all of the cards in the deckin this case, four Link controls on the first card and a group of List controls, which are on cards later in the application.
Listing 2 Page_Load Subroutine
01 Sub Page_Load(Source As Object, E As EventArgs) 02 GetDataAndBindData() 03 End Sub
Listing 3 is a little long, but it is not as bad as it first seems. Let's see exactly what is being done here.
Listing 3 Binding Data to the Mobile Controls
01 Sub GetDataAndBindData() 02 03 Dim myDataSet As New DataSet() 04 Dim myCommand As SqlDataAdapter 05 Dim myConn As SqlConnection 06 Dim mySQLString As String 07 08 myConn = New SQLConnection("Data Source=localhost;User Id=sa;password=; Initial Catalog=dnaMobileData") 09 10 mySQLString = "SELECT * FROM vwAllUserTasks" 11 myCommand = New SqlDataAdapter(mySQLString, myConn) 12 myCommand.Fill(myDataSet, "All") 13 14 mySQLString = "SELECT * FROM vwAllUserTasks WHERE StatusName='Not Started'" 15 myCommand = New SqlDataAdapter(mySQLString, myConn) 16 myCommand.Fill(myDataSet, "NotStarted") 17 18 mySQLString = "SELECT * FROM vwAllUserTasks WHERE StatusName='In Progress'" 19 myCommand = New SqlDataAdapter(mySQLString, myConn) 20 myCommand.Fill(myDataSet, "InProgress") 21 22 mySQLString = "SELECT * FROM vwAllUserTasks WHERE StatusName='Complete'" 23 myCommand = New SqlDataAdapter(mySQLString, myConn) 24 myCommand.Fill(myDataSet, "Complete") 25 26 27 'Bind Controls to dataset data 28 listAll.DataSource = myDataSet.Tables("All").DefaultView 29 listAll.DataTextField = "name" 30 listAll.DataValueField = "id" 31 listAll.DataBind() 32 33 listNotStarted.DataSource = myDataSet.Tables("NotStarted").DefaultView 34 listNotStarted.DataTextField = "name" 35 listNotStarted.DataValueField = "id" 36 listNotStarted.DataBind() 37 38 listInProgress.DataSource = myDataSet.Tables("InProgress").DefaultView 39 listInProgress.DataTextField = "name" 40 listInProgress.DataValueField = "id" 41 listInProgress.DataBind() 42 43 listComplete.DataSource = myDataSet.Tables("Complete").DefaultView 44 listComplete.DataTextField = "name" 45 listComplete.DataValueField = "id" 46 listComplete.DataBind() 47 48 linkListAll.Text = "All Tasks: (" + listAll.Items.Count.ToString + ")" 49 linkListNotStarted.Text = "Tasks Not Started: (" + listNotStarted.Items.Count.ToString + ")" 50 linkListInProgress.Text = "Tasks In Progress: (" + listInProgress.Items.Count.ToString + ")" 51 linkListComplete.Text = "Tasks Complete: (" + listComplete.Items.Count.ToString + ")" 52 53 End Sub
Lines 03-06 are just declaring variables and objects required for data access. These should be familiar to anyone who has done ADO.NET development work. Line 08 is used to set-up and create a SQLConnection object, which is used to connect to a SQL server database.
Lines 10 to 12 present a more interesting section of code. These three lines are repeated four times, once for each type of task status listed on the first page. Also if you remember from the application overview at the beginning of this article, I mentioned that after selecting an option from the first card, you would be presented with another card of information that displays all of the tasks for that option. This section of code may be used to create that list as well, which might seem strange.
Why create all of this data up front rather than as it is needed? Why not treat it like a traditional web application? The answer is quite simple: Because multiple cards can be downloaded to a mobile device at on time, all of the cards should act in a disconnected manner. That is to say, once the deck has been downloaded, it should work without requiring access to the web server unless specifically required. This means that any issues dealing with connection latencyor indeed disconnection - do not hamper the operation of the mobile device too much. Because the bandwidth of a mobile device connection is particularly small, the less access to a web server the better.
Normally, single stored procedure would be used to retrieve all of these values, and then bind the resulting data to a mobile:List object, but for reasons of simplicity, I have not done so here.
Back to the code: On line 10, a string is declared to hold a SQL statement that is to be processed by a SQL Server. The statement itself is pretty simple; just return all of the tasks in the database. On line 11, a SQLDataAdapter object is created using the Connection object and the SQL query string created in line 09. Finally, in line 12, the SQL query is processed and its resulting data is stored in a DataSet object that was created on line 03. It's assigned the name of "All", so that we can reference this table of data later in the application.
Lines 14-16 do the same as above. But note that the SQL statement string (line 14) is changed to a query that returns only those tasks that have a status of 'Not Started'. Once the string has been redefined, the SQLDataAdapter is created with the new SQL statement (line 15).
The same process applies for lines 19-21, and lines 23-25. The only differences being the SQL statements and the name of the tables that are being stored in the dataset.
Lines 26-37 setup and bind data to 4 separate List controls, and their data sources, this is done by first assigning a DataSource, and then by assigning the DataTextField and DataValue Field Values as shown below.
28 listAll.DataSource = myDataSet.Tables("All").DefaultView 29 listAll.DataTextField = "name" 30 listAll.DataValueField = "id" 31 listAll.DataBind()
On line 29, the List control sets its datatextfield property to the Name field in the table. The datatextfield property is the value used as an option in the list. On line 30, the datavaluefield sets the ID field in the table. The datavaluefield is used to store the value of an option in the List control. In this case, each name of each task in the database will be an option on the List control and each ID in the database will be the value used for each option. On line 31, the List control is bound to its datasource based on the datatextfield and datavaluefield properties.
This process is repeated for each List control in all the other cards in the deck. Only the List control name and the datasource value are changed to reflect the differing sets of data required.
The final section of code in this subroutine deals with setting the values of the Link controls on the first card. The text property of each Link control simply is assigned to the text label that is to be used, which in this case is a predefined label plus the total number of items in each List control to which data is bound.
Cards 2,3,4 and 5Listing the Tasks for the Specified Option.
The presentation code for these four cards is essentially the same with the exception of differing IDs (see Figure 2). Therefore, I am only going to walk through the code for the first one (see Listing 4).
Figure 2 The Presentation of cards 2, 3, 4, and 5.
Listing 4 Presentation Code for Card 2
01 <%--Display all Tasks in Database as a Selection List--%> 02 <Mobile:Form ID="cardListAll" RUNAT="server"> 03 04 <strong> 05 <Mobile:Label RUNAT="server" > 06 All Tasks 07 </Mobile:Label> 08 </strong> 09 <br/> 10 11 <Mobile:List runat=server 12 id="listAll" OnItemCommand="ListHandler"> </Mobile:List><br/> 13 14 <Mobile:Link RUNAT="server" 15 NavigateUrl="#cardHome">Home Page</Mobile:link> 16 17 </Mobile:Form>
Line 02, defines the card ID. (As stated earlier in the article, you can have many cards in a deck.) The only way to see any cards in the deck (apart from the first one) is to explicitly navigate to them. In the case of this card, navigation is done from the first card in the file through a Link control. Lines 04-08 just render the title of the card to the mobile device's display. Then, on line 10, a List control is defined.
The subroutine identified in the OnItemCommand element will be called. Also notice that no options are defined for this control because they are generated at runtime by the Page_Load event, as shown in the previous section. Finally, on line 14 a Link control is created, which is used to navigate to the first card in the deck. As before, it references the card name in the target attribute of the control.
Card 6Displaying the Task Information
Line 11 in Listing 4 refers to a subroutine called ListHandler by its onItemCommand attribute. This subroutine is called when an option is selected on the List control. This subroutine is used by all of the cards with a Databound list. Its sole purpose is to connect to the server and download the information on the task that was selected. In a real application you would still use the dataset that was created earlier for this purpose. For this brief tutorial, however, I decided to access the database again, except this time using a SQLDataReader control. The presentation code is listed in Listing 5, and can be viewed in figure 4.
Figure 4 Presentation of Card 6.
Listing 5_Presentation Code for Card 6
01 <Mobile:Form id="CardDisplaySelection" runat=server> 02 <strong>Task Name:</strong> 03 <br/> 04 <Mobile:Label runat=server id="taskName"></Mobile:Label> 05 <br/> 06 <strong>Task Description: </strong> 07 <br/> 08 <Mobile:Label runat=server id="taskDescription"></Mobile:Label> 09 <br/> 10 <strong>Task Staus: </strong> 11 <br/> 12 <Mobile:Label runat=server id="taskStatus"></Mobile:Label> 13 <br/> <Mobile:Link RUNAT="server" NavigateUrl="#cardHome">Home Page</Mobile:link> 15 </Mobile:Form>
In this control, each element of the task that was selected is rendered one after the other. You might find the formatting of the code somewhat strange, but this makes the best possible use of the mobile device screen. As stated at the beginning of this article, this application was designed with mobile phones in mind. Consequently, it renders pretty well on such devices. As before, the Label controls do not render text by themselves. Instead they have their text generated by the ListHandler subroutine shown in Listing 6.
Listing 6_ListHandler Subroutine
Sub ListHandler(ByVal Source As Object, ByVal E As System.Web.UI.MobileControls.ListCommandEventArgs) 02 03 Dim myConn As SqlConnection 04 Dim myCommand As SqlCommand 05 Dim myParam As SqlParameter 06 Dim myReader As SqlDataReader 07 08 myConn = New SqlConnection("Data Source=localhost;User Id=sa;password=;Initial Catalog=dnaMobileData") 09 myConn.Open() 10 11 myCommand = New SqlCommand("spGetTask", myConn) 12 myCommand.CommandType = CommandType.StoredProcedure 13 myParam = myCommand.Parameters.Add(New SqlParameter("@TaskID", SqlDbType.Int)) 14 myParam.Direction = ParameterDirection.Input 15 myParam.Value = E.ListItem.Value 16 17 myReader = myCommand.ExecuteReader() 18 19 While myReader.Read() 20 21 taskName.Text = myReader.Item("name") 22 taskDescription.Text = myReader.Item("description") 23 taskStatus.Text = myReader.Item("statusname") 24 25 End While 26 27 myConn.Close() 28 29 ActiveForm = CardDisplaySelection 30 31 End Sub
You are now into the final part of code for this application. In lines 03-06, the variables and objects required to read data from the database are set up. On lines 08 and 09, a connection to the database is created and opened. Line 11 defines a SQLCommand object to use with the database. This example calls on the following stored procedure (see Listing 7), which is in the database.
Listing 7_Database stored procedure
01 CREATE PROCEDURE spGetTask 02 @TaskID int 03 AS 04 SELECT * FROM vwAllUserTasks WHERE id = @TaskID
As you can see the stored procedure is pretty simplistic. It takes the TaskID as a parameter. The stored procedure uses the TaskID in a simple select query. (The TaskID is the value of the List control option selected.)
On line 12 (Listing 6) the command type of the SQLCommand object is set to stored procedure. Then on line 13 a SQLParameter object is defined as the @TaskID parameter in the stored procedure with a data type of int. Line 14 tells the parameter object that it is holding an input only parameter. Line 15 assigns the parameter's value based on the option value of the passed List control. (The List control is passed to the event handler, which is processing its OnItemCommand event).
On line 17, the stored procedure is executed, which leads us to line 19. That's a simple While loop that reads the next item in the datareader object. If no data is returned by the stored procedure, the While block gets skipped. If there is a returned value, however, the values from the datareader are assigned to the Label controls on the sixth Card. Once this has been done, the sixth card is displayed by setting the mobile device's active card value to the ID of the sixth card.