No part of this document may be reproduced in any form or by any electronic or mechanical means including information storage and retrieval systems, without permission in writing from the author.
This document collects some of Basic4android tutorials and examples. The most updated tutorials can be found online: http://www.basic4ppc.com/forum/basic4android-getting-startedtutorials/
2
Table of contents Installing Basic4android and Android SDK .....................................................................4 Hello world - Installing Android Emulator ......................................................................7 Guess my number - Visual designer & Events ............................................................... 13 IDE Tips ....................................................................................................................... 19 B4A-HelpViewer - View and search the documentation offline..................................... 24 B4A-Bridge a new way to connect to your device ......................................................... 25 Connecting your device to the IDE using ADB.............................................................. 28 Android Process and activities life cycle........................................................................ 32 Static Code Modules ..................................................................................................... 36 Service Modules............................................................................................................ 37 Variables & Objects ...................................................................................................... 41 Currency Converter – libraries, file manager and other important concepts.................... 45 Working with files......................................................................................................... 53 Views (controls) and Dialogs ........................................................................................ 57 GPS tutorial .................................................................................................................. 64 MediaPlayer – Playing music ........................................................................................ 68 ListView tutorial ........................................................................................................... 71 ScrollView tutorial ........................................................................................................ 76 TabHost tutorial ............................................................................................................ 78 FlickrViewer example – Download multiple images concurrently ................................. 80 Two activities example.................................................................................................. 81 Building a linked list using Type keyword..................................................................... 83 SQL tutorial .................................................................................................................. 85 XML parsing with the XMLSax library......................................................................... 90 Take pictures with the internal camera........................................................................... 93 Serial (Bluetooth) tutorial.............................................................................................. 95 JSON parsing and generating ...................................................................................... 100 Animation tutorial ....................................................................................................... 103 Network tutorial .......................................................................................................... 107 Regular Expressions .................................................................................................... 111 Downloading files using Services................................................................................ 114 AsyncStreams tutorial ................................................................................................. 116
3
Installing Basic4android and Android SDK Basic4android depends on two additional (free) components: - Java JDK - Android SDK
Installation instructions: The first step should be to install the Java JDK as Android SDK requires it as well. Note that there is no problem with having several versions of Java installed on the same computer. - Open the Java 6 JDK download link. - Select "Windows" in the platform combo box (for 64 bit machines as well). Android SDK doesn't work with Java 64bit JDK. You should install the regular JDK for 64bit computers as well. - Press on the red Continue button. There is no need to sign in! If for some reason you don't see the red Continue button try to switch to a different browser. - On the next page you should press on the file link:
- Download the file and install it. Next step is to install the Android SDK and a platform: - Download Android SDK. - Install the SDK. - When the application opens it will show a page with all the available packages. Press "Cancel" button as you do not need to install all the platforms. - Choose "Available Packages" and choose "SDK Platform 2.2, API 8". It should appear under the "Android repository" node. The file structure of API 9 is different. For now you should use API 8.
4
Note that you can install more packages later. - Press on Install Selected and install both packages. Install and configure Basic4android - Download and install Basic4android. - Open Basic4android. - Choose Tools menu - Configure Paths.
5
- Use the browse buttons to locate "javac.exe" and "android.jar" javac is located under \bin. android.jar is located under \platforms\android-8 On Windows 64 bit, Java will probably be installed under C:\Program Files (x86).
6
Hello world - Installing Android Emulator In this tutorial we will create a new AVD (Android Virtual Device) which is an emulator instance. Then we will create a simple program that displays a simple message box and also writes a message to the log. You can also connect a real device to the IDE: Connecting your device with ADB Connecting your device with B4A-Bridge Create new AVD - Run Basic4android. - Choose Tools Menu - Run AVD Manager. Wait a few seconds. - The AVD Manager should appear:
- Choose New and fill the fields similar to the following image:
7
- Press on Create AVD. - Note that you can create more than one AVD. Each can have a different resolution or can target a different API version (you will need to install additional platforms first). - Now press Start in order to start the emulator
- You will see several windows popping up and disappearing. This is fine. - The emulator should boot up:
8
Wait... on the first time it can take several minutes till the emulator is ready. The emulator is ready when it gets to this screen:
You may see this screen, which is the lock screen, instead:
Drag the lock icon to the right to unlock the device. Note that there is no need to restart the emulator each time you deploy a program. The emulator can be kept running all the time. If you are not familiar with Android you can play with the emulator. Press on the button with the small squares to get to the application page.
9
Writing your first Basic4android program - As this is a new program we should first set its location by choosing File - Save. It is highly recommended to save each project in its own folder. - Create a new folder: "Hello world", open the folder and save the program as "Hello world". - Write the following code under Sub Activity_Create: Code: Sub Activity_Create(FirstTime As Boolean) Log("Hello world!") Msgbox("Hello world?", "First program") End Sub
-
Press F5 to compile and deploy your program to the emulator. The package dialog should appear (empty):
Each Android application is identified by a unique package string. This is a string built of several parts separated with periods. The string should include at least two parts. You cannot install two applications with the same package on one device. Note that you can always change the package name (and the label) under tools menu. - Enter a package name. - Next you will be asked to enter the application "label". This is the application name that the user will see. Your program will now be compiled and installed to the emulator:
10
The emulator is significantly slower than a real device. In many cases it is more convenient to work with a real device as the installation is much faster. Note that you can always redeploy your program. There is no need to close the running program on the emulator. Tracking the log with LogCat Android devices keep an internal buffer of log messages. These messages can be very handy for debugging. To view the logs you should switch to the LogCat tab in the right pane and press connect:
11
There are two "Hello world!" messages in the screenshot as I ran the program twice. Unchecking "Filter" will show all available messages (not just messages relevant to your program). Hello world
12
Guess my number - Visual designer & Events In this tutorial we will use the designer to create the layout. The layout includes an EditText (TextBox) and a Button. The user needs to guess a random number. The user enters the number in the EditText view (control) and presses on the button to submit the guess. A "toast" message will appear, indicating to the user whether the number is larger or smaller than the chosen number. - Create a new project and save it. - Open the designer by choosing the Designer menu. The designer is made of two main components. The "control panel" which contains all the available properties and options, and is part of the IDE:
and the "visual" component which runs on a device or emulator:
13
The visual component, as it names suggests, displays the layout. It also allows you to move and resize the views (controls). Changing the layout in the visual component will also change the values stored in the control panel. Note that all the data is stored in the control panel component. Therefore nothing bad will happen if the emulator crashes or is turned off. You can connect to it again and the layout will appear. The first step is to connect to the device. Press Tools - Connect. This step takes several seconds. Note that the IDE will stay connected until the IDE closes. Closing the designer will not disconnect the connection. Use the Add View menu to add a Button, an EditText and a Label. Change the views Text property and position them similar to this:
14
Change the Activity Drawable property to GradientDrawable to achieve the gradient effect. Tip: When working with a small monitor you may find it convenient to check the "Top Most" option (in the upper right corner). It will cause the control panel to stay on top and not be hidden by the emulator. Save your layout, name it Layout1. An important concept about layouts is that there is a complete separation between your code and the layouts. The layout is saved as a file, with ".bal" extension. Each project can have any number of such files and unless you explicitly load a layout file, it will not have any effect on your application. Once you have saved a layout, it is automatically added to the "File manager". You can see it under the "Files" tab in the IDE right pane. We want to catch the button's click event. Each view has an EventName value. It is a property in the Designer, and a parameter passed to the Initialize method when adding views programmatically. In order to catch an event you should write a Sub with the following structure (it is simpler than it sounds): Sub _ (event parameters). In the designer, the EventName property is set by default to the view's name. If we want to catch the Click event of a button with EventName value of Button1 we should write the following sub signature: Sub Button1_Click So here is the complete code:
15
Code: Sub Process_Globals End Sub Sub Globals Dim MyNumber As Int Dim EditText1 As EditText 'will hold a reference to the view added by the designer End Sub Sub Activity_Create(FirstTime As Boolean) Activity.LoadLayout("Layout1") 'Load the layout file. MyNumber = Rnd(1, 100) 'Choose a random number between 1 to 99 End Sub Sub Button1_Click If EditText1.Text > MyNumber Then ToastMessageShow("My number is smaller.", False) Else If EditText1.Text < MyNumber Then ToastMessageShow("My number is larger.", False) Else ToastMessageShow("Well done.", True) End If EditText1.SelectAll End Sub
Some notes: - Every activity module comes with an Activity object that you can use to access the activity. - Activity.LoadLayout loads the layout file. - There are other views that can load layout files. Like Panel and TabHost. For TabHost each tab page can be created by loading a layout file. - In order to access a view that was added with the designer we need to declare it under Sub Globals. - ToastMessageShow shows a short message that disappears after a short period. Using a toast message in this case is not optimal as it may be unnoticed when the soft keyboard is open. Tip for writing events subs: In the IDE, write Sub and press space. You should see a small tooltip saying "press tab to insert event declaration". Press tab, choose the object type and choose the event.
16
Now all you need to do is to write the required event name and press enter. Supporting multiple screen resolutions and orientations Each layout file can include a number of layout variants. Each layout variant holds a different set of values for the position and size of the views. If for example, you change the text of any view it will be changed in all variants automatically. However if you change the position of a view it will only affect the current variant. Note that scaling is handled automatically if required. Which means that if we run our program on a high resolution device, the layout will be automatically scaled. Still you may choose to create different variants for different scales. When you load a layout file the variant that best matches the current device will be loaded. Usually you will need two variants: - 320 x 480, scale = 1 (160 dpi). This is the default scale in portrait mode. - 480 x 320, scale = 1 (160 dpi). Default scale in landscape mode. Ok, so open the designer again. Load Layout1 file if it is not opened. Choose "New Variant" and choose 480 x 320 (second option).
Change the emulator orientation by clicking on the emulator and then press on Ctrl + F11. Note that the device layout details appear under the list of variants. Change the layout to be similar to this:
17
You can change the current selected variant and see how it affects the visual layout. Save the layout and run the program. Change the emulator orientation and see how the layout changes accordingly. Android destroys the old activity and creates a new activity each time the orientation changes. Therefore Activity.LoadLayout will be called again each time. Unfortunately the number will also be randomly chosen again each time. This can be easily fixed... But not in this tutorial. The project is attached. Last tip for this tutorial: - The IDE includes an "Export as zip" option under Files menu. This method creates a zip file with all the required files.
18
IDE Tips The IDE has several powerful features which can help you concentrate on writing your code and building your application. I'm listing here some of the less obvious features: - Ctrl
+ Space = auto complete
Pressing Ctrl + Space activates the auto complete feature which will then show you a list with the available keywords, variables, modules, methods, subs, properties and fields. The list includes a description for most items. Pressing Ctrl + Space after typing the first few letters will usually select the required item automatically.
- Tool tip information - while writing methods parameters, a tool tip will be opened with the method signature and description. The tool tip might hide some important code that you now need. You can hide it by pressing escape. You can also turn it almost invisible by pressing the ctrl key. Another press will return it to be fully opaque.
19
LogCat - The LogCat tab displays the device built-in logs. These logs are very useful for debugging. You can log messages with the Log keyword. In order to start displaying the logs you press on the Connect button. The logs can be filtered and then you only see messages generated by Basic4android or your application.
Note that if you have more than one device connected you can switch to a different device by pressing on the Connect button. Designer generated members tool - This tool allows you to add the declaration code for the designer views and to add event subs. Note that you only need to declare views that you intend to access by code. Nothing will happen if you select an existing item (there will be no duplicated code).
20
To open this tool choose - Tools - Generate Members (from the designer form). Background compilation - Pressing Alt + 3 will compile and install your application while keeping the IDE responsive. The status bar at the bottom of the screen displays the progress of the process and when the installation is completed. A short sound will notify you if the process failed. In that case you may need to compile regularly (F5) in order to see the error message (it depends on the type of error). Working with multiple connected devices - In many cases you have more than one device connected. For any operation that starts a connection you will be shown the list of connected device and you will choose the target device. If you compile in the background the last device will be used again. This is usually more convenient than compiling in the foreground and selecting the target device each time. Designer - Duplicate - You can duplicate any view by selecting the view and then choosing Tools - Duplicate View. If the view has child views then all its child views will be duplicated as well. Export as zip - Export as zip option creates a zip file with all the required project files. This is useful when you want to share your project with others or create a backup. It is located under Files menu. Clean Project / Clean Unused Files - Clean project deletes all generated files. These are 21
files that are generated during compilation. Clean unused files deletes files that are located under the Files folder but are not used by the project (it will not delete any file reference by any of the project layouts). A list of unused files will be displayed before deletion (and allows you to cancel the operation). Run AVD Manager - The AVD manager allows you to create and start emulators. This menu opens the manager. Note that there is no need to keep the AVD manager open after starting an emulator. Events subs signatures -There is a special auto complete feature that can help you write the event subs signatures. Start with writing Sub followed by a space:
A message will appear as in the image. Press Tab key:
A list will be displayed with all the available types (that have at least one event). Choose the required type and press enter.
Choose the specific event. Code similar to the following code will be generated:
22
The EventName string will be selected. Change it to match the object "EventName" value and press enter. That's it. Designer top most property - The designer has a "top most" check box which you can use to keep the designer as the top most form. This is useful when working with the designer and the emulator on a small screen. Debugging data - By default Basic4android compiler adds some debugging data to your code. This data is used when an error occurs. It allows the program to show the original code line which raised the error. This data does take some space and may impact performance, though it usually should be insignificant. You can remove this data by unchecking Project - Include Debug Information.
23
B4A-HelpViewer - View and search the documentation offline I'm happy to release the second beta version of B4A HelpViewer. This utility allows you to browse and search the documentation and the forum posts from the desktop. It automatically checks for updated libraries and forum posts during start-up. The libraries manuals are downloaded and stored locally. The search functionality is built using Lucene.
For now there is no installation step. You should unzip the file and run B4A-HelpViewer. Note that it should not be located under Program Files as it needs "write permissions". Download link 24
B4A-Bridge a new way to connect to your device Currently there are two ways which you can use to test your application during development. You can either work with the Android emulator or if your device supports ADB debugging, you can connect to your real device. The Android emulator is very slow compared to a real device (especially with applications installation). Therefore in most cases it is more convenient to work with a real device. Personally I'm only using the emulator when working with the visual designer. However not all devices support ADB debugging. This is exactly the reason for the new B4A-Bridge tool. B4A-Bridge is made of two components. One component runs on the device and allows the second component which is part of the IDE to connect and communicate with the device. The connection is done over a network (B4A-Bridge cannot work if there is no network available). Once connected, B4A-Bridge supports all of the IDE features which include: installing applications, viewing LogCat and the visual designer. Android doesn't allow applications to quietly install other applications, therefore when you run your application using B4A-Bridge you will see a dialog asking for your approvement. Getting started with B4A-Bridge 1. First you need to install B4A-Bridge on your device. B4A-Bridge can be downloaded here: http://www.basic4ppc.com/android/files/b4a_bridge.apk. B4A-Bridge is also available in Android market. Search for: B4A Bridge. Note that you need to allow install of applications from "Unknown sources". This is done by choosing Settings from the Home screen - Manage Applications. 2. Run B4A-Bridge on your device. It will display a screen similar to:
25
Status will be: Waiting for connections. 3. In the IDE choose Tools - B4A Bridge - Connect. You will need to enter the IP address that appears on the device screen. The status bar at the bottom of the screen shows the current status:
That's it. When B4A-Bridge gets connected it first checks if the designer application needs to be updated. In that case it will first install the designer application. B4A-Bridge keeps running as a service until you press on the Stop button. You can always reach it by opening the notifications screen:
26
Pressing on the notification will open the main screen. As mentioned above, when you run an application you are required to approve the installation. You will usually see the following screens:
In the above dialog you should choose Open to start the application. If you try to install an existing application signed with a different key, the install will fail (without any meaningful message). You should first uninstall the existing application. Go to the home screen - Settings - Applications - Manage applications choose the application - Uninstall. Once you finished developing you should press on the Stop button in order to save battery. Note that B4A-Bridge was written with Basic4android. The source code is available here: http://www.basic4ppc.com/forum/basic...html#post45854 27
Connecting your device to the IDE using ADB This tutorial was written by Andrew Graham Introduction A program called adb.exe - Android Debug Bridge - is the key component that is used to communicate between programs on your desktop and the emulator or device. adb can be invoked without user intervention by programs such as Basic4Android and Eclipse but also has a command line interface that can be used by advanced users. adb.exe is provided with the Android SDK and is located, together with other development tools, on early versions of the Android SDK in the \tools and on more recent versions in the \platform-tools folder. Unless adb can connect properly to the emulator or device Basic4Android will not be able to load and run applications, however in this event they could still be compiled then copied to the SD card on the device by USB and installed from there. This tutorial tries to explain how to get your device connected to adb so that you can use it with the Basic4Android IDE.
Device SD cards and USB Although this capability is entirely independent of adb it is worth including here for completeness in describing how devices use USB connections. Android devices have a USB port that when plugged into a desktop lets the SD card on the device look like a USB memory stick. The first time that a device is connected to a desktop by USB Windows should automatically detect this and install the necessary driver without you having to do anything. The SD card will then appear as a disc drive in Windows Explorer and Device Manager while the device is connected. While the SD card is visible to the desktop as a drive the device will not be able to access it as it is unmounted from the device. Some, but not all, devices allow you to mount and unmount the SD card from the device when it is plugged into the desktop USB. Other devices do not offer this option but automatically unmount it on connection to USB and remount it on disconnection. You will have to see how your own device behaves in this regard
The emulator and adb There should be absolutely no problem using the emulator with Basic4Android. adb will connect automatically and should work well, although I have found that if the emulator is running Android 1.6 the adb connection will frequently drop and cause problems. Therefore I recommend using Android 2.1 or later in the emulator to avoid any possible problems. Also there should be no problem interacting with the emulator with adb in command line mode if that is required. Because the emulator should just work the
28
remainder of this article deals with connecting devices by means of adb.
Devices and adb In my experience so far devices differ in their USB capability and in their interaction with adb and Windows so the following can only be a guide. Your experience may vary. adb can connect a device to the desktop by two different means, Wi-Fi and USB. In both cases for adb to recognise the device Settings -> Applications -> Development -> USB debugging must be checked. This runs the adb daemon on the device which communicates with adb on the desktop.
adb and WiFi To connect to a device wirelessly you need to connect the device to the network by Wi-Fi and obtain the device IP address. This can be found under Settings -> Wireless & Networks -> Wi-Fi Settings. Press the connected network under the WiFi Networks section and a message box will display the connection details. Before running Basic4Android open a Command Window in the \tools folder and type the command adb connect 192.168.0.10 replacing the IP address with the correct one for the device. if you get an error try specifying the port adb connect 192.168.0.10:5555 I have encountered only partial functionality with adb over Wi-Fi. I have a tablet that works fine with adb and so with Basic4Android - which is just as well as the tablet lacks the USB connectivity needed for adb. I also have a phone that mostly works with adb in command line mode but adb hangs and doesn't return to the command prompt after performing an install or uninstall although the actual operation on the device is successful. This behaviour means that neither Basic4Android nor Eclipse can be used with the phone over wireless but thankfully they work perfectly over USB.
adb and USB
29
Many Android devices, but not all like some cheap Chinese Android tablets, have two more USB interfaces "Android ADB Interface" and "Android Composite ADB Interface". These belong to a device that, on successful USB driver installation, appears as "Android Phone" in Device Manager and is used by adb to connect to the phone over USB. If your phone lacks these it may still be possible to connect adb to it by means of wireless as described above. These Android interfaces, unlike SD card access, need custom USB drivers that are located in the \usb_driver. In this folder there is a android_winusb.inf file that contains the USB IDs needed to install the drivers for different phones. When a phone is connected and Windows asks for the drivers for this device point it to this folder. If the drivers then fail to install it is likely that the android_winusb.inf does not contain the IDs for the phone. In android_winusb.inf there are two sections the one named [Google.NTx86] contains the details for 32 bit systems, another named [Google.NTamd64] contains the details for 64 bit systems. The two sections are in fact identical. For my phone to be recognised the following details needed to be inserted in each of the two sections mentioned. ;Orange San Francisco %SingleAdbInterface% = USB_Install, USB\VID_19D2&PID_1354&MI_00 %CompositeAdbInterface% = USB_Install, USB\VID_19D2&PID_1354&MI_02 These necessary details were obtained courtesy of a certain well known Internet search engine. However, rather belatedly, Google now at last has a list of links to manufacturer sites that provide USB drivers for their devices on this page Google USB Driver . The actual link is OEM USB Drivers. Note that for some devices, like my phone, Windows may try to install the drivers without USB Debugging being enabled on the phone. This is likely to fail even when you point Windows at the drivers that installed successfully with USB Debugging enabled. As this will happen every time the phone is plugged in to USB the solution is to keep USB Debugging enabled. This has no downside as, for this phone at least, the USB access to the SD card is entirely independent of whether USB Debugging is enabled or not. More on adb and WiFi Some devices seem to not have the adb daemon enabled for wireless. There is an application called adbWireless that can overcome this for many, but not all, devices. It is available on the Android Market, the latest version is 1.4.1, but it needs root access to the phone to work. Another application called UniversalAndroot may help here - it worked on my ZTE
30
Blade/Orange SanFrancisco to let me run adbWireless on it. It can be downloaded from universalandroot by clicking one of the links at the bottom of the page by the 2D barcodes.
31
Android Process and activities life cycle Lets start simple: Each Basic4android program runs in its own process. A process has one main thread which is also named the UI thread which lives as long as the process lives. A process can also have more threads which are useful for background tasks. A process starts when the user launches your application, assuming that it is not running already in the background. The process end is less determinant. It will happen sometime after the user or system has closed all the activities. If for example you have one activity and the user pressed on the back key, the activity gets closed. Later when the phone gets low on memory (and eventually it will happen) the process will quit. If the user launches your program again and the process was not killed yet the same process will be reused. A Basic4android application is made of one or more activities. Android support several other "main" components. These will be added to Basic4android in the future. Activities are somewhat similar to Windows Forms. One major difference is that, while an activity is not in the foreground it can be killed in order to preserve memory. Usually you will want to save the state of the activity before it gets lost. Either in a persistent storage or in memory that is associated with the process. Later this activity will be recreated when needed. Another delicate point happens when there is a major configuration change in the device. The most common is an orientation change (user rotates the device). When such a change occurs the current activities are destroyed and then recreated. Now when we create the activity we can create it according to the new configuration (for example, we now know the new screen dimensions). How do we handle it? When you create a new activity you will start with the following code template: Code: Sub Process_Globals 'These global variables will be declared once when the application starts. 'These variables can be accessed from all modules. End Sub
32
Sub Globals 'These global variables will be redeclared each time the activity is created. 'These variables can only be accessed from this module. End Sub Sub Activity_Create(FirstTime As Boolean) End Sub Sub Activity_Resume End Sub Sub Activity_Pause (UserClosed As Boolean) End Sub
Variables can be either global or local. Local variables are variables that are declared inside a sub other than Process_Globals or Globals. Local variables are local to the containing sub. Once the sub ends these variables no longer exist. Global variables can be accessed from all subs. There are two types of global variables. Process variables and activity variables. Process variables - These variables live as long as the process lives. You should declare these variables inside sub Process_Globals. This sub is called once when the process starts (this is true for all activities, not just the first activity). These variables are the only "public" variables. Which means that they can be accessed from other modules as well. However, not all types of objects can be declared as process variables. All of the views for example cannot be declared as process variables. The reason is that we do not want to hold a reference to objects that should be destroyed together with the activity. In other words, once the activity is being destroyed, all of the views which are contained in the activity are being destroyed as well. If we hold a reference to a view, the garbage collector would not be able to free the resource and we will have a memory leak. The compiler enforces this requirement. Activity variables - These variables are contained by the activity. You should declare these variables inside Sub Globals. 33
These variables are "private" and can only be accessed from the current activity module. All objects types can be declared as activity variables. Every time the activity is created, Sub Globals is called (before Activity_Create). These variables exist as long as the activity exists. Sub Activity_Create (FirstTime As Boolean) This sub is called when the activity is created. The activity is created when the user first launches the application, the device configuration has changed (user rotated the device) and the activity was destroyed, or when the activity was in the background and the OS decided to destroy it in order to free memory. This sub should be used to load or create the layout (among other uses). The FirstTime parameter tells us if this is the first time that this activity is created. First time relates to the current process. You can use FirstTime to run all kinds of initializations related to the process variables. For example if you have a file with a list of values that you need to read, you can read it if FirstTime is True and store the list as a process variable. Now we know that this list will be available as long as the process lives and there is no need to reload it even when the activity is recreated. To summarize, you can test whether FirstTime is True and then initialize process variables. Sub Activity_Resume and Sub Activity_Pause (UserClosed As Boolean) Each time the activity moves from the foreground to the background Activity_Pause is called. Activity_Pause is also called when the activity is in the foreground and a configuration change occurs (which leads to the activity getting paused and then destroyed). Activity_Pause is the last place to save important information. Generally there are two types of mechanisms that allow you to save the activity state. Information that is only relevant to the current application instance can be stored in one or more process variables. Other information should be stored in a persistent storage (file or database). For example, if the user changed some settings you should save the changes to a persistent storage at this point. Otherwise the changes may be lost. Activity_Resume is called right after Activity_Create finishes or after resuming a paused activity (activity moved to the background and now it returns to the foreground). Note that when you open a different activity (by calling StartActivity), the current activity is first paused and then the other activity will be created if needed and (always) resumed. As discussed above Activity_Pause is called every time that the activity moves from the
34
foreground to the background. This can happen because: 1. A different activity was started. 2. The Home button was pressed 3. A configuration changed event was raised (orientation changed for example). 4. The Back button was pressed. In scenarios 1 and 2, the activity will be paused and for now kept in memory as it is expected to be reused later. In scenario 3 the activity will be paused, destroyed and then created (and resumed) again. In scenario 4 the activity will be paused and destroyed. Pressing on the Back button is similar to closing the activity. In this case you do not need to save any instance specific information (the position of pacman in a PacMan game for example). The UserClosed parameter will be true in this scenario and false in all other. Note that it will also be true when you call Activity.Finish. This method pauses and destroys the current activity, similar to the Back button. You can use UserClosed parameter to decide which data to save and also whether to reset any related process variables to their initial state (move pacman position to the center if the position is a process variable).
35
Static Code Modules Basic4android v1.2 includes a new type of module which is the static code module. Adding a static code module is done by choosing Project - Add New Module - Code Module. Unlike Activities and Services, code modules are not related in any way with Android process life cycle. These are just containers for code. All of the subs in these modules are public and can be accessed from other modules. Code modules use cases include: - Avoiding duplicate code in multiple modules. - Sharing code between projects. For example you can create a code module that parses some file. Later you can easily reuse this module in other applications. - Separating your application logic. Each code module can be used for a specific task. This will make your program clearer and easier to maintain. As a code module is not tied to an activity or service it uses the calling component context when required. For example, calling a code module sub that shows a Msgbox from an activity will work. However if you call it from a service it will fail as services are not allowed to show dialogs. Code modules cannot catch events. While you can use a code module to initialize a button for example: Code: Sub ButtonsCreator(Text As String) As Button Dim b As Button b.Initialize("Button") b.Text = Text Return b End Sub
From the activity module you can call: Code: Activity.AddView(CodeModule.ButtonsCreator("press here"), 10dip, 10dip, 200dip, 200dip)
Now in order to catch the click event you should create a sub named Button_Click. This sub should be located in the Activity module, as Code modules cannot catch events. CallSub which internally uses the events mechanism cannot be used to call code module subs (which can be called directly instead).
36
Service Modules Basic4android v1.2 adds support for Service modules. Service modules play an important role in the application and process life cycle. Start with this tutorial if you haven't read it before: Android Process and activities life cycle Code written in an activity module is paused once the activity is not visible. So by only using activities it is not possible to run any code while your application is not visible. Services life cycle is (almost) not affected by the current visible activity. This allows you to run tasks in the background. Services usually use the status bar notifications to interact with the user. Services do not have any other visible elements. Services also cannot show any dialog (except of toast messages). Note that when an error occurs in a service code you will not see the "Do you want to continue?" dialog. Android's regular "Process has crashed" message will appear instead. Before delving into the details I would like to say that using services is simpler than it may first sound. In fact for many tasks it is easier to work with a service instead of an activity as a service is not paused and resumed all the time and services are not recreated when the user rotates the screen. There is nothing special with code written in service. Code in a service module runs in the same process and the same thread as all other code. It is important to understand how Android chooses which process to kill when it is low on memory (a new process will later be created as needed). A process can be in one of the three following states: - Foreground - The user currently sees one of the process activities. - Background - None of the activities of the process are visible, however there is a started service. - Paused - There are no visible activities and no started services. Paused processes are the first to be killed when needed. If there is still not enough memory, background processes will be killed. Foreground processes will usually not be killed. As you will soon see a service can also bring a process to the foreground. Adding a service module is done by choosing Project - Add New Module - Service Module. The template for new services is: Code:
37
Sub Process_Globals End Sub Sub Service_Create End Sub Sub Service_Start End Sub Sub Service_Destroy End Sub
Sub Process_Globals is the place to declare the service global variables. There is no other Globals sub like in Activity as Service doesn't support Activity objects. Sub process globals should only be used to declare variables. It should not run any other code as it might fail. This is true for other modules as well. Note that Process_Global variables are kept as long as the process runs and are accessible from other modules. Sub Service_Create is called when the service is first started. This is the place to initialize and set the process global variables. Once a service is started it stays alive until you call StopService or until the whole process is destroyed. Sub Service_Start is called each time you call StartService (or StartServiceAt). When this subs runs the process is moved to the foreground state. Which means that the OS will not kill your process until this sub finishes running. If you want to run some code every couple of minutes / hours you should schedule the next task with StartServiceAt inside this sub. Sub Service_Destroy is called when you call StopService. The service will not be running after this sub until you call StartService again (which will run Sub Service_Create followed by Sub Service_Start).
Service use cases As I see it there are four main use cases for services. - Separating UI code with "business" or logic code. Writing the non-UI code in a service is easier than implementing it inside an Activity module as the service is not paused and resumed and it is usually will not be recreated (like an Activity). You can call StartService during Activity_Create and from now on work with the service module. 38
A good design is to make the activity fetch the required data from the service in Sub Activity_Resume. The activity can fetch data stored in a process global variable or it can call a service Sub with CallSub method. - Running a long operation. For example downloading a large file from the internet. In this case you can call Service.StartForeground (from the service module). This will move your activity to the foreground state and will make sure that the OS doesn't kill it. Make sure to eventually call Service.StopForeground. - Scheduling a repeating task. By calling StartServiceAt you can schedule your service to run at a specific time. You can call StartServiceAt in Sub Service_Start to schedule the next time and create a repeating task (for example a task that checks for updates every couple of minutes). - Run a service after boot. By checking Project - Service properties - Run At Boot your service will run after boot is completed. Notifications Status bar notifications can be displayed by activities and services. Usually services use notifications to interact with the user. The notification displays an icon in the status bar. When the user pulls the status bar they see the notification message. Example of a notification (using the default icon):
The user can press on the message, which will open an activity as configured by the Notification object. The notification icon is an image file which you should manually put in the following folder: \Object\res\drawable. Accessing other modules
39
Process global objects are public and can be accessed from other modules. Using CallSub method you can also call a sub in a different module. It is however limited to non-paused modules. This means that one activity can never access a sub of a different activity as there could only be one running activity. However an activity can access a running service and a service can access a running activity. Note that if the target component is paused then an empty string returns. No exception is thrown. You can use IsPause to check if the target module is paused. For example if a service has downloaded some new information it can call: Code: CallSub(Main, "RefreshData")
If the Main activity is running it will fetch the data from the service process global variables and will update the display. It is also possible to pass the new information to the activity sub. However it is better to keep the information as a process global variable. This allows the activity to call RefreshData whenever it want and fetch the information (as the activity might be paused when the new information arrived). Note that it is not possible to use CallSub to access subs of a Code module. Examples: Downloading a file using a service module Periodically checking Twitter feeds
40
Variables & Objects Types Basic4android type system is derived directly from Java type system. There are two types of variables: primitives and non-primitives types. Primitives include the numeric types: Byte, Short, Int, Long, Float and Double. Primitives also include: Boolean and Char. List of types with their ranges: http://www.basic4ppc.com/forum/basic...html#post45511 Primitive types are always passed by value to other subs or when assigned to other variables. For example: Code: Sub S1 Dim A As Int A = 12 S2(A) Log(A) 'Prints 12 End Sub Sub S2(B As Int) B = 45 End Sub
All other types, including arrays of primitives types and strings are categorized as nonprimitive types. When you pass a non-primitive to a sub or when you assign it to a different variable, a copy of the reference is passed. This means that the data itself isn't duplicated. It is slightly different than passing by reference as you cannot change the reference of the original variable. All types can be treated as Objects. Collections like lists and maps work with Objects and therefore can store any value. Here is an example of a common mistake, where the developer tries to add several arrays to a list: Code: Dim arr(3) As Int Dim List1 As List List1.Initialize For i = 1 To 5 arr(0) = i * 2 arr(1) = i * 2 arr(2) = i * 2
41
List1.Add(arr) 'Add the whole array as a single item Next arr = List1.Get(0) 'get the first item from the list Log(arr(0)) 'What will be printed here???
You may expect it to print 2. However it will print 10. We have created a single array and added 5 references of this array to the list. The values in the single array are the values set in the last iteration. To fix this we need to create a new array each iteration. This is done by calling Dim each iteration: Code: Dim arr(3) As Int 'This call is redundant in this case. Dim List1 As List List1.Initialize For i = 1 To 5 Dim arr(3) As Int arr(0) = i * 2 arr(1) = i * 2 arr(2) = i * 2 List1.Add(arr) 'Add the whole array as a single item Next arr = List1.Get(0) 'get the first item from the list Log(arr(0)) 'Will print 2
Tip: You can use agraham's CollectionsExtra library to copy an array. Casting Basic4android casts types automatically as needed. It also converts numbers to strings and vice versa automatically. In many cases you need to explicitly cast an Object to a specific type. This can be done by assigning the Object to a variable of the required type. For example, Sender keyword returns an Object which is the object that raised the event. The following code changes the color of the pressed button. Note that there are multiple buttons that share the same event sub. Code: Sub Globals Dim Btn1, Btn2, Btn3 As Button End Sub Sub Activity_Create(FirstTime As Boolean) Btn1.Initialize("Btn") Btn2.Initialize("Btn") Btn3.Initialize("Btn") Activity.AddView(Btn1, 10dip, 10dip, 200dip, 50dip) Activity.AddView(Btn2, 10dip, 70dip, 200dip, 50dip) Activity.AddView(Btn3, 10dip, 130dip, 200dip, 50dip)
42
End Sub Sub Btn_Click Dim b As Button b = Sender 'Cast the Object to Button b.Color = Colors.RGB(Rnd(0, 255), Rnd(0, 255), Rnd(0, 255)) End Sub
The above code could also be written more elegantly: Code: Sub Globals End Sub Sub Activity_Create(FirstTime As Boolean) For i = 0 To 9 'create 10 buttons Dim Btn As Button Btn.Initialize("Btn") Activity.AddView(Btn, 10dip, 10dip + 60dip * i, 200dip, 50dip) Next End Sub Sub Btn_Click Dim b As Button b = Sender b.Color = Colors.RGB(Rnd(0, 255), Rnd(0, 255), Rnd(0, 255)) End Sub
Scope Variables that are declared in Sub Globals or Sub Process_Globals are global and can be accessed from all subs. Other variables are local and can only be accessed from the sub that they are declared in. See the Activity lifecycle tutorial for more information about Globals vs. Process_Globals variables. Tips All views types can be treated as Views. This allows you to change the common properties of views easily. For example, the following code disables all views that are direct children of the activity: Code: For i = 0 To Activity.NumberOfViews - 1 Dim v As View v = Activity.GetView(i) v.Enabled = False Next
If we only want to disable buttons: 43
Code: For i = 0 To Activity.NumberOfViews - 1 Dim v As View v = Activity.GetView(i) If v Is Button Then 'check whether it is a Button v.Enabled = False End If Next
The Type keyword allows you to create your own type of objects. Custom types behave exactly like other non-primitive types.
44
Currency Converter – libraries, file manager and other important concepts In this tutorial we will use the following web service to convert currencies: http://www.webservicex.net/CurrencyC...ToCurrency=EUR
There are several important aspects in this application. This tutorial will only briefly touch each topic. Files You can add files to your project using the Files tab:
45
In our case we have two file. CountryCodes.txt is a text file containing the list of currencies. Each line contains exactly one value. layout1.bal is the layout file created with the designer. Layout files are added automatically to the file manager. Note that the layout file contains another two image files, the buttons arrows. These files are listed in the designer. If we remove layout1.bal they will be removed from the package as well. The packaged files are also named assets. Locally they are stored under the Files sub folder. This code reads the text file and stores the data in a list: Code: If FirstTime Then countries = File.ReadList(File.DirAssets, "CountryCodes.txt")
File.ReadList is a convenient method that opens a file and adds all its lines to a List. Files are always referenced by their folder and name. The assets are referenced by the File.DirAssets value. Android file system is case sensitive. Which means that image1.jpg is not the same as Image1.jpg (unlike Windows file system). Structures You can create new types or structures using the Type keyword. Later you can declare variables of these new types. Types can hold any other objects, including other types and including themselves (and including arrays of all of these). Structures will be covered more deeply in a different tutorial... Structures are declared in one of the global subs. Code: Type MyTag (FromValue As EditText, ToValue As EditText, _ FromCurrency As Spinner, ToCurrency As Spinner) Dim CurrentTask As MyTag
This code declares a type that holds two EditTexts (textboxes) and two Spinners (Comboboxes). We also declare a variable of that type named CurrentTask. In the code you will see that we have another type named StateType which we use to store the current state. All views have a property named Tag. You can set this property to any object you like. We are using it together with the Sender keyword to handle both buttons with the same sub.
46
Libraries
As you can see in the image, the Libraries tab page shows a list of available libraries. The checked libraries are referenced. Note that you cannot remove the reference to the core library. Adding additional libraries Libraries are made of a pair of files. The xml file that describes the library and the jar file which holds the compiled code. Additional libraries and updates to official libraries are available here: http://www.basic4ppc.com/forum/addit...icial-updates/ Note that only users who bought Basic4android can download these libraries. To add a library to Basic4android all you need to do is to copy both files to a folder recognized by Basic4android. By default this is the 'Libraries' folder that is usually located in: c:\Program Files\Anywhere Software\Basic4android\Libraries. You can also configure an additional libraries folder by choosing Tools - Configure Paths. Note that the additional folder is scanned first for libraries. This means that you can update an existing library by putting the new files in the additional libraries folder (there is no need to delete the old files from the internal folder). Http library The Http library includes three objects. HttpClient - This is the main object that executes and manages the requests and responses. The HttpClient can execute multiple requests concurrently. It is very important to declare it as a Process global. The HttpClient handles requests that run in the background and it should not be tied to the activity life cycle. Communication is done in two steps. First a connection is established by sending a HttpRequest object and then the response is read from the server. The first step is always a non blocking action. It can take a long period till the connection 47
is established and you do not want to make your application be non-responsive at this time. Note that Android has a special "Application not responding" dialog which allows the user to force close the application. The second step, which is the consumption of the response can be either blocking or nonblocking. If you download a file for example you should probably choose the nonblocking option. This code creates and sends the GET request. Code: Dim request As HttpRequest request.InitializeGet(URL & fromCountry & "&ToCurrency=" & toCountry) request.Timeout = 10000 'set timeout to 10 seconds If HttpClient1.Execute(request, 1) = False Then Return 'Will be false if their is already a running task (with the same id).
We are setting the timeout to 10 seconds which is quite short. The default is 30 seconds. The target web service is pretty unstable, which makes things more interesting. I prefer it to fail fast in our case. HttpClient.Execute method receives two parameters. The first is the request object and the second is the Task ID. This integer will be passed back in the ResponseSuccess or ResponseError events. It allows you to differentiate between different tasks that may be running in the background. HttpClient.Execute will return false if their is already a running task with the same ID. This helps you prevent unnecessary multiple requests. You can also check the status of running tasks with the IsBackgroundTaskRunning keyword. Once the response is ready, ResponseSuccess or ResponseError will be raised. If things went well, we can now read the response, find the rate and display it. Otherwise we display a "toast" message with the error message. As I wrote above, this specific web service seems to be unstable so your experience may vary. State As discussed in the life cycle tutorial we are required to save and restore the state of the application. In our case the state is made of the values in the text boxes and the current selected currencies.
48
The following type and variable are declared in Sub Process_Globals: Code: Type StateType (TextUp As String, TextDown As String, _ IndexUp As Int, IndexDown As Int) Dim State As StateType 'This must be a process variable as it stores the state 'and should not be released when the activity is destroyed.
On the first run we set its values with the default values we want: Code: Sub ResetState 'set the starting state State.TextUp = 1 State.TextDown = "" State.IndexUp = 0 'USD State.IndexDown = 43 'Euro End Sub
Later we save and read it as needed: Code: Sub Activity_Resume txtUp.Text = State.TextUp txtDown.Text = State.TextDown spnrUp.SelectedIndex = State.IndexUp spnrDown.SelectedIndex = State.IndexDown End Sub Sub Activity_Pause (UserClosed As Boolean) If UserClosed Then ResetState 'reset the state to the initial settings. Else State.TextUp = txtUp.Text State.TextDown = txtDown.Text State.IndexUp = spnrUp.SelectedIndex state.IndexDown = spnrDown.SelectedIndex End If End Sub
In Activity_Resume we read the values and set the required views. Note that Activity_Resume is called right after Activity_Create. So it will also be called on the first time we run the application. In Activity_Pause we save the value in the state object (which is a process variable). Note that if the user pressed on the back key (which means that he wants to close our application) we return the state to the initial state. Therefore the user will see a "clean new" application the next time he will run our application.
49
It is worth paying attention to this line: Code: CurrentTask.FromCurrency.SelectedItem.SubString2(0, 3)
CurrentTask is of type MyTag. It has a field named FromCurrency which is of type Spinner. Spinner has a property named SelectedItem which returns a String. String has a method named Substring2. Also note that this code is valid: "abcd".Substring(2) The complete code (file is also attached): Code: 'Activity module Sub Process_Globals Dim countries As List Dim URL As String URL = "http://www.webservicex.net/CurrencyConvertor.asmx/ConversionRate?FromC urrency=" Dim HttpClient1 As HttpClient Type StateType (TextUp As String, TextDown As String, _ IndexUp As Int, IndexDown As Int) Dim State As StateType 'This must be a process variable as it stores the state 'and should not be released when the activity is destroyed. End Sub Sub Globals Dim txtUp, txtDown As EditText Dim spnrUp, spnrDown As Spinner Dim btnUp, btnDown As Button Type MyTag (FromValue As EditText, ToValue As EditText, _ FromCurrency As Spinner, ToCurrency As Spinner) Dim CurrentTask As MyTag End Sub Sub ResetState 'set the starting state State.TextUp = 1 State.TextDown = "" State.IndexUp = 0 'USD State.IndexDown = 43 'Euro End Sub Sub Activity_Create(FirstTime As Boolean) If FirstTime Then Log("************************")
50
'load the list of countries countries = File.ReadList(File.DirAssets, "CountryCodes.txt") 'initialize the HttpClient object which is responsible for all communication. HttpClient1.Initialize("HttpClient1") ResetState End If Activity.LoadLayout("layout1") spnrUp.AddAll(countries) spnrDown.AddAll(countries) Dim t1 As MyTag t1.FromValue = txtUp t1.ToValue = txtDown t1.FromCurrency = spnrUp t1.ToCurrency = spnrDown btnDown.Tag = t1 Dim t2 As MyTag t2.FromValue = txtDown t2.ToValue = txtUp t2.FromCurrency = spnrDown t2.ToCurrency = spnrUp btnUp.Tag = t2 End Sub Sub Activity_Resume txtUp.Text = State.TextUp txtDown.Text = State.TextDown spnrUp.SelectedIndex = State.IndexUp spnrDown.SelectedIndex = State.IndexDown End Sub Sub Activity_Pause (UserClosed As Boolean) If UserClosed Then ResetState 'reset the state to the initial settings. Else State.TextUp = txtUp.Text State.TextDown = txtDown.Text State.IndexUp = spnrUp.SelectedIndex state.IndexDown = spnrDown.SelectedIndex End If End Sub Sub btn_Click Dim btn As Button btn = Sender 'Fetch the actual button that raised this event. CurrentTask = btn.Tag 'Take the object from its Tag property. Dim fromCountry, toCountry As String fromCountry = CurrentTask.FromCurrency.SelectedItem.SubString2(0, 3) 'get the currency code
51
toCountry = CurrentTask.ToCurrency.SelectedItem.SubString2(0, 3) Dim request As HttpRequest request.InitializeGet(URL & fromCountry & "&ToCurrency=" & toCountry) request.Timeout = 10000 'set timeout to 10 seconds If HttpClient1.Execute(request, 1) = False Then Return 'Will be false if their is already a running task (with the same id). ProgressDialogShow("Calling server...") End Sub Sub HttpClient1_ResponseSuccess (Response As HttpResponse, TaskId As Int) Log("ResponseSuccess") ProgressDialogHide Dim result As String result = Response.GetString("UTF8") 'Convert the response to a string Log(result) Dim rate As Double 'Parse the result i = result.IndexOf(".NET/") If i = -1 Then Msgbox("Invalid response.", "Error") Return End If i2 = result.IndexOf2("<", i + 1) rate = result.substring2(i + 7, i2) Log("Rate = " & rate) If IsNumber(CurrentTask.FromValue.Text) = False Then Msgbox("Please enter a valid number.", "Error") Return End If 'Set the answer CurrentTask.ToValue.Text = Round2(CurrentTask.FromValue.Text * rate, 2) End Sub Sub HttpClient1_ResponseError (Reason As String, StatusCode As Int, TaskId As Int) Log(Reason) Log(StatusCode) ProgressDialogHide msg = "Error connecting to server." If reason <> Null Then msg = msg & CRLF & Reason ToastMessageShow (msg, True) End Sub
52
Working with files Many applications require access to a persistent storage. The two most common storage types are files and databases. We will cover text files in this tutorial. The predefined Files object has several utility methods for working with text files which are pretty easy to use. Files locations - There are several important locations where you can read or write files. File.DirAssets The assets folder includes the files that were added with the file manager. These files are read-only. You can not create new files in this folder (which is actually located inside the apk file). File.DirInternal / File.DirInternalCache These two folders are stored in the main memory and are private to your application. Other applications cannot access these files. The cache folder may get deleted by the OS if it needs more space. File.DirRootExternal The storage card root folder. File.DirDefaultExternal The default folder for your application in the SD card. The folder is: /Android/data//files/ It will be created if required. Note that calling any of the two above properties will add the EXTERNAL_STORAGE permission to your application. Tip: You can check if there is a storage card and whether it is available with File.ExternalReadable and File.ExternalWritable. The predefined File object (predefined means that you do not need to declare it yourself) includes several methods for writing and reading to files. You can also use TextReader and TextWriter to do it manually. Note that TextReader and TextWriter are not limited to files and can work with other streams.
53
TextReader and TextWriter have an advantage over the File read/write methods when working with large files. The File methods read the file completely and store its content in memory. In many cases this is the most convenient solution, however if you work with large files (more than 1-2mb) you may prefer to work with TextReader or TextWriter. File.WriteString - Writes the given text to a new file. File.ReadString - Reads a file and returns it content as a string. File.WriteList - Writes all values stored in a list to a file. All values are converted to string type if required. Each value will be stored in its own line. Note that if a value contains the new line character it will saved over more than one line and when you read it, it will be read as multiple items. File.ReadList - Reads a file and stores each line as an item in a list. File.WriteMap - Takes a map object which holds pairs of key and value elements and stores it in a text file. The file format is known as Java Properties file: .properties Wikipedia, the free encyclopedia The file format is not too important unless the file is supposed to be edited manually. This format makes it easy to edit it manually. One common usage of File.WriteMap is to save a map of "settings" to a file. File.ReadMap - Reads a properties file and returns its key/value pairs as a Map object. Note that the order of entries returned might be different than the original order. Example: Code: Sub Process_Globals End Sub Sub Globals End Sub Sub Activity_Create(FirstTime As Boolean) If File.ExternalWritable = False Then Msgbox("Cannot write on storage card.", "") Return End If SaveStringExample ReadStringExample WriteListExample ReadListExample WriteMapExample ReadMapExample WriteTextWriter
54
ReadTextReader End Sub Sub SaveStringExample File.WriteString(File.DirRootExternal, "String.txt", _ "This is some string" & CRLF & "and this is another one.") End Sub Sub ReadStringExample Msgbox(File.ReadString(File.DirRootExternal, "String.txt"), "") End Sub Sub WriteListExample Dim List1 As List List1.Initialize For i = 1 To 100 List1.Add(i) Next File.WriteList(File.DirRootExternal, "List.txt", List1) End Sub Sub ReadListExample Dim List1 As List 'We are not initializing the list because it just holds the list that returns from File.ReadList List1 = File.ReadList(File.DirRootExternal, "List.txt") Msgbox("List1.Size = " & List1.Size & CRLF & "The third item is: " & List1.Get(2), "") End Sub Sub WriteMapExample Dim Map1 As Map Map1.Initialize For i = 1 To 10 Map1.Put("Key" & i, "Value" & i) Next File.WriteMap(File.DirRootExternal, "Map.txt", Map1) End Sub Sub ReadMapExample Dim Map1 As Map 'Again we are not initializing the map. Map1 = File.ReadMap(File.DirRootExternal, "Map.txt") 'Append all entries to a string builder Dim sb As StringBuilder sb.Initialize sb.Append("The map entries are:").Append(CRLF) For i = 0 To Map1.Size - 1 sb.Append("Key = ").Append(Map1.GetKeyAt(i)).Append(", Value = ") sb.Append(Map1.GetValueAt(i)).Append(CRLF)
55
Next Msgbox(sb.ToString,"") End Sub Sub WriteTextWriter Dim TextWriter1 As TextWriter TextWriter1.Initialize(File.OpenOutput(File.DirRootExternal, "Text.txt", False)) For i = 1 To 10 TextWriter1.WriteLine("Line" & i) Next TextWriter1.Close End Sub Sub ReadTextReader Dim TextReader1 As TextReader TextReader1.Initialize(File.OpenInput(File.DirRootExternal, "Text.txt")) Dim line As String line = TextReader1.ReadLine Do While line <> Null Log(line) 'write the line to LogCat line = TextReader1.ReadLine Loop TextReader1.Close End Sub
56
Views (controls) and Dialogs Many users who start to develop Android applications may be frustrated by the fact that they don't find the control they are looking for. Its even worse, for some mysterious reason there are no "Controls" at all. Instead there are "Views". This page shows screenshots of all the available views and dialogs:
Views Button:
Checkbox:
EditText (TextBox): Can also be multiline.
ImageView:
Label:
57
ListView (ListBox):
Panel: Can hold other views (and load layout files).
ProgressBar:
RadioButton:
58
ScrollView:
SeekBar (TrackBar):
Spinner (ComboBox):
TabHost (TabControl):
ToggleButton:
59
WebView:
Dialogs Basic4android dialogs are blocking modal dialogs. This means that the code execution stops while the dialog is shown. Some of the dialogs are shown by calling a keyword (like Msgbox). Other dialogs are included in Andrew's excellent dialogs library.
Internal dialogs InputList:
InputMultiList:
60
Msgbox (or Msgbox2):
Dialogs library ColorDialog:
61
ColorPickerDialog:
DateDialog:
InputDialog:
NumberDialog:
62
TimeDialog:
FileDialog:
63
GPS tutorial The GPS is an important feature of many Android devices. Fortunately it is pretty easy to work with it. In this tutorial we will cover a simple program that shows the current position as well as the satellites status.
The GPS functionality is packaged in the GPS library. Therefore we should first add a reference to this library:
64
There are three types of relevant objects. The main one is GPS. The GPS manages the connection and events. The second is Location. A Location is a structure that holds the data available regarding a specific "fix". The data includes the latitude and longitude coordinates, the time (expressed as ticks) of this fix and other information like bearing, altitude and so on. It may happen that not all information is available (due to poor reception for example). The Location also includes other functionalities like calculating the distance and bearing to another location and methods to convert the coordinates string formats. Usually you will work with Location objects passed to you in the LocationChanged events. However you can also initialize such objects yourself (this is useful for calculating distance and bearing between locations). The last one is GPSSatellite. This is a structure that holds various information regarding the currently known satellites. It is passed to you in GPSStatus event. Back to GPS. The GPS object should be declared as a Process_Global object. Otherwise new instances will be created each time the activity is recreated. The first step is to initialize the object. Like many other Initialize methods this one expects an EventName parameter. This is the prefix for the events that will be raised by the GPS object. Here is the complete code: Code: Sub Process_Globals Dim GPS1 As GPS End Sub Sub Globals Dim lblLon As Label Dim lblLat As Label Dim lblSpeed As Label Dim lblSatellites As Label End Sub Sub Activity_Create(FirstTime As Boolean) If FirstTime Then GPS1.Initialize("GPS") End If Activity.LoadLayout("1") End Sub Sub Activity_Resume If GPS1.GPSEnabled = False Then
65
ToastMessageShow("Please enable the GPS device.", True) StartActivity(GPS1.LocationSettingsIntent) 'Will open the relevant settings screen. Else GPS1.Start(0, 0) 'Listen to GPS with no filters. End If End Sub Sub Activity_Pause (UserClosed As Boolean) GPS1.Stop End Sub Sub GPS_LocationChanged (Location1 As Location) lblLat.Text = "Lat = " & Location1.ConvertToMinutes(Location1.Latitude) lblLon.Text = "Lon = " & Location1.ConvertToMinutes(Location1.Longitude) lblSpeed.Text = "Speed = " & Location1.Speed End Sub Sub GPS_UserEnabled (Enabled As Boolean) ToastMessageShow("GPS device enabled = " & Enabled, True) End Sub Sub GPS_GpsStatus (Satellites As List) lblSatellites.Text = "Satellites:" & CRLF For i = 0 To Satellites.Size - 1 Dim Satellite As GPSSatellite Satellite = Satellites.Get(i) lblSatellites.Text = lblSatellites.Text & CRLF & Satellite.Prn & _ " " & Satellite.Snr & " " & Satellite.UsedInFix & " " & Satellite.Azimuth _ & " " & Satellite.Elevation Next End Sub
The next step is to tell the GPS to start listening for data. The GPS can consume quite a lot of battery. Therefore it is recommended to stop using the GPS whenever it is not necessary. It is recommended to start listening in Activity_Resume and stop listening in Activity_Pause. It may happen that the user has turned off the GPS. Due to privacy concerns the Android OS doesn't allow you to turn the GPS on programatically. The best thing that you can do is to ask the user to enable the GPS device. The following code shows a message if the GPS is not enabled and also opens the GPS control panel so the user only needs to check the GPS option: Code:
66
Sub Activity_Resume If GPS1.GPSEnabled = False Then ToastMessageShow("Please enable the GPS device.", True) StartActivity(GPS1.LocationSettingsIntent) 'Will open the relevant settings screen. Else GPS1.Start(0, 0) 'Listen to GPS with no filters. End If End Sub
If the GPS is enabled we are starting to listen for data. The Start method receives two values which are the minimum time (milliseconds) and the minimum distance (meters) between events. An event will be raised when at least one of these criterions is met. This can help saving battery. In our case we are passing 0 and therefore will get all fix events. The GPS raises three events: - GPS_LocationChanged (Location1 As Location) This is the main event. Location1 holds the data for the new fix. -GPS_GpsStatus (Satellites As List) This event allows you to display information about the currently available satellites. Note that not all satellites in the list are actually used for calculating the last fix. So it is possible that the list will include several satellites but still the reception is not good enough for a fix. - GPS_UserEnabled (Enabled As Boolean) This event is raised whenever the user changes the status of the GPS device. It is also raised right after calling Start. The program is attached.
67
MediaPlayer – Playing music The MediaPlayer is used to play audio files. MediaPlayer supports formats like: mp3, midi, wave and ogg. The full list is available here: Android Supported Media Formats | Android Developers In order to play an audio file we first need to load the file: Code: MediaPlayer1.Load(File.DirAssets, "IsawHerStandingThere.mid")
In this case the file was added with the file manager and therefore File.DirAssets is used. Now we can start playing the file with: Code: MediaPlayer1.Play
and pause the playback with: Code: MediaPlayer.Pause
Calling Play will resume from the same position. Note that you can only call Pause while MediaPlayer is playing. You can call MediaPlayer.Load at any point (after initialization), and load a new file. MediaPlayer should be declared in Process_Globals otherwise a new instance will be created each time the activity is recreated. The program attached is a small program that allows the user to see the playback progress and to change the position.
68
This is the code: Code: Sub Process_Globals Dim MediaPlayer1 As MediaPlayer Dim timer1 As Timer End Sub Sub Globals Dim barPosition As SeekBar Dim barVolume As SeekBar Dim lblPosition As Label Dim Looping As ToggleButton End Sub Sub Activity_Create(FirstTime As Boolean) If FirstTime Then MediaPlayer1.Initialize( ) MediaPlayer1.Load(File.DirAssets, "IsawHerStandingThere.mid") Timer1.Initialize("timer1", 1000) End If Activity.LoadLayout("1") Looping_CheckedChange(Looping.Checked) 'set the default value End Sub Sub Activity_Resume MediaPlayer1.Play timer1.Enabled = True timer1_Tick 'don't wait one second for the UI to update. End Sub Sub Activity_Pause (UserClosed As Boolean) If MediaPlayer1.IsPlaying Then MediaPlayer1.Pause timer1.Enabled = False End Sub Sub timer1_Tick If MediaPlayer1.IsPlaying Then barPosition.Value = MediaPlayer1.Position / MediaPlayer1.Duration * 100 lblPosition.Text = "Position: " & ConvertToTimeFormat(MediaPlayer1.Position) & _ " (" & ConvertToTimeFormat(MediaPlayer1.Duration) & ")" End If End Sub 'converts milliseconds to m:ss format. Sub ConvertToTimeFormat(ms As Int) As String Dim seconds, minutes As Int seconds = Round(ms / 1000) minutes = Floor(seconds / 60) seconds = seconds Mod 60
69
Return NumberFormat(minutes, 1, 0) & ":" & NumberFormat(seconds, 2, 0) 'ex: 3:05 End Sub Sub barVolume_ValueChanged (Value As Int, UserChanged As Boolean) MediaPlayer1.SetVolume(barVolume.Value / 100, barVolume.Value / 100) End Sub Sub barPosition_ValueChanged (Value As Int, UserChanged As Boolean) If UserChanged = False Then Return 'the value was changed programmatically MediaPlayer1.Position = Value / 100 * MediaPlayer1.Duration If MediaPlayer1.IsPlaying = False Then 'this can happen when the playback reached the end and the user changes the position MediaPlayer1.Play End If timer1_Tick 'immediately update the progress label End Sub Sub Looping_CheckedChange(Checked As Boolean) MediaPlayer1.Looping = Checked End Sub
We are initializing MediaPlayer only once when the application starts. Playing starts when the activity resumes, which happens right after the Create event. A timer is used to check the playback position every second and update the label and seek bar. Note that the seekbar ValueChanged position includes a boolean value named UserChanged which you can use to differentiate between changes done by the user (by dragging the thumb) and changes done programmatically. The Looping property determines whether playback will restart automatically when it reaches the end.
70
ListView tutorial The ListView control is a very powerful control. It allows you to show short or long lists in a very "sleek" way. Creating a simple list is easy: Code: Sub Globals Dim ListView1 As ListView End Sub Sub Activity_Create(FirstTime As Boolean) ListView1.Initialize("ListView1") For i = 1 To 300 ListView1.AddSingleLine("Item #" & i) Next Activity.AddView(ListView1, 0, 0, 100%x, 100%y) End Sub Sub ListView1_ItemClick (Position As Int, Value As Object) Activity.Title = Value End Sub
The ListView can be added programmatically or with the designer. For now the items must be added with code. About the code: - ListView1.Initialize("ListView1") - Here we initialize the list and set the event name property to ListView1. Which means that in order to catch related events we should have subs like: ListView1_ItemClick.
71
- ListView1.AddSingleLine - adds a single line item. - Activity.AddView(ListView1, 0, 0, 100%x, 100%y) - Note the use of the percentage units. We are setting the width and height to the values of the containing activity. There are currently three types of items: single line, two lines and two lines and bitmap. Each type can be customized. The default look is:
This is the relevant code: Code: Dim Bitmap1 As Bitmap Bitmap1.Initialize(File.DirAssets, "button.gif") For i = 1 To 300 ListView1.AddSingleLine("Item #" & i) ListView1.AddTwoLines("Item #" & i, "This is the second line.") ListView1.AddTwoLinesAndBitmap("Item #" & i, "This is the second line.", Bitmap1) Next
We can set different bitmaps to different items. Note that this code loads an image file named button.gif. This file should be added to the Files tab (in the right pane). You can download the project which is attached to this post. Customizing each type Each of the three types can be customized. The change will affect all items of that type. The ListView has three "models" which are stored under: - SingleLineLayout - TwoLinesLayout - TwoLinesAndBitmap
72
Each of this model has an ItemHeight property, a Background property and one or more views properties. Again, if you change any of these properties it will affect all the items of this type. Example of customizing the single line items: Code: ListView1.SingleLineLayout.ItemHeight = 100dip ListView1.SingleLineLayout.Label.TextSize = 20 ListView1.SingleLineLayout.Label.TextColor = Colors.Blue ListView1.SingleLineLayout.Label.Gravity = Gravity.CENTER For i = 1 To 300 ListView1.AddSingleLine("Item #" & i) ListView1.AddTwoLines("Item #" & i, "This is the second line.") ListView1.AddTwoLinesAndBitmap("Item #" & i, "This is the second line.", Bitmap1) Next
Result:
Note that the ItemHeight is set to 100dip. The 'dip' unit causes it to automatically scale the height based on the current device scale. For the TextSize it is a mistake to use dip units as the text size is already measured in scaled units. The above code is equivalent to this code (which is a bit more clear): Code: ListView1.SingleLineLayout.ItemHeight = 100dip Dim label1 As Label label1 = ListView1.SingleLineLayout.Label 'set the label to the model label. label1.TextSize = 20
In a similar way you can change the way the other types look. The other types have additional views: SecondLabel and ImageView. Return value First notice that there is no selected item. The reason is that the combination of scrolling the list with finger and scrolling with the wheel or keyboard makes it non relevant. You should catch the ItemClick event and then handle the clicked item. The value of the clicked item is passed as a parameter. Now, what is a value of an item??? By default this is the text stored in the first line. However you can change it to any object you like by using: AddSingleLine2, AddTwoLines2 and AddTwoLinesAndBitmap2 methods. These methods receive an additional parameter which is the return value. This allows you to pass more information as required by your application. Background optimization There is a hidden assumption that the background behind the ListView is solid black. If you set the background to something else like a gradient background or image you will see that during scrolling the background disappears. You can change the background scrolling color with the ScrollingBackgroundColor property. If the background is not solid color set it to Colors.Transparent. Example (the activity background is a gradient): Code: Dim GD As GradientDrawable GD.Initialize("TR_BL", Array As Int(Colors.Gray, Colors.LightGray)) Activity.Background = GD ListView1.ScrollingBackgroundColor = Colors.Transparent
Tips If you want a single line item with a bitmap (and do not need two lines and a bitmap), you can set the visible property of the second label to false. If you have many items then you should enable the fast scroller: Code: ListView1.FastScrollEnabled = true
74
A short example is attached.
75
ScrollView tutorial The ScrollView is a very useful container which allows you to show many other views on a small screen. The ScrollView holds an inner panel view which actually contains the other views. The user vertically scrolls the inner panel as needed. In this example we will load images from the sdcard and add those to a ScrollView.
Adding views to a ScrollView is done by calling: Code: ScrollView.Panel.AddView(...)
In order to avoid "out of memory" errors we use LoadBitmapSample instead of LoadBitmap. LoadBitmapSample accepts two additional parameters: MaxWidth and MaxHeight. When it loads a bitmap it will first get the bitmap original dimensions and then if the bitmap width or height are larger than MaxWidth or MaxHeight the bitmap will be subsampled accordingly. This means that the loaded bitmaps will have lower resolution than the original bitmap. The width / height ratio is preserved. The following code sets the inner panel height and adds an ImageView for each loaded bitmap: Code: ScrollView1.Panel.Height = 200dip * Bitmaps.Size 'Set the inner panel height according to the number of images. For i = 0 To Bitmaps.Size - 1 Dim iv As ImageView 'create an ImageView for each bitmap
76
iv.Initialize("") 'not interested in any events so we pass empty string. Dim bd As BitmapDrawable bd.Initialize(Bitmaps.Get(i)) iv.Background = bd 'set the background of the image view. 'add the image view to the scroll bar internal panel. ScrollView1.Panel.AddView(iv, 5dip, 5dip + i * 200dip, ScrollView1.Width - 10dip, 190dip) Next
The code that is loading the bitmaps looks for jpg files under /sdcard/Images If you want to run this program on the emulator you will first need to create this folder and copy some images to it. This is done with the "adb" command, that comes with Android SDK. Open a shell console (Windows Start - Run - Cmd). Go to the sdk tools folder and issue: Code: c:\android-sdk-windows\tools> adb -e shell mkdir /sdcard/Images
The -e parameter tells adb to send the command to the only connected emulator. The command is mkdir with the name of the folder. Note that Android file system is case sensitive. Now we need to copy some images to this folder. This is done with the push command: Code: adb -e push "C:\temp" /sdcard/Images
This will copy all files under c:\temp to the Images folder. The emulator is very slow compared to a real device. While on a real device 50 large images load in 2-3 seconds. It can take a long time for the emulator to load a few small images. I recommend you to copy 2 - 3 small images at most. Also the experience of the ScrollView on the emulator cannot be compared to a real device (with capacitive screen). The program is attached.
77
TabHost tutorial The TabHost view is a very important view. It allows you to add several layouts into the same activity.
The designer currently doesn't support adding views directly to the TabHost. You can only add the TabHost and set its layout:
There are several ways to add tab pages. Usually it is recommended to create a layout file
78
in the designer for each page and then load it. The designer treats every layout file separately. It is your responsibility to set the views names to distinct names (this is only required for views that you plan to access programmatically). This is done with AddTab or AddTabWithIcon. Example: Code: Sub Activity_Create(FirstTime As Boolean) Activity.LoadLayout("main") Dim bmp1, bmp2 As Bitmap bmp1 = LoadBitmap(File.DirAssets, "ic.png") bmp2 = LoadBitmap(File.DirAssets, "ic_selected.png") TabHost1.AddTabWithIcon ("Name", bmp1, bmp2, "page1") 'load the layout file of each page TabHost1.AddTab("Color", "page2") TabHost1.AddTab("Animal", "page3") End Sub
AddTabWithIcon receives two bitmaps. There are actually two icons. One when the tab is selected and one when the tab is not selected. The guidelines recommend creating a dark version for the selected icon and a light version for the not selected icon. You can manually change the selected tab by setting the CurrentTab property. The example is attached.
79
FlickrViewer example – Download multiple images concurrently
This application gets this html page: Flickr: Explore interesting photos from the last 7 days in FlickrLand... , parses it and finds the 9 image links. It then sends a request for each of the images. Downloaded images are then displayed in the ImageViews. Each time you press on the Connect button 9 new images appear. Pressing on an image shows it larger in a second activity (note that the full image is not downloaded so the second activity just shows the thumbnail with its original size). If you run this program you can see that all requests are handled in the background and that images appear whenever they are ready. The TaskId plays an important role here. Each image request is sent with a TaskId between 0 to 8. This TaskId is later used to get the correct ImageView from the ImageViews array.
80
Two activities example This example demonstrates how to work with more than one activity and how to pass information between the activities. In this example we have two activities. One activity is the "main" activity. When the user presses a button a "list" activity will be displayed. The user will choose an item from the list and then return to the main activity:
It is recommended that you first read the Activities life cycle tutorial if you haven't read it before. In order to add a new or existing activity to your project you should choose Project - Add New / Existing Module. Variables declared in Sub Process_Globals are public variables and can be accessed from other activities. Therefore we will save the chosen value in such a variable. When the user presses on the "choose item" button we open the second activity: Code: Sub Button1_Click StartActivity(Activity2) End Sub
When we open an activity the current one is first paused and then the target activity is resumed (and created if needed). You can see it in the logs:
The second activity is pretty simple: Code:
81
Sub Process_Globals Dim result As String result = "" End Sub Sub Globals Dim ListView1 As ListView End Sub Sub Activity_Create(FirstTime As Boolean) Activity.LoadLayout("2") For i = 1 To 100 ListView1.AddSingleLine("Item #" & i) Next End Sub Sub Activity_Resume End Sub Sub Activity_Pause (UserClosed As Boolean) End Sub Sub ListView1_ItemClick (Position As Int, Value As Object) result = value 'store the value in the process global object. StartActivity(Main) 'show the main activity again End Sub
When the user presses on an item we store the value in the 'result' variable and we return to the main activity. The main activity Resume event will be raised. So in this event we check if 'result' variable is not empty and change the label's text. Code: Sub Activity_Resume If Activity2.result.Length > 0 Then Label1.Text = "You have chosen: " & Activity2.result Else Label1.Text = "Please press on the button and choose an item." End If End Sub
In more complex applications with more than two activities you can use a process global variable in the main activity. Before starting an activity you can set this variable to some constant and then in Sub Activity_Resume check the value of this variable to know which activity was started and act accordingly. The project is attached.
82
Building a linked list using Type keyword he Type keyword is used to create your own types or structures. You can use such types to create simple structures that group some values. However you can also use it to create more complex collections. In this tutorial we will create a linked list. A simpler usage of Type is demonstrated in the currency converter tutorial. A linked list is a collection of elements. Each element has a value field and a 'next element' object. The 'next element' is actually a reference to the next element in the list. The type is declared like this: Code: Sub Process_Globals Type Element (NextElement As Element, Val As Int) Dim Head As Element 'declare a variable of that type. Dim Last As Element End Sub
The interesting thing about this declaration is that we are using the current type as a field. The ability to declare such recursive types is very powerful. Before we can access any of the type fields, it should be initialized by calling its Initialize method. Note that if the fields were initialized automatically then it would not have been possible to create such recursive types. Note that if your type only includes numeric fields and strings then there is no need to call Initialize (though nothing bad will happen if you do call it). Once we declared a type we can use it like any other "regular" types. It can be passed to subs, you can create an array of it and so on. Lets build our list: Code: Sub InitializeList (Value As Int) Head.Initialize Head.Val = Value Last = Head 'The last item is currently the head. End Sub
The InitializeList sub initializes the head element, sets its value and sets the Last variable to reference the head element (as this is the only item in the list). Adding an item: Code: Sub AddElement(Value As Int)
83
'create a new element Dim e As Element e.Initialize e.Val = Value Last.NextElement = e 'set the NextElement of the current last element to the new one. Last = e 'set the last variable to point to the new element. End Sub
We are creating a new element. The current last element NextElement field is set to the new element and eventually the last variable is updated to the new element. Going over all the items in the list is done by starting with the head element and then going forward by calling NextElement. When we reach an uninitialized element we stop. Code: Sub ListToString As String Dim e As Element Dim sb As StringBuilder sb.Initialize e = Head Do While e.IsInitialized = True sb.Append(e.Val).Append(CRLF) e = e.NextElement Loop Return sb.ToString End Sub
84
SQL tutorial This tutorial covers the SQL library and its usage with Basic4android. There are many general SQL tutorials that cover the actual SQL language. If you are not familiar with SQL it is recommended to start with such a tutorial. SQL Introduction Android uses SQLite which is an open source SQL implementation. Each implementation has some nuances. The following two links cover important information regarding SQLite. SQLite syntax: Query Language Understood by SQLite SQLite data types: Datatypes In SQLite Version 3 SQL in Basic4android The first step is to add a reference to the SQL library. This is done by going to the Libraries tab and checking SQL. There are two types in this library. An SQL object gives you access to the database. The Cursor object allows you to process queries results. Usually you will want to declare the SQL object as a process global object. This way it will be kept alive when the activity is recreated. SQLite stores the database in a single file. When we initialize the SQL object we pass the path to a database file (which can be created if needed). Code: Sub Process_Globals Dim SQL1 As SQL End Sub Sub Globals End Sub Sub Activity_Create(FirstTime As Boolean) If FirstTime Then SQL1.Initialize(File.DirDefaultExternal, "test1.db", True) End If CreateTables FillSimpleData LogTable1 InsertManyRows Log("Number of rows = " & SQL1.ExecQuerySingleResult("SELECT count(*) FROM table1"))
85
InsertBlob 'stores an image in the database. ReadBlob 'load the image from the database and displays it. End Sub
The SQL1 object will only be initialized once when the process starts. In our case we are creating it in the sd card. The last parameter (CreateIfNecessary) is True so the file will be created if it doesn't exist. There are three types of methods that execute SQL statements. ExecNonQuery - Executes a "writing" statement and doesn't return any result. This can be for example: INSERT, UPDATE or CREATE TABLE. ExecQuery - Executes a query statement and returns a Cursor object that is used to process the results. ExecQuerySingleResult - Executes a query statement and returns the value of the first column in the first row in the result set. This method is a shorthand for using ExecQuery and reading the value with a Cursor. We will analyze the example code: Code: Sub CreateTables SQL1.ExecNonQuery("DROP TABLE IF EXISTS table1") SQL1.ExecNonQuery("DROP TABLE IF EXISTS table2") SQL1.ExecNonQuery("CREATE TABLE table1 (col1 TEXT , col2 INTEGER, col3 INTEGER)") SQL1.ExecNonQuery("CREATE TABLE table2 (name TEXT, image BLOB)") End Sub
The above code first deletes the two tables if they exist and then creates them again. Code: Sub FillSimpleData SQL1.ExecNonQuery("INSERT INTO table1 VALUES('abc', 1, 2)") SQL1.ExecNonQuery2("INSERT INTO table1 VALUES(?, ?, ?)", Array As Object("def", 3, 4)) End Sub
In this code we are adding two rows. SQL.ExecNonQuery2 receives two parameters. The first parameter is the statement which includes question marks. The question marks are then replaced with values from the second List parameter. The List can hold numbers, strings or arrays of bytes (blobs). Arrays are implicitly converted to lists so instead of creating a list we are using the Array keyword to create an array of objects. Code: 86
Sub LogTable1 Dim Cursor1 As Cursor Cursor1 = SQL1.ExecQuery("SELECT col1, col2, col3 FROM table1") For i = 0 To Cursor1.RowCount - 1 Cursor1.Position = i Log("************************") Log(Cursor1.GetString("col1")) Log(Cursor1.GetInt("col2")) Log(Cursor1.GetInt("col3")) Next Cursor1.Close End Sub
This code uses a Cursor to log the two rows that were previously added. SQL.ExecQuery returns a Cursor object. Then we are using the For loop to iterate over all the results. Note that before reading values from the Cursor we are first setting its position (the current row). Code: Sub InsertManyRows SQL1.BeginTransaction Try For i = 1 To 500 SQL1.ExecNonQuery2("INSERT INTO table1 VALUES ('def', ?, ?)", Array As Object(i, i)) Next SQL1.TransactionSuccessful Catch Log(LastException.Message) End Try SQL1.EndTransaction End Sub
This code is an example of adding many rows. Internally a lock is acquired each time a "writing" operation is done. By explicitly creating a transaction the lock is acquired once. The above code took less than half a second to run on a real device. Without the BeginTransaction / EndTransaction block it took about 70 seconds. A transaction block can also be used to guarantee that a set of changes were successfully done. Either all changes are made or none are made. By calling SQL.TransactionSuccessful we are marking this transaction as a successful transaction. If you omit this line, all the 500 INSERTS will be ignored. It is very important to call EndTransaction eventually. Therefore the transaction block should usually look like: Code: SQL1.BeginTransaction
87
Try 'Execute the sql statements. SQL.TransactionSuccessful Catch 'the transaction will be cancelled End Try SQL.EndTransaction
Note that using transactions is only relevant when doing "writing" operations. Blobs The last two methods write an image file to the database and then read it and set it as the activity background. Code: Sub InsertBlob 'convert the image file to a bytes array Dim InputStream1 As InputStream InputStream1 = File.OpenInput(File.DirAssets, "smiley.gif") Dim OutputStream1 As OutputStream OutputStream1.InitializeToBytesArray(1000) File.Copy2(InputStream1, OutputStream1) Dim Buffer() As Byte 'declares an empty array Buffer = OutputStream1.ToBytesArray 'write the image to the database SQL1.ExecNonQuery2("INSERT INTO table2 VALUES('smiley', ?)", Array As Object(Buffer)) End Sub
Here we are using a special type of OutputStream which writes to a dynamic bytes array. File.Copy2 copies all available data from the input stream into the output stream. Then the bytes array is written to the database. Code: Sub ReadBlob Dim Cursor1 As Cursor 'Using ExecQuery2 is safer as it escapes special characters automatically. 'In this case it doesn't really matter. Cursor1 = SQL1.ExecQuery2("SELECT image FROM table2 WHERE name = ?", Array As String("smiley")) Cursor1.Position = 0 Dim Buffer() As Byte 'declare an empty byte array Buffer = Cursor1.GetBlob("image") Dim InputStream1 As InputStream InputStream1.InitializeFromBytesArray(Buffer, 0, Buffer.Length) Dim Bitmap1 As Bitmap Bitmap1.Initialize2(InputStream1)
88
InputStream1.Close Activity.SetBackgroundImage(Bitmap1) End Sub
Using a Cursor.GetBlob we fetch the previously stored image. Now we are using an input stream that reads from this array and load the image.
89
XML parsing with the XMLSax library The XmlSax library provides an XML Sax parser. This parser sequentially reads the stream and raises events at the beginning and end of each element. The developer is responsible to do something useful with those events. There are two events: Code: StartElement (Uri As String, Name As String, Attributes As Attributes) EndElement (Uri As String, Name As String, Text As StringBuilder)
The StartElement is raised when an element begins. This event includes the element's attributes list. EndElement is raised when an element ends. This event includes the element's text. In this example we will parse the forum RSS feed. RSS is formatted using XML. A simplified example of this RSS is: Code: Basic4ppc / Basic4android - Android programming http://www.basic4ppc.com/forum Basic4android - android programming and development60http://www.basic4ppc.com/forum/images/misc/rss.jpgBasic4ppc / Basic4android - Android programming http://www.basic4ppc.com/forum Phone library was updated - V1.10 http://www.basic4ppc.com/forum/additional-librariesofficial-updates/6859-phone-library-updated-v1-10-a.html Sun, 12 Dec 2010 09:27:38 GMThttp://www.basic4ppc.com/forum/additional-librariesofficial-updates/6859-phone-library-updated-v1-10-a.html ...MORE ITEMS HERE
90
The first line is part of the XML protocol and is ignored. On the second line the StartElement event will be raised with "Name = rss" and the attributes will include the "version" field. The EndElement of the rss element will only be called on the last line: . We will populate a list view with all items parsed from an offline file. When the user will press on an item we will open the browser with the relevant link. Every item represents a forum thread.
For each item we are interested in two values. The title and the link. The SaxParser object includes a handy list that holds the names of all the current parents elements. This is useful as it will help us find the "correct" 'title' and 'link' elements. The correct elements are the ones under the 'item' element. The parsing code in this case is pretty simple: Code: Sub Parser_StartElement (Uri As String, Name As String, Attributes As Attributes) End Sub
91
Sub Parser_EndElement (Uri As String, Name As String, Text As StringBuilder) If parser.Parents.IndexOf("item") > -1 Then If Name = "title" Then Title = Text.ToString Else If Name = "link" Then Link = Text.ToString End If End If If Name = "item" Then ListView1.AddSingleLine2(Title, Link) 'add the title as the text and the link as the value End If End Sub
Title and Link are global variables. We are only using EndElement events in this program. First we check if we are inside an 'item' element. If this is the case we check the actual element name and save it if it is 'title' or 'link'. If the current element is 'item' it means that we are done parsing an item. So we add the data collected to the list view. We are using ListView.AddSingleLine2. This method receives two values. The first is the item text and the second is the value that will return when the user will click on this item. In this case we are storing the link as the return value. Later we will use it to open the browser: Code: Sub ListView1_ItemClick (Position As Int, Value As Object) StartActivity(PhoneIntents1.OpenBrowser(Value)) 'open the brower with the link End Sub
The code that initiated the parsing is: Code: Dim in As InputStream in = File.OpenInput(File.DirAssets, "rss.xml") 'This file was added with the file manager. parser.Parse(in, "Parser") '"Parser" is the events subs prefix. in.Close
92
Take pictures with the internal camera The new Camera library allows you to take pictures using the back facing camera. The following steps are required for taking a picture: - Initialize a camera object. The Initialize method expects a Panel. The preview images will be displayed on this panel. - Wait for the Ready event. - In the Ready event Sub, call Camera.StartPreview to show the images preview. - Call TakePicture to take a picture. - The PictureTaken event will be raised with the picture passed as a bytes array (in JPEG format). - Call StartPreview again to restart the images preview. Only one process can access the camera at a time. Therefore you should release the camera when your activity is paused. Usually you will initialize the camera during Activity_Resume and pause it during Activity_Pause.
The following code saves the image: Code: Sub Camera1_PictureTaken (Data() As Byte) Dim out As OutputStream out = File.OpenOutput(File.DirRootExternal, "1.jpg", False) out.WriteBytes(data, 0, data.Length) out.Close ToastMessageShow("Image saved: " & File.Combine(File.DirRootExternal, "1.jpg"), True)
93
You can see in the attached code that the "take picture" button is only enabled when the camera is ready. For now the camera orientation is always set to landscape mode. Usually you will want to force the activity to also be in landscape mode. This is done by checking 'Lansdcape' in Project - Orientations Supported menu. On the emulator the preview images will show a moving check board.
94
Serial (Bluetooth) tutorial This tutorial covers the Serial library. This library allows you to connect with other Bluetooth devices using virtual serial ports (RFCOMM). Note that this example requires Core library v1.05 or above. The Serial library requires Android OS 2.0 or above (API level 5 or above). We will build a simple chat example which allows two connected devices to send text messages.
We created a process global object named Serial1 of type Serial. Usually it is a good idea to initialize process objects in Sub Activity_Create when FirstTime is True. This way the objects will be initialized exactly once. Code: Sub Activity_Create(FirstTime As Boolean) If FirstTime Then Serial1.Initialize("Serial1") Timer1.Initialize("Timer1", 200) End If Activity.LoadLayout("1") Activity.AddMenuItem("Connect", "mnuConnect") Activity.AddMenuItem("Disconnect", "mnuDisconnect")
95
End Sub
The next Sub running is Activity_Resume: Code: Sub Activity_Resume If Serial1.IsEnabled = False Then Msgbox("Please enable Bluetooth.", "") Else Serial1.Listen 'listen for incoming connections End If End Sub
Here we are checking if the Bluetooth device is enabled. If it is not enabled we ask the user to enable it. Note that by putting this code in Activity_Resume (and not Activity_Create) this test will happen every time the activity is resumed. So if the user goes to the settings screen, enables the device and then returns to our application we will now know that the Bluetooth is enabled. If the Bluetooth is enabled we start listening for incoming connections. This allows other devices to connect with our device. This is not required if you connect to a device that listens for connections (like external GPS for example). Note that calling Listen more than once doesn't do anything. So we are safe calling it this way. When the user presses on the Connect menu item we show the user the list of known paired devices. When the user clicks on a device name we fetch its MAC address from the map and connect to it:
96
Code: Sub mnuConnect_Click Dim PairedDevices As Map PairedDevices = Serial1.GetPairedDevices Dim l As List l.Initialize For i = 0 To PairedDevices.Size - 1 l.Add(PairedDevices.GetKeyAt(i)) 'add the friendly name to the list Next Dim res As Int res = InputList(l, "Choose device", -1) 'show list with paired devices If res <> DialogResponse.CANCEL Then Serial1.Connect(PairedDevices.Get(l.Get(res))) 'convert the name to mac address End If End Sub
The connection is not established immediately. It will happen in the background. When the connection is established, the Connected event will be raised. A 'Success' parameter tells us if the connection is successful. The Connected event can also be raised if an incoming connection is established. Code:
97
Sub Serial1_Connected (Success As Boolean) If Success Then ToastMessageShow("Connected successfully", False) TextReader1.Initialize(Serial1.InputStream) TextWriter1.Initialize(Serial1.OutputStream) timer1.Enabled = True connected = True Else connected = False Timer1.Enabled = False Msgbox(LastException.Message, "Error connecting.") End If End Sub
If the connection was established successfully we can start the data transfer. TextReader1 and TextWriter1 are process global object. We now initialize them using the serial stream. This will allow us to send and receive text over our newly created connection. Timer1 is used to test whether there is incoming data (and read this data). Now we enable it and start listening. If the connection is not successful we retrieve the exception and show it. Sending messages - When the user presses on btnSend we send the text: Code: Sub btnSend_Click If connected Then TextWriter1.WriteLine(txtSend.Text) TextWriter1.Flush txtSend.Text = "" End If End Sub
'connected' is a variable that we use to know if we are currently connected. Note that we call Flush after writing the text. This way we make sure that TextWriter doesn't buffer the text and sends it right away. Receiving messages - Whenever the timer ticks we check if there is any data waiting to be read. If there is, we read it and add it to the large EditText: Code: Sub Timer1_Tick If connected Then If TextReader1.Ready Then 'check if there is any data waiting to be read txtLog.Text = txtLog.Text & TextReader1.ReadLine & CRLF txtLog.SelectionStart = txtLog.Text.Length End If
98
End If End Sub
TextReader.ReadLine is a blocking call. It waits till there is at least a single character to be read. Therefore we need to test TextReader.Ready if we don't want to block our application. This application can also be used to connect with non-Android devices. An external GPS for example:
The external GPS continuously sends its data as text. I actually had quite an interesting conversation with the external GPS... The program is attached. Edit: It is recommended to use the new AsnycStreams object instead of polling the available bytes parameter with a timer. Using AsyncStreams is simpler and more reliable.
99
JSON parsing and generating JSON format is a a format similar to XML but usually it is shorter and easier to parse. Many web services now work with JSON. JSON official site: JSON Using the new JSON library, you can parse and generate JSON strings easily. As an example we will parse a the following JSON string: Code: {"menu": { "id": "file", "value": "File", "popup": { "menuitem": [ {"value": "New", "onclick": "CreateNewDoc()"}, {"value": "Open", "onclick": "OpenDoc()"}, {"value": "Close", "onclick": "CloseDoc()"} ] }
100
}}
This example was taken from json.org/examples. Curl brackets represent an object and square brackets represent an array. Objects hold key/value pairs and arrays hold list of elements. Commas separate between elements. In this example, the top level value is an object. This object contains a single object with the key "menu". The value of this object is another object that holds several elements. We will get the "menuitem" element, which holds an array of objects, and print the values of the "value" element. After parsing the string, JSON objects are converted to Maps and JSON arrays are converted to Lists. We will read this string from a file added by the files manager (Files tab). Code: Dim JSON As JSONParser Dim Map1 As Map JSON.Initialize(File.ReadString(File.DirAssets, "example.json")) Map1 = JSON.NextObject Dim m As Map 'helper map for navigating Dim MenuItems As List m = Map1.Get("menu") m = m.Get("popup") MenuItems = m.Get("menuitem") For i = 0 To MenuItems.Size - 1 m = MenuItems.Get(i) Log(m.Get("value")) Next
JSON.NextObject parses the string and returns a Map with the parsed data. This method should be called when the top level value is an object (which is usually the case). Now we will work with Map1 to get the required values. We declare an additional Map with the name 'm'. Code: m = Map1.Get("menu")
The object that maps to "menu" is assigned to m. Code: m = m.Get("popup")
The object that maps to "popup" is now assigned to m. Code: MenuItems = m.Get("menuitem")
101
The array assigned to "menuitem" is assigned to the MenuItems list. We will iterate over the items (which are maps in this case) and print the values stored in the elements with "value" key. Code: For i = 0 To MenuItems.Size - 1 m = MenuItems.Get(i) Log(m.Get("value")) Next
The output in the LogCat is: New Open Close Generating JSON strings is done in a similar way. We create a Map or a List that holds the values and then using JSONGenerator we convert it to a JSON string: Code: Dim Data As List Data.Initialize Data.Add(1) Data.Add(2) Data.Add(3) Data.Add(Map1) 'add the previous map loaded from the file. Dim JSONGenerator As JSONGenerator JSONGenerator.Initialize2(Data) Msgbox(JSONGenerator.ToPrettyString(2), "")
JSONGenerator can be initialized with a map or a list. Converting the data to a JSON string is done by calling ToString or ToPrettyString. ToPrettyString adds indentation and is easier to read and debug.
102
Animation tutorial
103
The Animation library allows you to animate views. These small animations are really nice and can affect the user overall impression of your application. The attached program demonstrates the available types of animations which are: Alpha - Causes a fading in or fading out effect. Scale - The view's size smoothly changes. Rotate - The view rotates. Translate - The view moves to a different position.
Creating an animation is done in four steps:
104
- Declare the animation object. - Initialize the object based on the required animation. - Set the animation parameters (duration, repeat and repeat mode). - Start the animation by calling Start with the target view. In this program an animation is attached to each button as its Tag value. All the buttons click events are caught with Sub Button_Click. In this sub we take the attached animation from the sender button and start it. The code for the first five animations and buttons: Code: Dim a1, a2, a3, a4, a5 As Animation Activity.LoadLayout("1") a1.InitializeAlpha("", 1, 0) Button1.Tag = a1 a2.InitializeRotate("", 0, 180) Button2.Tag = a2 a3.InitializeRotateCenter("", 0, 180, Button3) Button3.Tag = a3 a4.InitializeScale("", 1, 1, 0, 0) Button4.Tag = a4 a5.InitializeScaleCenter("", 1, 1, 0, 0, Button4) Button5.Tag = a5 Dim animations() As Animation animations = Array As Animation(a1, a2, a3, a4, a5) For i = 0 To animations.Length - 1 animations(i).Duration = 1000 animations(i).RepeatCount = 1 animations(i).RepeatMode = animations(i).REPEAT_REVERSE Next
We are using a temporary array to avoid writing duplicate code. Setting RepeatCount to 1 means that each animation will play twice. The REPEAT_REVERSE means that the second time the animation will play it will play backwards. The animation attached to Button6 is made of 4 chained animations. The button moves down, then left, then up and then right back to the start. We are using these animations: Code: a6.InitializeTranslate("Animation", 0, 0, 0dip, 200dip) 'we want to catch the AnimationEnd event for these animations a7.InitializeTranslate("Animation", 0dip, 200dip, -200dip, 200dip) a8.InitializeTranslate("Animation", -200dip, 200dip, -200dip, 0dip) a9.InitializeTranslate("Animation", -200dip, 0dip, 0dip, 0dip) Button6.Tag = a6 animations = Array As Animation(a6, a7, a8, a9) For i = 0 To animations.Length - 1
105
animations(i).Duration = 500 Next
In this case we do not want to repeat each animation. Starting the animations: Code: Sub Button_Click Dim b As Button b = Sender 'Safety check. Not really required in this case. If Not(b.Tag Is Animation) Then Return Dim a As Animation a = b.Tag a.Start(b) End Sub
Last part if the usage of AnimationEnd event to start the next animation for Button6: Code: Sub Animation_AnimationEnd If Sender = a6 Then a7.Start(Button6) Else If Sender = a7 Then a8.Start(Button6) Else If Sender = a8 Then a9.Start(Button6) End If End Sub
This program looks really nice on a real device. It also works on the emulator but the animations are less smooth.
106
Network tutorial The Network library allows you to communicate over TCP/IP with other computers or devices. The Network library contains two objects. Socket and ServerSocket. The Socket object is the communication endpoint. Reading and writing are done with Socket.InputStream and Socket.OutputStream. ServerSocket is an object that listens for incoming connections. Once a connection is established an event is raised and a socket object is passed to the event sub. This socket will be used to handle the new client. Client application Steps required: - Create and initialize a Socket object. - Call Socket.Connect with the server address. - Connection is done in the background. The Connected event is raised when the connection is ready or if it failed. - Communicate with the other machine using Socket.InputStream to read data and Socket.OutputStream to write data. Server application Steps required: - Create and initialize a ServerSocket object. - Call ServerSocket.Listen to listen for incoming connections. This happens in the background. - Once a connection is established the NewConnection event is raised and a Socket object is passes. - Call ServerSocket.Listen if you want to accept more connections. - Using the Socket object received, communicate with the client. We will see two examples. The first example connects to a time server and displays the current date and time as received from the server. Code: Sub Process_Globals Dim Socket1 As Socket End Sub Sub Globals End Sub
107
Sub Activity_Create(FirstTime As Boolean) Socket1.Initialize("Socket1") Socket1.Connect("nist1-ny.ustiming.org" , 13, 20000) End Sub Sub Socket1_Connected (Successful As Boolean) If Successful = False Then Msgbox(LastException.Message, "Error connecting") Return End If Dim tr As TextReader tr.Initialize(Socket1.InputStream) Dim sb As StringBuilder sb.Initialize sb.Append(tr.ReadLine) 'read at least one line Do While tr.Ready sb.Append(CRLF).Append(tr.ReadLine) Loop Msgbox("Time received: " & CRLF & sb.ToString, "") Socket1.Close End Sub
We are creating a new socket and trying to connect to the server which is listening on port 13. The next step is to wait for the Connected event. If the connection is successful we create a TextReader object and initialize it with Socket1.InputStream. In this case we want to read characters and not bytes so a TextReader is used. Calling tr.ReadLine may block. However we want to read at least a single line so it is fine. Then we read all the other available lines (tr.Ready means that there is data in the buffer).
In the second application we will create a file transfer application, that will copy files from the desktop to the device. The device will use a ServerSocket to listen to incoming connections.
108
Once a connection has been made, we will enable a timer. This timer checks every 200ms whether there is any data waiting to be read. The file is sent in a specific protocol. First the file name is sent and then the actual file. We are using a RandomAccessFile object to convert the bytes read to numeric values. RandomAccessFile can work with files or arrays of bytes, we are using the later in this case. RandomAccessFile can be set to use little endian byte order. This is important here as the desktop uses this byte order as well. The desktop example application was written with Basic4ppc. Once connected the user selects a file and the file is sent to the device which saves it under /sdcard/android. Both applications are attached. Some notes about the code: - The server is set to listen on port 2222. The server displays its IP when it starts. The desktop client should use this IP address when connecting to a real device (this IP will not work with the emulator). However if you work with the emulator or if your device is connected to the computer in debug mode you can use 'adb' to forward a desktop localhost port to the device. This is done by issuing "adb forward tcp:5007 tcp:2222" Now in the client code we should connect to the localhost ip with port 5007. Code: Client.Connect("127.0.0.1", 5007)
Again if you are testing this application in the emulator you must first run this adb command. Adb is part of the Android SDK. - Listening to connections: Code: Sub Activity_Resume ServerSocket1.Listen End Sub Sub Activity_Pause (UserClosed As Boolean) If UserClosed Then Timer1.Enabled = False Socket1.Close ServerSocket1.Close 'stop listening End If End Sub Sub ServerSocket1_NewConnection (Successful As Boolean, NewSocket As Socket)
109
If Successful Then Socket1 = NewSocket Timer1.Enabled = True InputStream1 = Socket1.InputStream OutputStream1 = Socket1.OutputStream ToastMessageShow("Connected", True) Else Msgbox(LastException.Message, "Error connecting") End If ServerSocket1.Listen 'Continue listening to new incoming connections End Sub
In Sub Activity_Resume (which also called right after Activity_Create) we call ServerSocket.Listen and start listening to connections. Note that you can call this method multiple times safely. In Sub Activity_Pause we close the active connection (if there is such a connection) and also stop listening. This only happens if the user pressed on the back key (UserClosed = True). The ServerSocket will later be initialized in Activity_Create. The server side application can handle new connections. It will just replace the previous connection with the new one. The desktop client example application doesn't handle broken connections. You will need to restart it to reconnect. Edit: It is recommended to use the new AsnycStreams object instead of polling the available bytes parameter with a timer. Using AsyncStreams is simpler and more reliable.
110
Regular Expressions Regular expressions are very powerful and make complicate parsing challenges much easier. This short tutorial will describe the usage of regular expressions in Basic4android. If you are not familiar with regular expressions you can find many good tutorials online. I recommend you to start with this one: Regular Expression Tutorial - Learn How to Use Regular Expressions Basic4android uses Java regular expression engine. See this page for specific nuances related to this engine: Pattern (Java Platform SE 6) Regular expressions methods in Basic4android start with the predefined object named Regex. You can write Regex followed by a dot to see the available methods. All methods accept a pattern string. This is the regular expression pattern. Note that internally the compiled patterns are cached. So there is no performance loss when using the same patterns multiple times. For each method there are two variants. The difference between the variants is that the second one receives an 'options' integer that affects the engine behavior. For now there are two option, CASE_INSENSITIVE and MULTILINE. CASE_INSENSITIVE makes the pattern matching be case insensitive. MULTILINE changes the string anchors ^ and & match the beginning and end of each line instead of the whole string. Both options can be combined by calling Bit.Or(Regex.MULTILINE, Regex.CASE_INSENSITIVE). Matching the whole string IsMatch and IsMatch2 are good to validate user input. The result of these methods is true if the whole string matches the pattern. For example the following code checks if a date string is formatted in a format similar to: 12-31-2010 Code: Log(Regex.IsMatch("\d\d-\d\d-\d\d\d\d", "11-15-2010")) 'True Log(Regex.IsMatch("\d\d-\d\d-\d\d\d\d", "12\31\2010")) 'False
This pattern will also match the string "99-99-9999". Splitting text
111
Split and Split2 splits a text around matches of the given pattern. Simple case: Code: Dim data As String data = "123,432,13,4,12,534" Dim numbers() As String numbers = Regex.Split(",", data) Dim l As List l.Initialize2(numbers) Log(l)
Lists can be easily printed with Log so we add the array to the list. The result is:
The comma followed by a single space is part of the list formatting. The expected values were parsed. Now if the data value was "123, 432 , 13 , 4 , 12, 534" The result wasn't perfect:
There are extra spaces which are part of the parsed values. We can change the pattern to match a comma or white space: Code: numbers = Regex.Split("[,\s]", data)
The result is still not as we want it:
Many empty strings were added. The correct pattern in this case is: Code: numbers = Regex.Split("[,\s]+", data)
Find matches in string Here we have a long string and we want to find all matches of a pattern in the string. We can also use capture groups to get specific parts of the match. As an example we will find and print email addresses in text: Code:
112
Dim data As String data = "Please contact [email protected] or [email protected]" Dim matcher1 As Matcher matcher1 = Regex.Matcher("\w+@\w+\.\w+", data) Do While matcher1.Find = True Log(matcher1.Match) Loop
This code prints: [email protected][email protected] Note that this pattern is far from being a good pattern for email validation / matching. In the second example we will use a Matcher with capturing groups to validate a date text. The pattern is similar to the pattern in the first example with the addition of parenthesis. These parenthesis mark the groups: Code: Log(IsValidDate("13-31-1212")) 'false Log(IsValidDate("12-31-1212")) 'true Sub IsValidDate(Date As String) As Boolean Dim matcher1 As Matcher matcher1 = Regex.Matcher("(\d\d)-(\d\d)-(\d\d\d\d)", Date) If matcher1.Find = True Then Dim days, months As Int months = matcher1.Group(1) 'fetch the first captured group. days = matcher1.Group(2) 'fetch the second captured group If months > 12 Then Return False If days > 31 Then Return False Return True Else Return False End If End Sub
The groups feature is very useful. If you find yourself calling String.IndexOf together with String.Substring multiple times, it is a good hint that you should move to a Regex and Matcher.
113
Downloading files using Services As discussed in the Services tutorial, some tasks are easier to implement with services than with activities. In this example we will download an image file with a service and use it for the activity background. This service can be used to download larger files as well. The advantage of a service over activity in this case is that the service isn't paused when the application is in the background. The steps required for downloading a file with this service is to first set the URL and the target file (the downloaded file is written to this file) and then call StartService. The process global variables are used for passing those values to the service. Process global variables can be accessed from all modules and are always available (even when the component is paused). The service then downloads the file. When download completes the service uses CallSub to notify the Activity that the download has completed. It is possible that the activity is not active when download is complete. In this case CallSub will not do anything. Press on Home button after starting the download to pause the activity. We are handling this case in the activity resume event. When the activity resumes we check if we were in the middle of a download task. If yes, then we check the service JobStatus global variable which tells us whether the task has finished. In most cases it is better to let the activity pull data from the service than pushing the data to the activity from the service. This way when the activity is ready it can grab the data and work with it. If we were to download a large file in the background it is possible that the OS will at some point kill our process to free some memory. This will of course break our download. To avoid this issue the service calls Service.StartForeground while downloading. This call marks our process as a foreground process. Which means that it is important not to kill it. A status bar notification is also displayed when the service is in this state. You should be careful not to overuse this method and only call it if it is really problematic for your service to be killed.
114
When download completes we also check if the activity is currently active. If not we show a status bar notification that notifies the user that download has completed. When the user presses on the notification (by first dragging the status bar downwards) our activity is resumed. The program is attached.
115
AsyncStreams tutorial A new object type is available in the RandomAccessFile library named AsyncStreams. AsyncStreams allows you to read data from an InputStream and write data to an OutputStream without blocking your program. The reading and writing are done with two separate threads. When new data is available the NewData event is raised with the data. When you write data to the OutputStream the data is added to an internal queue and then sent in the background. AsyncStreams is very useful when working with slow streams like network streams or Bluetooth streams. If you work with such streams using the main thread your program may hang or block while waiting for a value. The way we tried to deal with it in the Serial tutorial and Network tutorial is with a Timer that checks whether there are bytes waiting in the buffer. However even if there are bytes available there is a chance that not enough are available and then our program will hang or block until those are available. Using AsyncStreams is usually simpler and safer. AsyncStreams can work in two modes. Regular mode and "prefix mode". Both work as described above. The following code demonstrates a simple program that sends text to a connected device or computer: Code: Sub Process_Globals Dim AStreams As AsyncStreams Dim Server As ServerSocket Dim Socket1 As Socket End Sub Sub Globals Dim EditText1 As EditText End Sub Sub Activity_Create(FirstTime As Boolean) If FirstTime Then
116
Server.Initialize(5500, "Server") Server.Listen Log("MyIp = " & Server.GetMyIP) End If EditText1.Initialize("EditText1") EditText1.ForceDoneButton = True Activity.AddView(EditText1, 10dip, 10dip, 300dip, 60dip) End Sub Sub Server_NewConnection (Successful As Boolean, NewSocket As Socket) If Successful Then ToastMessageShow("Connected", False) Socket1 = NewSocket AStreams.InitializePrefix(Socket1.InputStream, False, Socket1.OutputStream, "AStreams") Else ToastMessageShow(LastException.Message, True) End If Server.Listen End Sub Sub AStreams_NewData (Buffer() As Byte) Dim msg As String msg = BytesToString(Buffer, 0, Buffer.Length, "UTF8") ToastMessageShow(msg, False) Log(msg) End Sub Sub AStreams_Error ToastMessageShow(LastException.Message, True) End Sub 'press on the Done button to send text Sub EditText1_EnterPressed If AStreams.IsInitialized = False Then Return If EditText1.Text.Length > 0 Then Dim buffer() As Byte buffer = EditText1.Text.GetBytes("UTF8") AStreams.Write(buffer) EditText1.SelectAll Log("Sending: " & EditText1.Text) End If End Sub Sub Activity_Pause(UserClosed As Boolean) If UserClosed Then Log("closing") AStreams.Close Socket1.Close End If
117
End Sub
Once there is a connection we initialize the AsyncStreams object: Code: AStreams.InitializePrefix(Socket1.InputStream, False, Socket1.OutputStream, "AStreams")
Then when there is new data available we convert the bytes to string and show it: Code: Sub AStreams_NewData (Buffer() As Byte) Dim msg As String msg = BytesToString(Buffer, 0, Buffer.Length, "UTF8") ToastMessageShow(msg, False) End Sub
When the user enters text in the EditText, the text is being sent: Code: Sub EditText1_EnterPressed If AStreams.IsInitialized = False Then Return If EditText1.Text.Length > 0 Then Dim buffer() As Byte buffer = EditText1.Text.GetBytes("UTF8") AStreams.Write(buffer) EditText1.SelectAll Log("Sending: " & EditText1.Text) End If End Sub
Prefix mode When the object is initialized in prefix mode the data is expected to adhere to the following protocol: every message (bytes array) should be prefixed with the bytes array length (as an Int). So if another device sends us a message made of 100 bytes. The stream is expected to include 4 bytes with a value of 100 and then the 100 bytes. The NewData event will be raised with the 100 bytes. It will not include the 4 prefix bytes. When you send data with Write or Write2 the bytes length will be added automatically as the prefix of this message. If you can work in prefix mode, which is usually only possible if you are implementing both sides, it is highly recommended to do so. Under the cover AsyncStreams uses the message length prefix to make sure that the NewData event is always raised with complete messages. If for example it expects 100 bytes and only 60 bytes arrived, it will wait till the other 40 bytes arrive. In regular mode the event will be raised twice and you will need to handle the two parts of the message. AsyncStreams also handles the case where 100 bytes are expected and more than 100 bytes arrived (which means that there are several messages).
118
The Error event will be raised if there is any error. You can use LastException to find the reason for the error.