- Android Software Stack
- The Android Application structure
- 2. Android Versions
- 3. Getting started with Android development tools
Our First Android Application
- 4. Android Project Structure
5. Linear and Relative Layouts
· Linear layout
· Relative layout
6. Table and Absolute Layouts
· Table Layout
· Absolute layout
7. Frame Layout and Scroll View
· Frame Layout
· Scroll View
8. Introduction to UI in Android and Text Controls
· Text Controls
- Text View
- Edit Text
- Auto Complete Edit Text
- Multi Complete Text View
- The Basic Button
- Image Button
- Toggle Button
10. Android Selection Controls
- List View
- Check box
- Radio Button
- Spinner
- Grid View
- The Spinner
- Grid View
- Types of resources
- String Resources
- String format Resources
- Styled text Resources
- Image Resources
- Color draw able Resources
- Color Resources
- Dimensions Resources
- Array Resources
- Anatomy of Tabbed Application
- Setting the content of tabs
- Adding tabs at run-time
- iPhone-Like Tab
· Styles
· Inheriting Styles
- Using Themes
- Using Intents to launch phone activities
- Intent Filters
- Passing Results between Intents:
- Receiving Extra data in sub activity
- Handling sub activity results
- Adding Icons to menu items
- Handling Menu Items Events
- Using onOptionsItemSelected method
- Using listeners
14. Options menu
- 15. Context Menus
17. Gallery Control
· Handling Gallery Events
· Displaying Images
18. Using Toasts and Alerts
· Displaying simple text
· Toasts
· Displaying complex views
· Alerts
· Creating Alerts
· Displaying Custom Views
- Displaying an alert of items
- · Displaying alerts with items with choices
19. View Flipper and Sliding drawer
· View Flipper
- Sliding Drawer
- Responding to Sliding Drawer Events
20. Using SQLite database
- Creating SQLite Database
- · Creating the database
- Upgrading the database
- Managing Foreign-Key Constraints
- Executing SQL statements
- Updating values
- Deleteing rows
- Executing queries
- Managing Cursors
- Android default content providers
- content providers basics
- Making Queries
- Inserting, updating and deleting
- Updating info using Content Providers
- Deleting info using Content Providers
22. Building Android Content Providers
- Creating the content type
- Modifying the Manifest.xml file
- Testing the content provider
23. Handlers
- Handlers
24. Async Tasks
- The Difference between Handler and AsyncTask
25. Using Preferences
· Saving Preferences
· Saving Activity-level preferences
· Reading preferences values
- Saving Application-level preferences
- Sharing preferences across applications
- Creating Preferences Activities
26. Calling Web Services
- Requesting SOAP web service
- Requesting REST web service
- Connecting to a web service over a Secure Sockets Layer (SSL): protocol
- Parsing XML
- Parsing the response with DOM Parser
- Parsing the response with SAX Parser
- Parsing JSON respone
- Calling a service
- Binding to a service through an Binder object
28. Android Services
· Creating a service
- Defining the AIDL file
- Defining the Service
- Consuming the service at the client application
30. Implementing Search activities
Introduction to Android App Development
Welcome to the first Android development articles on Mobile Orchard. We are going to start a series of articles to introduce you to the world of Android development. We are going to start off with the Android platform and the necessary tools to start Android development.
Android Software Stack:
This picture describes the Android software stack that can be described as a Linux Kernel and C/C++ libraries exposed through an application framework that provides services for and management of runtime and applications.
The elements of the Android software stack are:
- Linux Kernel: provides abstraction between the hardware and the rest of the stack, responsible for device drivers (Camera , Wi Fi, etc…), resources management , power management, security and net working
- C/C++ Libraries: such as SQL lite, Graphics libraries OpenGL ES, media framework and webkit layout engine.
- The Android Runtime: includes Core libraries and the Dalvik Vitual Machine.
The Core libraries provide most of java libraries + additional Android libraries.
The Dalvik VM provides (Just In Time) JIT compilation. the VM is optimized to run multiple instances of VMs. As Java applications access the core libraries each application has its own VM - The Android Application Framework: Provides classes required to develop an Android application and abstraction between hardware access. the Android Java API’s main library include telephony, content providers (data), resources, locations and UI.
- Application Layer: all Android applications(native or third party) are built on the application layer using the same API.
So Android applications are written in Java, but remember it is not Java ME (Mobile Edition). It’s just Most of J2SE libraries + Android’s own Java libraries.
The Android Application structure:
Android architecture encourages component reuse allowing you to publish and share activities, services and data between applications with security restrictions defined by you. This enables developers to include out of the box components such as the phone dialer or contact manager to their applications, or add new functionalities to them.
The bases of the Android applications are:
- Activity Manager: which controls the life cycle of the activities, activities can be compared to the windows or web forms they carry the controls (views) that construct the interface, an activity represents a single screen.
- Views: the UI components that construct the interface. They can be compared to the swing or windows forms controls
- Notification Manger: provides a consistent mechanism to notify or alert users.
- Content Providers: lets applications to share data between them.
- Resources Manager: much like the ASP.NET resources concept. Enables the developer to store resources such as strings or images.
Android Versions:
Android has been updated many times since its release, these updates were to fix bugs and add new functionalities. Each Android update has a name of a dessert.
- Android 1.5 (Cup Cake).
- Android 1.6 (Donut).
- Android 2.0/2.1 (Eclair).
- Android 2.2 (Froyo).
- Android 2.3 (Gingerbread).
Getting started with Android development tools:
Android applications can be developed on Windows, Mac or Linux platforms.
To start developing Android apps you need:
- Download and install the Java Development Kit (JDK).
- The Android SDK
- Eclipse IDE .
- Follow the installation links from this link.
After you’re done you will be ready to start developing Android Applications.
Our First Android Application:
Now we’re going to explore our first Android application which will be the famous Hello World application. We are going to explain the structure of an Android application and some of the basic concepts we must understand.
- Open Eclipse and select from the menu File>New> Project, you’ll see a window like this from the Android node select Android project and press Next.
- Now there are several fields that appear here they are:
Project Name: The Eclipse project name, the name of the folder that will hold the project files.
Application Name:The name of the application that will appear on the phone.
Package Name: The application package name, a good convention to use to avoid applications package name conflict is com.mydomain.myapp.
Create Activity: the name of the basic activity class of your application. it’s optional to create.
Min SDK Version: the minimum API level required by the application. You see in the Build Target list there is a list of the available SDK versions to use for the application and in front of each one the corresponding API level. You must ensure that you application API level is supported by the device select it to be 8 (for Android 2.2). - Press next; we won’t create a test project for our application yet.
- Press Finish then navigate to the project press run and choose run as Android Application, the emulator will start, wait until the main screen appears.
The Android Emulator may take some time, so please be patient. After the emulator launches, unlock it and your application will launch automatically. it should be like this:
Android Project Structure:
The project contains the following folders:
- Src: Contains all the source code (class files) for the project.
- Gen: Contains the R.java class file. The R.java is an automatically generated class file that holds a reference to each resource in the application
- Android 2.2: Its name changes according to the sdk version you use (2.2 here). Contains the Android API class files packed in android.jar files
- Assets: You can place any external resources (text files, images,…) in this folder. It’s much like the res file except it’s more like the file system where you put files that you want to access them as raw bytes
- Drawable folders: Contains the images required for you application. You can add your own image files to this folder. Notice that there are three folders with hdpi,ldpi and mdpi suffixes. These folders are used to include image resources with different resolutions suitable to the different phone screens. And at runtime Android will pick the suitable image from the suitable folder.
- Layout: Contains the main.xml file that defines the view that construct the User Interface of the application
- Values: Contains other resources that can be used for your application such as string resources, styles or colors
Now we’re going to take a deeper look on the Hello Android application and know what each line of code exactly means
The MainActivity.java file contains the following code:
package mina.android.hellowolrd;
import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
- The first line is the package name of the application.
- Lines 3,4 are import statements of two libraries(): android.app.Activity and android.os.Bundle
- Line 6 is the declaration of the MainActivity class which extends from Activity , Activity represents the User interface view or window of your application much like windows or web forms.
- Our class contains one function which is onCreate(Bundle savedInstanceState). This function is called when the activity is first created, it should contain all the initialization and UI setup.
This function has a parameter savedInstanceState of type Bundle
this object holds the activity’s previously saved state if exists (similar to asp.net view state concept) - Line 10 calls the super class’ OnCreateMethod with the activity’s
- Line 11 calls setContentView(R.layout.main); method which dsiplays the layout definition in main.xml file in res/layout directory by accessing a reference to it from the R class
This method is required to display the user interface, if the activity does not include this function then it would display a blank screen.
If we went to main.xml in the layout folder we will find it like this:
<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”vertical”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
>
<TextView
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:text=”@string/hello”
/>
</LinearLayout>
This xml layout file defines a vertical LinearLayout Container with a TextView inside it.
The TextView has a text property android:text=”@string/hello”. This means that there is a string resource called hello is defined in the values/strings.xml with the value “Hello World, MainActivity!”.
What if we want to reference this TextView from our class, we will add an id property to the TextView to be like this:
<TextView
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:text=”@string/hello”
android:id=”@+id/txtHello”
/>
The id of any view in Android must be in this format “@+id/(id of the view)”
After we added the id property to the view, we reference it from the code like this:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
TextView txt=(TextView)findViewById(R.id.txtHello);
txt.setText(“referenced from code”);
}
Run the application it should be like this:
Notice that we reference our TextView with the method findViewById(int Id).
The id of the TextView (txtHello) is strongly typed as it was added automatically to the R.java file and assigned an integer value:
public final class R {
public static final class attr {
}
public static final class drawable {
public static final int icon=0x7f020000;
}
public static final class id {
public static final int txtHello=0x7f050000;
}
public static final class layout {
public static final int main=0x7f030000;
}
public static final class string {
public static final int app_name=0x7f040001;
public static final int hello=0x7f040000;
}
}
Note:
You do not to add views (controls) to your activity in the xml directly. You can switch to the layout view by pressing the layout tab in the main.xml file and choose the views from the toolbox.
There is also a nice UI generator tool called Droid Draw.
Summary:
The basic Android application with only one view consists of a class that extends the Activity class. This class preserves the activity frozen state (like viewstate in asp.net) and it displays the UI defined in xml file layout file or defined explicitly programmatically.
Linear and Relative Layouts
· Linear layout
· Relative layout
· Table Layout
· Absolute layout
Frame Layout and Scroll View
· Frame Layout
· Scroll View
Linear and Relative Layouts
The way we construct the user interface in Android is pretty interesting. You can construct the UI widgets programmatically. But Android presents a decent way to construct the UI which is XML-based layouts.
The layout of an activity can be constructed by a XML file. These files are considered resources as they reside in res/layout folder. Each xml file consists of the declarations of widgets and their containers. The xml file is constructed in a hierarchical way, there is a tag that defines the layout of the widgets, inside this tag there are nested xml tags that define widgets, each widget tag has attributes that define the widget’s properties.
take a look at a simple layout xml file called main.xml which resides in res/layout directory:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="launch Activity 2" android:id="@+id/btnLaunch" /> </LinearLayout>
This layout defines a TextView and a button. The root xml node is <LinearLayout>node which defines the layout of all controls inside it.
We use the following code to construct the UI based on this xml definition.
setContentView(R.layout.main);
Also the TextView node contains attributes like :layout_width which can have the values fill_parentwhich denotes the the width of the control should occupy all the available width, also it could have the value wrap_content which denotes the control should occupy only width according to the width of its content which is the text inside the textView
The Button node has an extra attribute which is the android:id attribute, why this attribute is used in the button and not used in the TextView? The answer is that the TextView represents a label (just a static text to display) so there is no need to give it an id cause we will not reference it in the code, however if we need to display dynamic text in the TextView we can give it an id
To reference a control from the code we do it like this:
Button btnLaunch; btnLaunch=(Button)findViewById(R.id.btnLaunch);
notice that the android:id attribute should be in the format of @+id/Control’sID as shown above, if you write it like android:id=”btnLaunch” the IDE would denote an error and you won’t be able to reference the control from the code.
Why to use XML-layouts and when to construct the layout programmatically ?
- Using xml layouts achieves separation between the interface and the application logic, instead of writing a bulk of code that constructs the UI, defining it as XML is easier and less confusing.
- You can save using code for constructing more complicated UI such as populating check-box columns in a grid.
- It is a trend to construct the UI with XML definitions like in Microsoft’s XAML or even HTML, so people who are familiar with technologies like these would find it easy to deal with Android.
Finally we’re going to explain in more details all about Android different layouts
We have many layout forms:
- Linear layout: manages controls in horizontal or vertical way.
- Table layout: manages the controls in a group of columns and rows.
- Relative layout: manages the controls relative to one another or to the parent.
- Absolute layout: manages the controls in absolute position by coordinates.
- Frame layout: manages the controls in a dynamic way.
- Scroll view: manages the controls in a scrollable layout.
Linear layout:
Linear layout is like a box that contains controls inside it. Controls are drawn one after each other either horizontally or vertically according to the Orientation of the layout.
When dealing with Linear layout there are five properties that we can deal with:
- Orientation.
- Fill model.
- Weight.
- Gravity.
- Padding.
Orientation:
Orientation property determines whether the controls would be put in a horizontal way like in a row or in a vertical way like a column. The layout orientation is to set the property android:orientation.
vertical Orientation:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Linear Layout" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="this is a button 1" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="this is a button 2" /> </LinearLayout>
Horizontal Orientation:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Linear Layout" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="this is a button 1" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="this is a button 2" /> </LinearLayout>
If you want to set the orientation programmatically you can use this code:
.widget.LinearLayout mainLayout=new LinearLayout(this); mainLayout.setOrientation(LinearLayout.VERTICAL);
Fill Model:
The widgets inside linear layout have width and height properties. These properties can have three values:
- A numeric value in pixels or inches that gives the width or height properties an absolute value.
- They can have the value wrap_content meaning the widget should occupy it’s natural size unless there is no space then android can use word wrap to make the widget fit.
- They can have the value fill_parent meaning the widget should occupy all the available space of the closing container.
To set the fill model programmitaclly use this code:
Button b=(Button)findViewById(R.id.btn); b.setWidth(LayoutParams.WRAP_CONTENT); b.setHeight(LayoutParams.FILL_PARENT);
This is an example to two buttons one with width set to fill_parent and the other set to fill_parent:
Weight:
The weight property determines the ratio by which controls share free space. For example if we have two buttons and the weight of both is set to 1 (this is the default value) then the free space will be divided equally between them.
But if the value of the weight of one of them is 2 and the other is one, then the first button will occupy space half as that occupied by the second and so on.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/mainlayout" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Linear Layout" /> <Button android:layout_width="fill_parent" android:layout_height="fill_parent" android:text="weight set to 2" android:layout_weight="1" /> <Button android:layout_width="fill_parent" android:layout_height="fill_parent" android:text="weight set to 1" android:layout_weight="1" android:id="@+id/btn" /> </LinearLayout>
To set the weight of a widget programmatically it’s a little bit different, we use this code:
Button b=(Button)findViewById(R.id.btn); LayoutParams params=new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.FILL_PARENT,android.widget.LinearLayout.LayoutParams.FILL_PARENT,3); b.setLayoutParams(params);
the widget does not have a method to set the weight directly, instead you define
LayoutParams params=new android.widget.LinearLayout.LayoutParams
object and use the widget.setLayoutParams(params) method.
The LayoutParams class has many constructors including this one:
public LinearLayout.LayoutParams (int width, int height, float weight)
Gravity:
By default widget are positioned in the top-left of the screen, but if you want to change this you can use the layout_gravity property:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/mainlayout" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Linear Layout" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="left" android:layout_gravity="left" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="center" android:layout_gravity="center" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="center_vertical" android:layout_gravity="center_vertical" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="center_horizontal" android:layout_gravity="center_horizontal" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="right" android:layout_gravity="right" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="bottom" android:layout_gravity="bottom" /> </LinearLayout>
Some additional gravity properties include:
The layout gravity properties fill_vertical and fill_horizontal expand the size of the control horizontally or vertically if needed so that they fill the container.
If we used fill it will stretch from both files.
Difference between android:layout_gravity and android:gravity:
The android:layout_gravity sets the position of the view in the container, while android:gravitysets the position of the content of the view.
For example if the android:layout_gravity=”right” then the view would be placed in the right position in the container while if the view’s android:gravity=”right” then the text of the view would be placed at the right.
To set the android:gravity property programmatically you can use this code:
Button btn=(Button)findViewById(R.id.btn); btn.setGravity(Gravity.RIGHT);
Padding:
The android:padding property sets the padding between widgets. if you specify the padding property to the container then the container with all of its widgets would be shifted by the value, if you specify it to a single widget then the contents of that widget would be shifted by the specified value
If you use the android:padding property then this would apply the padding values to the four edges of the widget. If you need to be more specific you can use:
android:paddingTop or android:paddingLeft or android:paddingRight orandroid:paddingBottom
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/mainlayout" android:paddingLeft="20px" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="button 1" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="button 2" android:paddingLeft="40px" android:id="@+id/btn" /> </LinearLayout>
Relative layout:
Relative layout lays out widgets based on their position relative to each other. You can place a widget in a position relative to another widget’s position or relative to the container.
Relative layout is recommended to be used in your application’s layout because it fits with different screen resolutions of the different devices.
As we said in relative layout widgets can be placed
- Relative to the container.
- Relative to other widgets.
Relative to the container :
The widgets are placed in position relative to their container like by setting the following properties:
- android:layout_alignParentTop|Bottom|Right|Left to true. This aligns the Top|Bottom|Right|Left side of the widget with the Top|Bottom|Right|Left side of the container.
- android:layout_centerVertical: the widget should be positioned vertically in the center of the container.
- android:layout_centerHorizontal: the widget should be positioned horizontally in the center of the container.
- android:layout_centerInParent: the widget should be positioned both vertically and horizontally in the middle of the container.
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/mainlayout" android:paddingLeft="20px" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Relative layout" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="android:layout_alignParentTop" android:layout_alignParentTop="true" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="android:layout_alignParentBottom" android:layout_alignParentBottom="true" /> </RelativeLayout>
this xml gives the following layout:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/mainlayout" android:paddingLeft="20px" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="android:layout_alignParentLeft" android:layout_alignParentLeft="true" /> </RelativeLayout>
and this one:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/mainlayout" android:paddingLeft="20px" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="android:layout_alignParentRight" android:layout_alignParentRight="true" /> </RelativeLayout>
gives this one:
this one:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/mainlayout" android:paddingLeft="20px" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="android:layout_centerVertical" android:layout_centerVertical="true" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="android:layout_centerHorizontal" android:layout_centerHorizontal="true" /> </RelativeLayout>
gives this:
and finally this one:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/mainlayout" android:paddingLeft="20px" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="android:layout_centerInParent" android:layout_centerInParent="true" /> </RelativeLayout>
gives this:
Position Relative to other widgets’ positions:
There are four properties that determine the position of the widget in relation to other widgets:
- android:layout_above: the widget should be placed above the referenced widget.
- android:layout_below: the widget should be placed below the referenced widget.
- android:layout_toRightOf: the widget should be placed to the right of the referenced widget.
- android:layout_toLeftOf: the widget should be placed above the referenced widget.
this example:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/mainlayout" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button2" android:id="@+id/btn2" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button1 is below button2" android:layout_below="@id/btn2" android:id="@+id/btn1" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button3 below button 1" android:layout_below="@id/btn1" android:id="@+id/btn3" /> </RelativeLayout>
gives this layout:
and this one:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/mainlayout" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button2" android:id="@+id/btn2" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button1 is to the right of button2" android:layout_toRightOf="@id/btn2" android:id="@+id/btn1" /> </RelativeLayout>
gives this:
As we saw that there are properties that define the alignment of a widget relative to the container, there are also five properties that determine the position of the widget in relation to other widgets:
- android:layout_alignTop|Bottom|Right|Left: indicates that the widget’s Top|Bottom|Left|Right should be aligned with the Top|Bottom|Right|Left of the widget referenced in the property.
- android:layout_alignBaseLine: indicates that the two widget’s baselines should be aligned.
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button2" android:id="@+id/btn2" android:layout_centerVertical="true" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button1 is aligned to the top & Bottom of button2" android:layout_alignTop="@id/btn2" android:layout_toRightOf="@id/btn2" android:layout_alignBottom="@id/btn2" /> </RelativeLayout>
gives this:
Notes:
- When you reference a widget in a relative layout property, you must assure that this widget has been already defined in the layout.
- When you use the value fill_parent to assign athe height of a widget in a relative container, it could occupy all the available space so that any further controls defined in the xml file would not appear.
- o When referencing another control in relative layout property, we use the notation“@id/widgetID”
Table and Absolute Layouts
Table Layout:
Organizing widgets in a table is a famous approach in HTML design where you construct a table of a number of rows and cells and distribute the controls over the cells to achieve a consistent look for your UI. Android provides a similar technique.
In android you define the number of rows by your own and android determines the number of cells in each row according to the number of widgets in each row.
<TABLELAYOUT xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TABLEROW> <TEXTVIEW android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Enter your name"> <EDITTEXT android:layout_width="150px" android:layout_height="wrap_content"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Submit" /> </TABLEROW> </TABLELAYOUT>
You can define a table and add rows and views to it programmatically like this:
TableLayout tl=new TableLayout(this);
TableRow tr=new TableRow(this);
Button btn=new Button(this);
btn.setText("Hello");
tr.addView(btn);
tl.addView(tr);
setContentView(tl);
Spanning Multiple Columns:
A table cell can span multiple columns like this:
<TABLELAYOUT xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TABLEROW> <TEXTVIEW android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Enter your name"> <EDITTEXT android:layout_width="150px" android:layout_height="wrap_content"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Submit" /> </TABLEROW> <TABLEROW> <TEXTVIEW android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Spanning Two columns" android:layout_span="2"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Cancel" /> </TABLEROW> </TABLELAYOUT>
Or programmatically like this:
TableRow.LayoutParams trParams = new TableRow.LayoutParams(); trParams.span = 5; TableRow titleRow = new TableRow(this); TextView title = new TextView(this); title.setLayoutParams(trlp); titleRow.addView(title);
Also you can choose which column to put your widget by using android:layout_column property, you define the Zero-based index of the column where you want your widget to be:
<TABLELAYOUT xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TABLEROW> <TEXTVIEW android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Enter your name"> <EDITTEXT android:layout_width="150px" android:layout_height="wrap_content"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Submit" /> </TABLEROW> <TABLEROW> <TEXTVIEW android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Spanning Two columns" android:layout_span="2"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Cancel" /> </TABLEROW> <TABLEROW> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Help" android:layout_column="1" /> </TABLEROW> </TABLELAYOUT>
Note:
if you specify an index greater than the actual cells count, the widget won’t appear. For example in the previous example if android:layout_column had a value greater than 2 the widget wouldn’t appear.
Using Separators:
Table layout also allows you to put widgets directly under <TableRow> the tag to act as a separator between rows:
<TABLELAYOUT xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TABLEROW> <TEXTVIEW android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Enter your name"> <EDITTEXT android:layout_width="150px" android:layout_height="wrap_content"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Submit" /> </TABLEROW> <TABLEROW> <TEXTVIEW android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Spanning Two columns" android:layout_span="2"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Cancel" /> </TABLEROW> <VIEW android:layout_height="5px" android:background="#f00"> <TABLEROW> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Help" android:layout_column="5" /> </TABLEROW> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Separator" /> </TABLELAYOUT>
Stretching Columns:
In table layout each column occupies space equal to the size of the largest widget in it. But you can set the width of any column to take the largest available space, just like setting the width to 100 % in HTML. This is done by setting the property android:stretchColumns to the index of the column, also you can set multiple columns by separating them with a comma.
Look at the layout below:
<TABLELAYOUT xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TABLEROW> <TEXTVIEW android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Enter your name"> <EDITTEXT android:layout_width="100px" android:layout_height="wrap_content"> </TABLEROW> </TABLELAYOUT>
Now we gonna to add android:stretchColumns=”0″ to the table layout and see what it’s gonna look like:
<TABLELAYOUT xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:stretchcolumns="0"> <TABLEROW> <TEXTVIEW android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Enter your name"> <EDITTEXT android:layout_width="100px" android:layout_height="wrap_content"> </TABLEROW> </TABLELAYOUT>
Column 0 occupied the largest available space.
You can set this property from code like this:
TableLayout tl=new TableLayout(this); tl.setColumnStretchable(0, true);
the setColumnStretchable(ColumnIndex, IsStretchable) method parameters are the column index and a Boolean value to indicate it is going to be stretched.
Shrinking Columns:
Now what if we have a column that have a large content. Android columns by default do not wrap their content. Look at this layout:
<TABLELAYOUT xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TABLEROW> <TEXTVIEW android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Please enter your name and some very very long info as a test for this property"> <EDITTEXT android:layout_width="100px" android:layout_height="wrap_content"> </TABLEROW> </TABLELAYOUT>
See column zero occupies large space that column 1 is not visible.
We can use android:shrinkColumns property to wrap content of a certain column or for multiple columns by assigning column numbers separeted by commas.
When we use the property with column zero it will be like this:
<TABLELAYOUT xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:shrinkcolumns="0"> <TABLEROW> <TEXTVIEW android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Please enter your name and some very very long info as a test for this property"> <EDITTEXT android:layout_width="100px" android:layout_height="wrap_content"> </TABLEROW> </TABLELAYOUT>
Its like using style=”white-space:wrap;” style in HTML
You can set the property from code like this:
TableLayout tl=new TableLayout(this); tl.setColumnShrinkable(0, true);
The setColumnShrinkable(columnIndex, isShrinkable) method parameters are the column index and a Boolean to indicate it si going to be shrinked.
Hiding Columns:
Finally if you want to make some columns invisible you can use the propertyandroid:collapseColumns the same way we used the last two properties.
<TABLELAYOUT xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:collapsecolumns="0"> <TABLEROW> <TEXTVIEW android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Please enter your name and some very very long info as a test for this property"> <EDITTEXT android:layout_width="100px" android:layout_height="wrap_content"> </TABLEROW> </TABLELAYOUT>
See that column zero is invisible.
You can use this property from code like this:
TableLayout tl=new TableLayout(this); tl.setColumnCollapsed(0, true);
The method setColumnCollapsed(columnIndex, isCollapsed) parameters are the column index and a Boolean to indicate that it’s going to be collapsed
This is just like using the style=”display:none;” in HTML
Other functions can be called from code:
- TableLayout.setShrinkAllColumns(Boolean shrinkAllColumns) : shrinks all colums
- TableLayout.setStretchAllColumns(Boolean stretchAllColumns): stretches all columns
Absolute layout:
Note:
the absolute layout class is deprecated, you are encouraged to use Frame Layout or Relative layout.
The reason of that is that it won’t be compatible with all the android phones as they have different screen sizes and resolutions.
absolute layout lays widgets by specifying their exact X and Y positions. In android the origin (0,0) coordinate is located at the top left of the screen.
you can use Absolute Layout only if your application is targeted for a certain phone type so that you can be sure that it will display properly.
absolute layout is defiend in XML as <AbsoluteLayout>
by default, if you define any control in absolute layout without defining it’s x,y coordinates, it will be placed in the origin point at (x,y)=(0,0)
if you define x,y values that are too large, the widget will not appear on the screen
you can specify the values of x and y by many units as shown:
<ABSOLUTELAYOUT xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/mainlayout"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="placed at 100,130 pixels (px)" android:layout_x="100px" android:layout_y="130px" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="placed at 0,150 points (pt)" android:layout_x="0pt" android:layout_y="150pt" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="placed at 0.7,0.5 inches (in)" android:layout_x="0.7in" android:layout_y="0.5in" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="placed at 3,3 millimeters (mm)" android:layout_x="3mm" android:layout_y="3mm" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="placed at 0,270 density independant pixels (dp)" android:layout_x="0dp" android:layout_y="270dp" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="placed at 0,180 scale independant pixels (sp)" android:layout_x="0sp" android:layout_y="180sp" /> </ABSOLUTELAYOUT>
To define absolute layout from the code you can use the following code:
AbsoluteLayout abslay=new AbsoluteLayout(this);
Button btn=new Button(this);
btn.setText("Hello");
abslay.addView(btn, new AbsoluteLayout.LayoutParams(AbsoluteLayout.LayoutParams.WRAP_CONTENT,AbsoluteLayout.LayoutParams.WRAP_CONTENT,10,100));
setContentView(abslay);
the absolutelayout.layoutparams function has the following constructor:
AbsoluteLayout.LayoutParams(width,height,position X,position Y).
Frame Layout and Scroll View
Frame Layout:
Frame layout is used to display a single view at a time. The view can contain many widgets but only one will appear at a time.
for example we have two images that are same size:
and
In this example the activity has two image views but only one of them is displayed at a time which is the last view defined in the file. The frame layout displays views as if they are in a stack.
<?xml version="1.0" encoding="utf-8"?> <FrameLayout android:id="@+id/mainlayout" android:layout_height="fill_parent" android:layout_width="fill_parent" android:orientation="vertical" xmlns:android="http://schemas.android.com/apk/res/android"> <ImageView android:layout_height="wrap_content" android:layout_width="wrap_content" android:padding="5px" android:src="@drawable/image1"/> <ImageView android:layout_height="wrap_content" android:layout_width="wrap_content" android:padding="5px" android:src="@drawable/image2"/> </FrameLayout>
If we have two image views, one displays a blue box and the other displays a red box, the blue is bigger than the red one, it would be like this:
<?xml version="1.0" encoding="utf-8"?> <FrameLayout android:id="@+id/mainlayout" android:layout_height="fill_parent" android:layout_width="fill_parent" android:orientation="vertical" xmlns:android="http://schemas.android.com/apk/res/android"> <ImageView android:layout_height="wrap_content" android:layout_width="wrap_content" android:padding="5px" android:src="@drawable/blue"/> <ImageView android:layout_height="wrap_content" android:layout_width="wrap_content" android:padding="5px" android:src="@drawable/red"/> </FrameLayout>
Scroll View:
Suppose that our activity displays a large number of controls or content like this layout:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/txt" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Button 1" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Button 2" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Button 3" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Button 4" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Button 5" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Button 6" /> </LinearLayout>
This is a linear layout that displays a TextView with large text and some Buttons. As we can see, not all the buttons are displayed and that the layout does not fit in the device screen.
The solution to this problem is to use ScrollView as a container for the controls and a scroll bar to make the layout fit in the screen.
We will now change the layout with this code:
<?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/txt" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Button 1" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Button 2" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Button 3" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Button 4" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Button 5" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Button 6" /> </LinearLayout> </ScrollView>
As you can see the result is a scroll bar that we can use to see all the controls within the layout like this:
Remember, the ScrollView can have only one child control, so we can make a container (Linear, relative, Table Layouts) the child of the ScrollView and put all the controls inside this child.
So what do we do if we want to display this layout Horizontally ? In this case we’re going to use another container which is HorizontalScrollView. This container acts the same as the ScrollViewexcept that it scrolls child controls horizontally.
Now our layout will be like this:
<?xml version="1.0" encoding="utf-8"?> <HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/txt" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Button 1" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Button 2" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Button 3" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Button 4" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Button 5" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Button 6" /> </LinearLayout> </HorizontalScrollView>
In order to achive the Horizontal scrolling, we had to change the Orientation of the childLinearLayout to Horizontal.
31. Introduction to UI in Android and Text Controls
· Text Controls
- Text View
- Edit Text
- AutoCompleteEditText
- MultiCompleteText View
- The Basic Button
- Image Button
- Toggle Button
- List View
- Check box
- Radio Button
- Spinner
- GridView
- The Spinner
- GridView
Introduction to UI in Android and Text Controls
Layouts in Android are constructed from two objects: View and ViewGroup.
The View class is the base class for many widgets sub classes such as TextView and Buttonclasses.
The ViewGroup is a view that conatains other views. The Viewgroup class is the base class for many layouts in Android.
The UI Hierarchy is described in this image from the Android SDK documentation:
As you can see the root node is a view group which can be any layout that contains child views or even other viewgroups.
The root node appears on the activity by calling setContentView method in the activitiy’s onCreatemethod. Which in turn draws its child nodes and each child node draws its child nodes.
In the this series we are going to explore the categories of android widgets including:
- Text Controls
- Button controls
- Selection controls
- Pickers.
- Menus.
- Dialogs
- other controls
Text Controls:
Text controls include:
- TextView.
- EditText.
- AutoCompleteEditText.
- MultiCompleteTextView.
TextView:
The TextView represents non-editable text. It resembles the Label control in C# or ASP.NET but it has an interesting feature which is the ability to highlight the text if it is a URL, an e-mail or a phone number so that when the user clicks on the textview the default intent whether it is the web browser or the dialer launches:
<?xml version="1.0" encoding="utf-8"?> <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Visit Http://www.android-pro.blogspot.com" android:autoLink="web" android:id="@+id/txtURL" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Dial 1 650-253-0000" android:autoLink="all" /> </LinearLayout>
You can see that the TextViews containing URLs or Phone numbers are highlighted, and when the user presses on them the default intent, the browser or the dialer launches. This is done by using the property android:autoLink which can have the values:
web, email ,phone, map, All
This can be achieved from code by using the following code:
TextViewtxtURL=(TextView)findViewById(R.id.txtURL); Linkify.addLinks(txtURL, Linkify.ALL);
Edit Text:
The EditText is a subclass of the TextView it is like the TextBox in C#. it enables users to edit text and offers interesting features like Text correction, capitalizing letters and password text.
We set and get the text of EditText programmatically using setText(String text) and getText()methods
We can use the autoText property to make the EditText to correct the common spelling mistakes.
<?xml version="1.0" encoding="utf-8"?> <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <EditText android:layout_width="fill_parent" android:layout_height="wrap_content" android:autoText="true" /> </LinearLayout>
We can use the capitalize property to make the text capitalized like this:
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <EditText android:layout_width="fill_parent" android:layout_height="wrap_content" android:capitalize="characters" /> <EditText android:layout_width="fill_parent" android:layout_height="wrap_content" android:capitalize="none" /> <EditText android:layout_width="fill_parent" android:layout_height="wrap_content" android:capitalize="words" /> </LinearLayout>
We can use the password property to make the control accepts phone numbers as input:
<?xml version="1.0" encoding="utf-8"?> <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <EditText android:layout_width="fill_parent" android:layout_height="wrap_content" android:password="true" /> </LinearLayout>
We can enforce the Control to wrap all the text in a single line by setting android:singleLineproperty to true.
AutoCompleteTextView:
The AutoCompleteTextView is an EditText with auto complete functionality. The auto complete functionality can be achieved by attaching an Adapter with the auto complete values to the control like this:
<?xml version="1.0" encoding="utf-8"?> <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <AutoCompleteTextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/act" /> </LinearLayout>
Then attach the adapter from the code like this:
AutoCompleteTextView act=(AutoCompleteTextView)findViewById(R.id.act);
ArrayAdapterarr=new ArrayAdapter(this,android.R.layout.simple_dropdown_item_1line,new String []{"Hello","Hi","Alloha"});
act.setAdapter(arr);
The Adapter object is used to determine the way data can be represented (viewed) in controls. In a search program you can obtain the auto complete words from a web service and populate the adapter with these words.
MultiAutoCompleteTextView:
The AutoCompleteTextView can suggest auto complete for the entire text in the control, meaning that if you type more than one word it would try to match the whole sentence not the single words.
The MultiAutoCompleteTextView works the same way as the AutoCompleteTextView except that it can suggest words for each word in the sentence you type. you add a Tokenizer that parses the text and allows you to suggest where to start suggesting words like this:
<?xml version="1.0" encoding="utf-8"?> <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <MultiAutoCompleteTextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/act" /> </LinearLayout>
MultiAutoCompleteTextViewmact=(MultiAutoCompleteTextView)findViewById(R.id.act);
ArrayAdapterarr=new ArrayAdapter(this,android.R.layout.simple_dropdown_item_1line,new String []{"Hello","Hi","Alloha"});
mact.setAdapter(arr);
mact.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());
The tokenizer tells the control to start suggesting words separated by a comma.
Android offers three types of button controls:
- The Basic Button.
- Image Button.
- Toggle Button.
The basic Button:
The standard Android button is a subclass of the TextView class so it inherits all of its properties.
<linearlayout
android:layout_height=”fill_parent”
android:layout_width=”fill_parent”
android:orientation=”vertical” xmlns:android=”http://schemas.android.com/apk/res/android”
>
<Button
android:id=”@+id/btn”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”Click Me”
/>
</linearlayout>
If you want to implement the OnClick event handler for a button there are three ways to do it:
The first way is to implement the OnClickListner Interface for each single button in the activity like this:
Button btn=(Button)findViewById(R.id.btn);
btn.setOnClickListener(new OnClickListener()
{
public void onClick(View v) {
Button btn=(Button)v;
btn.setText(“You clicked on the button”);
}
}
);
The downside to doing it ths way is that it will lead to large code blocks with a lot of redundancy because you will have to repeat this for each button in your activity.
The second method is to make your activity implement the OnClickListner Interface and use theonClick method by switching between the buttons IDs:
public class ButtonControls extends Activity implements OnClickListener {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
public void onClick(View v) {
// TODO Auto-generated method stub
switch(v.getId())
{
case R.id.btn1:
//Do something
break;
case R.id.btn2:
// Do something
break;
}
}
}
Finally, the third method is to define the OnClick for the buttons using the XML layout definition. This is a nice new feature introduced in Andrpod 1.6. Which is similar to that in ASP.NET.
<button
android:id=”@+id/btn1″
android:layout_height=”wrap_content”
android:layout_width=”wrap_content”
android:onclick=”ClickHandler”
android:text=”Click Me”
/>
<button
android:id=”@+id/btn2″
android:layout_height=”wrap_content”
android:layout_width=”wrap_content”
android:onclick=”ClickHandler”
android:text=”Click Me too”
/>
Then you define the event handler method in your class file in the same normal way
switch(v.getId())
{
case R.id.btn1:
//Do something
break;
case R.id.btn2:
// Do something
break;
}
}
Image Button:
The ImageButton control is similar to the Button except it represents a button with an image instead of the text.
We can place an image in the res/drawable directory and reference it to be the source image of theImageButton.
<linearlayoutandroid:layout_height=”fill_parent” android:layout_width=”fill_parent” android:orientation=”vertical” xmlns:android=”http://schemas.android.com/apk/res/android”
/>
<imagebutton
android:id=”@+id/btn1″
android:layout_height=”wrap_content”
android:layout_width=”wrap_content”
android:src=”@drawable/globe”
/>
</linearlayout>
You can set the image source property from the code like this:
ImageButtonbtn=(ImageButton)findViewById(R.id.btn1);
btn.setImageResource(R.drawable.globe);
Toggle Button:
The ToggleButton is like a check box or radio button, it has two states: On or Off. The default behavior of ToggleButton is Off state, it displays a gray bar and the text Off.
When in On state it displays a green bar and has the text On.
<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayoutxmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”vertical”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
>
<ToggleButton
android:id=”@+id/tb”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”Switch On”
/>
</LinearLayout>
See that despite we specified the android:text property of the toggle button, it displays the default text “Off”.
This is because ToggleButton inherits from TextView. But practically the android:text property is useless.
Instead we define the android:textOn and android:textOff properties.
In code to check the state of the Toggle button programmatically you can define the click handler in the regular way:
<ToggleButton
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:textOff=”Switch On”
android:textOn=”Switch Off”
android:id=”@+id/btn”
android:onClick=”ClickHandler”
/>
Then check the state of it like this:
public void ClickHandler(View v)
{
ToggleButtontg=(ToggleButton)v;
if(tg.isChecked())
//Do something
else
//Do something else
}
here’s what it’s gonna look like:
<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayoutxmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”vertical”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
>
<ToggleButton
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:textOff=”Switch On”
android:textOn=”Switch Off”
/>
<ToggleButton
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:textOff=”Switch On”
android:textOn=”Switch Off”
/>
</LinearLayout>
Android offers selection Controls like
- List View.
- Check box.
- Radio Button.
- Spinner.
- GridView.
List View:
ListView represents a list of items that can be selected. It is similar to the ListBox in C#.
<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayoutxmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”vertical”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
>
<TextView
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:id=”@+id/txt”
/>
<ListView
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:id=”@+id/List”
/>
</LinearLayout>
To populate the list and handle the ItemClick event we can do it like this :
final String [] items=new String[]{“Item1″,”Item2″,”Item3″,”Item4”};
ArrayAdapter ad=new ArrayAdapter(this,android.R.layout.simple_list_item_1,items);
list=(ListView)findViewById(R.id.List);
list.setAdapter(ad);
list.setOnItemClickListener(new OnItemClickListener()
{
public void onItemClick(AdapterView arg0, View arg1, int arg2,
long arg3) {
// TODO Auto-generated method stub
TextView txt=(TextView)findViewById(R.id.txt);
txt.setText(list.getItemAtPosition(arg2).toString());
}
}
);
The above code displays the selected item text in the textview:
The parameters of the OnItemClick method are:
- AdapterView Arg0:the listview, notice that it is of type AdapterView.
- view Arg1: the view that represents the selected item, in this example it will be a TextView
- int Arg2: the position of the selected item.
- long Arg3:: the id of the selected item.
When creating the adapter you can specify the layout of the list by using Android’s built layout resource simple_list_item_1 to display a simple list or by using simple_list_item_single_choiceto display radio buttons for single selection:
Or by using simple_list_item_multiple_choice to display check boxes for multiple selection:
You can set the choice mode of the list by using setchoicemode() method:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.main);
final String [] items=new String[]{“Item1″,”Item2″,”Item3″,”Item4″};
ArrayAdapter ad=new ArrayAdapter(this,android.R.layout.simple_list_item_multiple_choice,items);
setListAdapter(ad);
ListView list=getListView();
list.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
}
Now suppose you want to change the text of the an item when it is clicked, you can do it like this:
list.setOnItemClickListener(new OnItemClickListener()
{
public void onItemClick(AdapterView arg0, View arg1, int arg2,
long arg3) {
// TODO Auto-generated method stub
TextView txt=(TextView)findViewById(R.id.txt);
items[arg2]=”changed”;
list.setAdapter(new ArrayAdapter(ListControls.this,android.R.layout.simple_list_item_1,items));
}
}
);
See that you actually change the value of the string array item at the selected position then bind the listview with the adapter again.
You can capture the View object and do what you want in a more neat way:
list.setOnItemClickListener(new OnItemClickListener()
{
public void onItemClick(AdapterView arg0, View arg1, int arg2,
long arg3) {
// TODO Auto-generated method stub
TextView temp=(TextView)arg1;
temp.setText(“changed 2”);
}
}
);
If the activity will contain just one listview you can create an activity that extends list view. In this case you don’t have to specify a layout as a listview will fill the screen.
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final String [] items=new String[]{“Item1″,”Item2″,”Item3″,”Item4”};
ArrayAdapter ad=new ArrayAdapter(this,android.R.layout.simple_list_item_1,items);
setListAdapter(ad);
If you want to reference or customize this listview then you can define it in the layouts xml fine by assigning it the id android:id=@android:id/list so that the activity knows which listView is the main list for the activity ..
This example shows a listview and a textview in a layouts xml file:
<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayoutxmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”vertical”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
>
<TextView
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:id=”@+id/txt”
android:text=”List View Demo”
/>
<ListView
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:id=”@android:id/list”
/>
</LinearLayout>
Now if you want to customize the ui of each row of the listview you define two layouts files: the first has the layout of the activity and the other has layout of each row in the listview. and pass the reference of the rows xml layout file to the constructor of the adapter that the list view is bound to.
Check Box:
The checkBox has two states: Checked and UnChecked. It inherits from TextView so it has all of its properties:
<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayoutxmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”vertical”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
>
<TextView
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:id=”@+id/txt”
/>
<CheckBox
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:id=”@+id/Chk”
android:text=”This is a check box”
android:checked=”false”
/>
</LinearLayout>
and to handle the check/uncheck events:
CheckBoxchk=(CheckBox)findViewById(R.id.Chk);
chk.setOnCheckedChangeListener(new OnCheckedChangeListener()
{
public void onCheckedChanged(CompoundButton arg0, boolean arg1) {
TextView txt=(TextView)findViewById(R.id.txt);
if (arg1)
txt.setText(“checked”);
else
txt.setText(“Unchecked”);
}
}
);
Radio Button:
Android provides RadioButton control. You create a RadioGroup and add RadioButtons inside it.
<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayoutxmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”vertical”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
>
<TextView
android:id=”@+id/txt”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
/>
<RadioGroup
android:id=”@+id/group”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
android:text=”Radio Group”
>
<RadioButtonandroid:id=”@+id/item1″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”Item1″
android:checked=”true”
/>
<RadioButtonandroid:id=”@+id/item2″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”Item2″ />
<RadioButtonandroid:id=”@+id/item3″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”Item3″ />
</RadioGroup>
</LinearLayout>
You can get the checked item from code like this:
RadioGrouprg=(RadioGroup)findViewById(R.id.group);
rg.setOnCheckedChangeListener(new android.widget.RadioGroup.OnCheckedChangeListener()
{
public void onCheckedChanged(RadioGroup arg0, int arg1) {
// TODO Auto-generated method stub
TextView txt=(TextView)findViewById(R.id.txt);
RadioButtonrb=(RadioButton)findViewById(arg1);
txt.setText(“You selected “+rb.getText());
}
}
);
and this was part one in the selection controls. in the next post we’re going to see the Spinner and the GridView Controls.
The Spinner
The Spinner control is similar to the ComboBox in C#. It displays a list to select from in a popup window so it may be a better choice over ListView if you have a lot items to display because it will save space.
It works in a similar way to that of the theListView
<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayoutxmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”vertical”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
>
<Spinner
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:id=”@+id/Spinner”
/>
</LinearLayout>
When you click on the spinner it popups like this:
The following code will handle the selected item:
final String [] items=new String[]{“Item1″,”Item2″,”Item3″,”Item4”};
ArrayAdapter ad=new ArrayAdapter(this,android.R.layout.simple_spinner_item,items);
ad.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
Spinner spin=(Spinner)findViewById(R.id.Spinner);
spin.setAdapter(ad);
spin.setOnItemSelectedListener(new OnItemSelectedListener()
{
public void onItemSelected(AdapterView arg0, View arg1,
int arg2, long arg3) {
TextView txt=(TextView)findViewById(R.id.txt);
TextView temp=(TextView)arg1;
txt.setText(temp.getText());
}
public void onNothingSelected(AdapterView arg0) {
// TODO Auto-generated method stub
}
});
The above code displays the selected item text in the textview.
The parameters of the OnItemClick method are:
AdapterView Arg0: the Spinner, notice that it is of type AdapterView.
- View Arg1: the view that represents the selected item, in this example it will be a TextView.
- int Arg2: the position of the selected item.
- long Arg3: the id of the selected item.
That is it for the Spinner control, we will now move on to the GridView.
GridView
The GridView is similar to ListView but it gives you the ability to control the look of the grid. You can specify the number of columns and columns width and spacing.
This example displays some items in a grid in a normal way:
<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayoutxmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”vertical”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
>
<GridView
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:id=”@+id/grid”
/>
</LinearLayout>
final String [] items=new String[]{“Item1″,”Item2″,”Item3″,”Item4″};
ArrayAdapter ad=new ArrayAdapter(this,android.R.layout.simple_list_item_1,items);
GridView grid=(GridView)findViewById(R.id.grid);
grid.setAdapter(ad);
This is a simple grid, similar to a ListView.
We can specify the number of columns that the grid has by specifying the android:numColumns property:
<GridView
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:id=”@+id/grid”
android:numColumns=”2″
/>
Or by using the code:
grid.setNumColumns(2);
The grid will be like this:
If we specified the number of columns to be three, it will be like this:
And so on, you get the idea.
We can also set the android:numColumns property to “auto_fit” so that the grid autommatically sets the number of columns according to the available space. We can control the vertical spacing between columns using android:verticalSpacing property:
<GridView
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:id=”@+id/grid”
android:numColumns=”2″
android:verticalSpacing=”100px”
/>
Or by using this code:
grid.setVerticalSpacing(150);
We can also control the horizontal spacing between columns using android:horizontalSpacing property:
<GridView
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:id=”@+id/grid”
android:numColumns=”2″
android:horizontalSpacing=”100px” />
Or by using this code:
grid.setHorizontalSpacing(150);
and that was it for the GridView.
- Types of resources
- String Resources
- String format Resources
- Styled text Resources
Image, Color, Arrays and Dimensions Resources
- Image Resources
- Color draw able Resources
- Color Resources
- Dimensions Resources
- Array Resources
Resources in Android are files stored under the res directory of your project. Resources can be physical files (Audio, video, images, text, etc…) or xml files that declare some values to use in your application.
Why use Resources:
- The resources are bound to the application in a way that you can change them without needing to change the source code or recompile the application.
- The Android Generates an ID for each resource file so you can access them directly and easily. All the resources IDs are added to the R.Java file.
- When you use XML resource files, Android pareses these files automatically so you can reference values defined in them directly with no extra effort.
- Using resources is very useful in Localization and Internationalization of the application in case you develop multilingual applications. This includes not just the labels but it can include alignment, directions images or any kind of files.
Types of resources:
- Strings, colors, arrays, dimensions. Defined in res/values/ directory. Using them is very useful in Localization and Internationalization.
- Images put in res/drawable directory. You can put all the images or icons you need in your application.
- Animations, defined in res/anime/ directory. You can define animations that for example correspond to a button click.
- XML, defined in res/xml/ directory for xml files containing your custom data.
- Layout resources, defined in res/layout/ for declaring the views that construct the user interface of the activities.
String Resources:
Android offers three types of string resources:
- Plain Strings.
- String formats.
- Styled texts.
Plain string resources can be declared in res/values/strings.xml
If you create an Android application [for example call it HelloAndroid] and just before adding anything, browse to res/values/strings.xml it will be like this:
Hello World, HelloAndroid!
HelloAndroid
This is a pretty basic example of Plain text resources. The resource with name=”app_name” is the name of the application that appears when you deploy the application to the phone, it’s referenced in the AndroidManifest.xml file in the application tab. You can change it as you want.
Now let’s add two entries in the file to see how can we use plain text resources within the application.
This is referenced from the res/layout/main.xml
This is referenced from the code
The first string will be the text of a text view defined in res/layout/main.xml file
See that to reference the first string we use the @string/[Resource Name] convention.
The second resource will be referenced from the code file of the activity like this
TextView txtHeader2=(TextView)findViewById(R.id.txtHeader2);
txtHeader2.setText(getString(R.string.plainResource2));
if you open R.java file of your project you will find that Android has generated a class called string with members referencing to your string resources:
public static final class string {
public static final intapp_name=0x7f040001;
public static final int hello=0x7f040000;
public static final int plainResource1=0x7f040002;
public static final int plainResource2=0x7f040003;
}
Also notice the way you access the string resources in Android, you don’t open the strings.xml file and parse it to extract the values you want to reference- instead you access them through the R.string class defined in R.Java and Android does the rest for you.
Also another interesting feature is that you can define your own resources file, go to res/values/ directory, right click>New>File and call the file for example CustomStrings
You will see something like this:
You can define resources manually by choosing the CustomStrings.xml tab or by using clicking Add button and adding the name and the value of the resource.
I will add a resource with the name CustomString and value this is a custom string and reference them from the layout like this:
<textviewandroid:id=”@+id/txtHeader1″ android:layout_height=”wrap_content” android:layout_width=”fill_parent” android:text=”@string/CustomString”>
Or from the code like this:
TextView txtHeader3=(TextView)findViewById(R.id.txtHeader3);
txtHeader3.setText(getString(R.string.CustomString));
String format Resources:
thedalvikvm offers string formats whih provide placeholders representing data to be replaced at runtime by variables
an example of a string format resource is:
This is resource for %1$s
the %1$s is the place holder that would be replaced by a variable string.
and from your code you can use it like this:
//String format resource
TextView txtHeader4=(TextView)findViewById(R.id.txtHeader4);
String strFormat=getString(R.string.StringFormat);
String header=String.format(strFormat, “Giving an example of string format resource”);
txtHeader4.setText(header);
and the text view will have a text equal to This is resource for Giving an example of string format resource.
Styled text Resources:
You can use string resources styled with these three HTML tags:
- <b>.
- <u>.
- <i>.
You can define the a string as follows:
This is an <u>example</u> of <i>Styled</i><b>Resources</b>
And use it from the code as this:
TextView txtHeader5=(TextView)findViewById(R.id.txtHeader5);
txtHeader5.setText(R.string.StyledResource);
notice that we use setText() method by calling the string resource directly.
if we use the getString() method it will display the string without styling
we can use the HTML styled text also by using spanned class.
Spanned textSpan = android.text.Html.fromHtml(htmlTaggedString);
//Set it in a text view
textView.setText(textSpan);
In this post we saw how Android provides three types of string resources: Plain strings, String formats which provide place holders to be replaced by variables in runtime and Styled text resources which provide styling with three standard HTML tags.
Image, Color, Arrays and Dimensions Resources
Image Resources:
Android provides us with the ability to use image resources in our applications. We can put image files in res/drawable directory and access them from the xml layout or by the generated ID from R.java class file.
You can use image files of the following formats:
- PNG
- JPEG
- GIF
we have three drawable folders:
- drawable-hdpi.
- drawable-mdpi
- drawable-ldpi.
These folders are used to put your images in to adapt to different screen sizes. For example you may create two files of the same image, one for high density screens (hdpi) and the other with smaller resolution for less dense screens (mdpi or ldpi).
Note that there are two restrictions:
- If you use two files with the same base name you will receive an error. You cannot use two files like Hello.jpeg and Hello.png.
- If you create any subdirectory under res/drawable directory any image files in it will be ignored.
Now let’s demonstrate how can we use image resources.
We will place an image called android.jpeg in te res/drawable directory and use it from xml layout as this:
<imageviewandroid:layout_height=”wrap_content” android:layout_width=”wrap_content” android:src=”@drawable/android” />
Or from code like this
ImageViewimg=(ImageView)findViewById(R.id.img);
img.setImageResource(R.drawable.android);
Or like this to get the image as a drawable object
ImageView img2=(ImageView)findViewById(R.id.img2);
Drawabledrawable=this.getResources().getDrawable(R.drawable.android);
img2.setBackgroundDrawable(drawable);
Color drawable Resources:
You can define XML files that contain definitions for color drawable resources which are color rectangles that can be used as backgrounds.
You can define the color drawable resources in values/strings.xml file or by creating a custom xml file to hold these resources.
Define color drawable resources like this:
<drawable name=”redBox”>#f00</drawable>
Or from code like this:
TextView txt=(TextView)findViewById(R.id.txt);
txt.setBackgroundResource(R.drawable.redBox);
Or like this:
ColorDrawable drawable2=(ColorDrawable)this.getResources().getDrawable(R.drawable.redBox);
txt.setBackgroundDrawable(drawable2);
And finally like this:
txt.setBackgroundResource(R.drawable.redBox);
Notice that if the textview does not have text the background color will not appear.
Color Resources:
Color resources are defined as string resources in res/values directory
You can define XML files that contain definitions for colors that can be used in your application.
Colors in Android are hexadecimal RGB values, also optionally specifying an alpha channel (transparency).
You have your choice of single-character hex values or double-character hex values, leaving you with four styles:
- #RGB (Red: #F00).
- #ARGB (Red with Alpha 5: #5F00).
- #RRGGBB (Red : #FF0000).
- #AARRGGBB (Red with Alpha 50: 50FF0000 #).
You define colors in the xml file as follows:
<color name=”Red”>#FF0000</color>
Now you can use it from code like this:
TextViewtxtColor=(TextView)findViewById(R.id.txtColor);
txtColor.setTextColor(this.getResources().getColor(R.color.Red));
Or from the layout as this:
<textviewandroid:layout_height=”wrap_content” android:layout_width=”fill_parent” android:text=”This is a color string from layout” android:textcolor=”@color/Red” />
Dimensions Resources:
You can define Dimension resources to use them with your widgets to define padding, width or height.
The dimension resources can be defined in the following units:
- Pixels: (px).
- Inches: (in).
- Millimeters: (mm).
- Points: (pt).
- Density: (dp) density-independent pixels based on 160 dpi (dot per inch).
- Scale: (sp) Scale-independent pixels (dimensions that allow for user sizing; helpful for use in
fonts).
To define dimension resources in xml files you can write it like this:
<dimen name=”PixelDim”>10px</dimen>
<dimen name=”PointsDim”>10pt</dimen>
<dimen name=”InchesDim”>0.2in</dimen>
<dimen name=”MilliDim”>5mm</dimen>
<dimen name=”DensityDim”>20dp</dimen>
<dimen name=”ScaleDim”>20sp</dimen>
And you can use them from the xml layout definition like this
<textviewandroid:id=”@+id/txtInches” android:layout_height=”wrap_content” android:layout_width=”fill_parent” android:text=”Font size is 0.2 Inches” android:textsize=”@dimen/InchesDim” />
Or from code like this:
TextViewtxtdp=(TextView)findViewById(R.id.txtdp);
txtdp.setTextSize(this.getResources().getDimension(R.dimen.DensityDim));
Array Resources:
Array resources allow you to define custom string arrays that hold values you can use in your application such as a countries list or a list of names.
An Array resource is defined using string-array element while items are defined using item element.
<string-array name=”countries”>
<item>USA</item>
<item>UK</item>
<item>Canada</item>
</string-array>
and you can use them from code like this:
String [] Countries=this.getResources().getStringArray(R.array.countries);
You can define more than one string-array in the same file.
Android App Development: Activity Life Cycle
The activity is the core of an android application. Each application can have one or more activities.
Activities in the system are managed in an activity stack (Last in Last out). When a new activity is launched it becomes on the top of the stack. Any previous activity will be below it and won’t come to the top until the new one exists.
The application on the top of the stack has the highest priority from the operating system. While the activity that is not visible has lower priority even if the a running activity requires more memory, the system can shut down that activity to free memory.
Android runs each activity in a separate process each of which hosts a separate virtual machine. Android saves metadata (state) of each activity so that when a new activity launches so that it can come back to the activity when the user backtracks.
The activity can be in one of four states:
Active: the activity started, is running and is in the foreground.
Paused: the activity is running and visible but another activity (non full sized) is running on the top or a notification is displayed. The user can see the activity but can not interact with it. A paused activity is fully alive (maintains state and member information) but can be killed by the system in low memory situations.
Stopped: the activity is running but invisible because the user has launched another activity that comes to the foreground the activity is alive (maintains state and member information) but can be killed by the system in low memory situations.
Dead: either the activity is not started or it was in pause or stop state and was terminated by the system to free some memory or by asking the user to do so.
The following figure shows the states of the activity and the methods that handle each state
The sequence is as follows:
- The activity starts, passes through onCreate(), onStart() the activity is still not visible to the user, onResume() then it comes to the foreground and becomes fully running.
- If another activity launches or a notification appears the activity passes through the onPause() method. Then there would be two scenarios:
1.if the system decides to kill your activity due to low memory the activity starts the cycle again from onCreate() method with Bundle savedInstanceState parameter that holds data about the previous state of the activity.
2.If the user resumes the activity by closing the new activity or the notification the activity cycle continues from the onResume() method - When the user is about to close the activity the activity calls onStop() method then onDestroy()method when the system destroys the activity.
But if another activity runs while the current one is was not shut, the activity calles onStop()method and if it is not killed by the system it will call onRestart() method then onStart() mehod and continues the cycle. - onCreate(): will be invoked in three cases:
– the activity runs for the first time and it will be invoked with null Bundle savedInstanceState parameter.
– the activity has been running then stopped by the user or destroyed by the system then it would be invoked with Bundle savedInstanceState that holds the previous state of the activity.
– the activity is running and you set the device to different resources like Portrait vs landscape, then the activity will be recreated.in this method you will create the user interface, bind data to controls and register the event handlers for the controls. Then it is followed by onStart()method. - onStart(): will be invoked when the activity is first launched or brought back to the foreground
it would be followed by onResume() if the activity continues and comes to foreground, or byonStop() if the activity is killed. - onRestart(): is invoked in case the activity has been stopped and is about to be run again. Always followed by onStart() mehod.
- onResume(); invoked when the activity is about to come to the foreground and is on the top of the activity stack. It is the place where you can refresh the controls if the activity is using a service that displays some feeds or news. Always followed by onPause() method.
- onPause(): is invoked when another activity launches while the current activity is launched or when the system decides to kill the activity. In this method you have to cancel everything you did in onResume() method like Stopping threads, stopping animations or clearing usage of resources(eg the camera).This method is followed by onResume() if the activity returns back to front or by onStop() if the activity is to be invisible.
- onStop(): is invoked when a new activity is about to come over the current one or the current one is to be destroyed. Always followed by onResume() if the activity comes back oronDestroy() if the activity is to be killed.
- onDestroy():is invoked when the activity is shutting down because the activity called finish()[terminated the activity] or because the system needs memory so it decided to kill the activity.
Killable methods:
There are methods that are “killable” meaning that after theses methods return, the process hosting them can kill the activity without executing any further code (due to lack of memory)
These methods are onPause(), onStop() and onDestroy()
here’s the declaration of the life cycle methods:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
protected void onStart() {
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onRestart() {
super.onRestart();
}
@Override
protected void onStop() {
super.onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
Summary:
- The entire activity life cycle is between the onCreate() where you construct the UI and aquire resources and onDestroy() method where you release all resources.
- The visible life time of the activity is between onStart() and onStop(). Between the activity is visible to the user although he may be unable to interact with it. Between the two methods you persist the state of the activity so that if another one comes to the foreground then comes back to the original activity you find the state persisted.
- The foreground lifetime is between the onResume() and onPause(). During this time the activity is fully interactive with the user. The activity can go through the resume and pause states many times (if the device sleeps or a new activity launches) .
- Anatomy of Tabbed Application
- Setting the content of tabs
- Adding tabs at run-time
- iPhone-Like Tab
Some times we want to wrap multiple views in a single window and navigate throught them with a Tab Container. this can be done in Android using TabHost control
There are two ways to use a TabHost application in Android:
- Using the TabHost to navigate through multiple views within the same activity.
- Using the TabHost to navigate through Actual multiple Activities using intents.
we will explain both ways.:
Anatomy of Tabbed Application
An activity with a TabHost may look like this:
The Activity consists of:
- A TabHost: the root element of the layout.
- The TabHost wraps a TabWidget which represents the tab bar.
- The TabHost wraps a FrameLayout which wraps the contents of each tab.
There are some rules that we must stick to when using a Tabbed activity:
- If the activity is of type TabActivity [optional] then the TabHost must have the id@android:id/tabhost.
- The TabWidget must have the id @android:id/tabs.
- The FrameLayout must have the id @android:id/tabcontent.
now let’s see an example to an activity with multiple tabs:
this is the layout
<?xml version=”1.0″ encoding=”utf-8″?>
<TabHost android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
android:id=”@+id/tabHost”
xmlns:android=”http://schemas.android.com/apk/res/android”
>
<TabWidget
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:id=”@android:id/tabs”
/>
<FrameLayout
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
android:id=”@android:id/tabcontent”
>
<LinearLayout
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:id=”@+id/tab1″
android:orientation=”vertical”
android:paddingTop=”60px”
>
<TextView
android:layout_width=”fill_parent”
android:layout_height=”100px”
android:text=”This is tab1″
android:id=”@+id/txt1″
/>
</LinearLayout>
<LinearLayout
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
android:id=”@+id/tab2″
android:orientation=”vertical”
android:paddingTop=”60px”
>
<TextView
android:layout_width=”fill_parent”
android:layout_height=”100px”
android:text=”This is tab 2″
android:id=”@+id/txt2″
/>
</LinearLayout>
<LinearLayout
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
android:id=”@+id/tab3″
android:orientation=”vertical”
android:paddingTop=”60px”
>
<TextView
android:layout_width=”fill_parent”
android:layout_height=”100px”
android:text=”This is tab 3″
android:id=”@+id/txt3″
/>
</LinearLayout>
</FrameLayout>
</TabHost>
then in the code of our activity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
TabHost tabHost=(TabHost)findViewById(R.id.tabHost);
tabHost.setup();
TabSpec spec1=tabHost.newTabSpec(“Tab 1”);
spec1.setContent(R.id.tab1);
spec1.setIndicator(“Tab 1”);
TabSpec spec2=tabHost.newTabSpec(“Tab 2”);
spec2.setIndicator(“Tab 2”);
spec2.setContent(R.id.tab2);
TabSpec spec3=tabHost.newTabSpec(“Tab 3”);
spec3.setIndicator(“Tab 3”);
spec3.setContent(R.id.tab3);
tabHost.addTab(spec1);
tabHost.addTab(spec2);
tabHost.addTab(spec3);
}
is going to look like this:
- We create tabs using TabSpecs class.
- We set the title of each tab using TabSpecs.setIndicator() method.
- We set the content of each tab using TabSpecs.setContent() method.
- if you use TabActivity to as a base class to your activity, you do not need to callTabHost.Setup() method.
We can add an icon to the title of the tab like this:
TabSpec spec1=tabHost.newTabSpec(“Tab 1”);
spec1.setContent(R.id.tab1);
spec1.setIndicator(“Tab 1”,getResources().getDrawable(R.drawable.flash));
TabSpec spec2=tabHost.newTabSpec(“Tab 2”);
spec2.setIndicator(“Tab 2”,getResources().getDrawable(R.drawable.sun));
spec2.setContent(R.id.tab2);
TabSpec spec3=tabHost.newTabSpec(“Tab 3”);
spec3.setIndicator(“Tab 3”,getResources().getDrawable(R.drawable.chart));
spec3.setContent(R.id.tab3);
it will look like this:
we can also specify the indicator to be a view:
TabSpec spec1=tabHost.newTabSpec(“Tab 1”);
spec1.setContent(R.id.tab1);
TextView txt=new TextView(this);
txt.setText(“Tab 1”);
txt.setBackgroundColor(Color.RED);
spec1.setIndicator(txt);
Setting the content of tabs:
we saw how to set the contents of tabs by specifying multiple layout resources to be displayed within the same activity.
what If we have multiple Activities in our application and we want to navigate between them using tabs ?
in this case we will have one activity as the root activity of the application. this activity will have theTabHost and will navigate to other activities using Intents.
Note: the root activity must inherit from TabActivity.
the root activity will have layout file like this:
<?xml version=”1.0″ encoding=”utf-8″?>
<TabHost android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
android:id=”@android:id/tabhost”
xmlns:android=”http://schemas.android.com/apk/res/android”
>
<TabWidget
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:id=”@android:id/tabs”
/>
<FrameLayout
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
android:id=”@android:id/tabcontent”
>
</FrameLayout>
</TabHost>
the other activities will have a simple layout consisting of a TextView.
now to the code of the root activity
public class TabDemo extends TabActivity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
TabHost tabHost=getTabHost();
// no need to call TabHost.Setup()
//First Tab
TabSpec spec1=tabHost.newTabSpec(“Tab 1”);
spec1.setIndicator(“Tab 1”,getResources().getDrawable(R.drawable.sun));
Intent in1=new Intent(this, Act1.class);
spec1.setContent(in1);
// Second Tab
TabSpec spec2=tabHost.newTabSpec(“Tab 2”);
spec2.setIndicator(“Tab 2”,getResources().getDrawable(R.drawable.chart));
Intent in2=new Intent(this,Act2.class);
spec2.setContent(in2);
tabHost.addTab(spec2);
tabHost.addTab(spec3);
}
}
and the activity will look like this:
Adding tabs at run-time:
we can add tabs to TabHost at run-time using TabSpec.setContent(TabContentFactory) method.
the TabContentFactory is an interface that requires the implementation of a callback methodcreateTabContent(String tag) which returns the view to be added to the content of the tab.
so in the last example if we changed code that adds the content of the second tab to this:
TabSpec spec1=tabHost.newTabSpec(“Tab 1”);
spec1.setIndicator(“Tab 1”,getResources().getDrawable(R.drawable.sun));
spec1.setContent(new TabContentFactory() {
@Override
public View createTabContent(String tag) {
// TODO Auto-generated method stub
return (new AnalogClock(TabDemo.this));
}
});
the activity will look like this:
iPhone-Like Tabs:
to give the Android tabs the same look of the iPhone tabs we wrap the TabWidget and theFrameLayout in a RelativeLayout container and addingandroid:layout_alignParentBottom=”true” attribute to the TabWidget just like this:
<?xml version=”1.0″ encoding=”utf-8″?>
<TabHost android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
android:id=”@android:id/tabhost”
xmlns:android=”http://schemas.android.com/apk/res/android”
>
<RelativeLayout
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
>
<TabWidget
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:id=”@android:id/tabs”
android:layout_alignParentBottom=”true”
/>
<FrameLayout
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
android:id=”@android:id/tabcontent”
>
</FrameLayout>
</RelativeLayout>
</TabHost>
Using Themes and Styles in Android
· Styles
· Inheriting Styles
- Using Themes
Using Themes and Styles in Android
In web design we have the concept of Styles and Themes. Styles like Cascading Style Sheets (CSS) define the values of the web controls attributes such as width, height, font color, background and so on. A style can be applied on several controls in several web pages.
Themes are used to group a set of styles to be applied on the whole web application. Themes (or sometimes skins) define the look of all control within the application.
Android introduces similar concepts by using Styles and Themes. A Style can be applied to views individually while a Theme is applied to a whole activity.
Styles:
Styles are defined as xml resources files in res/values directory of your project.
consider this definition of a TextView:
<TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:textColor="#FFF" android:typeface="monospace" android:text="First Text View" android:background="#00F" />
this defines a text view with width and height equal to wrap_content, white font color, font type “monospace” and blue back ground.
if we want to have the same results using a style: we first create a xml file (call it styles.xml) inres/values directory and it would be like this:
<?xml version="1.0" encoding="utf-8"?> <resources> <style name="BlueLabel"> <item name="android:layout_width">fill_parent</item> <item name="android:layout_height">wrap_content</item> <item name="android:typeface">monospace</item> <item name="android:background">#00F</item> <item name="android:textColor">#FFF</item> </style> </resources>
then redefine the TextView like this:
<TextView android:text="First Text View" style="@style/BlueLabel" />
and you will receive the same results.
the attributes in the <item>tag can be any layout property.
Inheriting Styles:
Styles in Android has an interesting feature which is the ability to inherit styles in a fashion similar to that in CSS. consider this example:
we have a style for a button like this:
<style name="ButtonStyle"> <item name="android:layout_width">wrap_content</item> <item name="android:layout_height">wrap_content</item> <item name="android:textSize">15px</item> <item name="android:typeface">serif</item> </style>
the button will appear like this:
we can make this style inherit from BlueLabel defined previously by adding the parent attribute to the <style*gt; tag:
<style name="ButtonStyle" parent="BlueLabel">
then the button will be like this:
the button inherited the background color from the parent style.
another interesting feature in styles inheritance is the ability to inherit from the platform built-in styles defined in the android.R.style namespace. to know more about the platform styles check this link
or you can type in your editor (Eclipse) android.R.style and let the intelli-sense list you the complete list of platform styles, if you want to use them in your xml just replace the undrscores with a period like this:
Widget_Button becomes @android:style/Widget.Button.
In the previous button style example we will set the parent of the style to be@android:style/Widget.Button.Small
and the button will be like this:
Note: if you apply a style to a ViewGroup widget, it’s child widgets will not inherit that style.
Using Themes:
you can apply styles as themes on an activity level or application level.
if you apply a theme on an activity level then all widgets within that activity will inherit from that theme.
to do so, open the AndroidManifest.xml and go the <activity> tag and add the android:themeattribute:
<activity android:name=".StylesDemo" android:label="@string/app_name" android:theme="@style/BlueLabel">
to apply a theme on the application level so that the style will be applied to all activities within your application, open the AndroidManifest.xml and go the <application> tag and add theandroid:theme attribute:
<application android:icon="@drawable/icon" android:label="@string/app_name" android:theme="@style/BlueLabel">
to set the theme of an activity programmatically call this line in the onCreate method
this.setTheme(R.style.BlueLabel);
and that’s was all about using themes and styles in Android, stay tuned for another topic next week.
- Using Intents to launch phone activities
- Intent Filters
Using intents to pass data and return results between activities
- Passing Results between Intents:
- Receiving Extra data in sub activity
- Handling sub activity results
Intents are used by an application to interact with the phone’s hardware components or other applications, or to start a service or activity with a certain piece of data or to broadcast that an event has occurred.
Using Intents to launch phone activities:
We can use Intents to launch the phone’s basic activities such as the phone dialer, the browser or search.
These intents are called Implicit Intents cause you don’t specify the activity you want to launch, rather Android determines the proper activity to launch based on the required action. Also when the launched activity finisheds its work, the original activity has no information that the launched activity has finished it’s work
In this example we create an intent that performs a phone number dial action. We don not specify that we want the dialer activity to launch, rather we specify that we want to dial a number and Android launches the dialer activity to perform this action
Consider this activity: consists of a TextView and a button to dial the number in the text view.
<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”vertical”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
>
<TextView
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:text=”Enter the phone number”
/>
<EditText
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:id=”@+id/txtNumber”
android:inputType=”phone”
/>
<Button
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”Dial”
android:id=”@+id/btnDial”
/>
</LinearLayout>
When you press the button the phone dialer launches and then you can call the number.
This is done using the following code:
btnDial.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// TODO Auto-generated method stub
Intent
dialIntent=new Intent(Intent.ACTION_DIAL,Uri.parse(“tel:”+(txtNumber.getText()).toString()));
startActivity(dialIntent);
}
});
Notice that the dialer has been launched but the user has to press the call button to make a call.
If you want the phone to dial the number automatically you could have used this intent:
Intent.ACTION_CALL
If you use this it requires adding the following permission to the manifest file:
<uses-permission android:name=”android.permission.CALL_PHONE”>
Another example to launch an intent to open the browser and navigate to a certain URL:
Intent in=new Intent(Intent.ACTION_VIEW, Uri.parse(“http://mobileorchard.com”));
startActivity(in);
For a list of available phone actions, check this link
Intent Filters:
Android components like activities can also serve implicit intents. but to do so they have to filter all implicit intents in order to serve only the intents they desire to serve. this is done using intent filters.
Suppose you want to create an activity that acts as the default dialer activity. You must associate an intent filter with this activity in order to that this activity serve the dial intents only.
Let’s demonstrate a simple example which is creating an application with one activity that we want to make it a dialer activity.
Create a new android project, create an activity and name it Dialer.
In the AndroidManifest.xml file of this application add the following to the Dialer activity:
<intent-filter android:priority=”100″ >
<action android:name=”android.intent.action.DIAL” />
<category android:name=”android.intent.category.DEFAULT” />
<data android:scheme=”tel”/>
</intent-filter>
to become:
<activity android:name=”.Dialer”
android:label=”@string/app_name”>
<intent-filter>
<action android:name=”android.intent.action.MAIN” />
<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
<intent-filter android:priority=”100″ >
<action android:name=”android.intent.action.DIAL” />
<category android:name=”android.intent.category.DEFAULT” />
<data android:scheme=”tel”/>
</intent-filter>
</activity>
Now what we have done is adding an intent filter to that activity. this intent filter has the following properties:
- Action: the type of implicit intents that this activity responds to. in our case it is the dial action. higher numbers represent higher priority.
- Priority: about the priority of that activity over other activities that respond to the same type of intents.
- Category: Implicit intents have built-in categories. in order that an implicit intent be captured by our activity, the implicit intent category must match our activity category.
- Data: adds data specification scheme to the intent filter.
So if any other application has the following module to launch the dialer:
Intent in=new Intent(Intent.ACTION_DIAL, Uri.parse(“tel:000”));
startActivity(in);
The user will see the following dialog offering him/her the choice between the default dialer and our custom dialer.
Using intents to pass data and return results between activities
.
We will make an application with three activities, each activity has a button that when pressed navigates to the next activity in a round-robin fashion.
this is what each activity looks like:
Each button will navigate to the next activity like this:
btn1.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// TODO Auto-generated method stub
Intent intent=new Intent(IntentsDemo2.this,Activity2.class);
startActivity(intent);
}
});
and the same for each button in the other activities.
We created the intent with a constructor that takes two parameters:
- Context: a reference to the current activity.
- Class: the type of the activity to be launched by the intent.
Passing Results between Intents:
By default when you launch an activity using an intent you don’t have a feedback on the whether the launched activity finished it’s work or not.
We can launch an activity as a sub activity of a parent activity. This means that when the sub activity closes, it triggers an event handler in the parent.
Consider the previous example, we want each activity to display the name of the activity that was displayed before it. So we launch the intent normally but we use the Intent.putExtra(String Name,String Value) method to pass any extra data needed.
In the first activity we launch the second activity like this:
Intent intent=new Intent(IntentsDemo2.this,Activity2.class);
intent.putExtra(“ComingFrom”, “Acticity 1”);
final int result=1;
startActivityForResult(intent, result);
We did not use the startActivity method as we did before, instead we usedstartActivityForResult(Intent intent, int requestCode)
thich takes two parameters:
- The intent to start.
- Request code: which is an integer identifier that is used as a corelation id to identify which sub activity has finished it’s work (will explain later) .
So the above code launches a sub activity and passes it an extra peice of information viaIntent.putExtra method.
Receiving Extra data in sub activity:
Now the sub activity has been started with an intent from the parent with some extra data. to retreive this data in the sub activity we use the getIntent() method.
The getIntent() method returns a reference t the intent tha started the sub activity.
So in the sub activity we can call the method like this:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main2);
Intent sender=getIntent();
String extraData=sender.getExtras().getString(“ComingFrom”);
}
This retreives the extra data added to the intent in the parent activity.
Handling sub activity results:
Now suppose that the parent activity wants to know what was the result returned from the sub activity or wants to get some data from the sub activity, this is handled by overriding theonActivityResult method in the parent activity.
Now suppose that the sub activity wants to pass a string containg the word “Hello” to the parent activity.
In the sub activity there is a button that when pressed returns to the parent activity. The button’s click event handler can be like this:
btn.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// TODO Auto-generated method stub
Intent intent=new Intent();
intent.putExtra(“ComingFrom”, “Hello”);
setResult(RESULT_OK, intent);
finish();
}
Here we used the intent.putExtra method to pass the extra data.
we also used the setResult(int result,Intent intent) method to set a result code to be sent to the caller parent activity.
The result code often has two predefined values Result_OK or Result_CANCELED. you can define any result value you want.
Also we called finish() method that closes the activity and returrns to the caller activity.
When the finish() method is invoked in the sub activity, the onActivityResult callback method is invoked in the caller activity.
So when overriding the onActivityResult method in the caller activity we can get the data passed from the sub activity.
@Override
public void onActivityResult(int requestCode,int resultCode,Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
String extraData=data.getStringExtra(“ComingFrom”));
}
35. Options menu
- Adding Icons to menu items
- Handling Menu Items Events
- Using onOptionsItemSelected method
- Using listeners
- 36. Context Menus
Options menu
.
Android phones have the menu button which displays a menu with several items that provide navigation or more functionality or settings to your applications.
Android has three types of menus
- Options menu.
- Context menu.
- Alternative menus.
Menu items can be grouped and each menu item can have submenu items.
Options Menu:
Each activity has a single menu. It appears when you press the menu button Each activity creates this menu in the callback method onCreateOptionsMenu you can override this method and add items to the menu.
If you press the menu button you will see the menu like this:
public class MenusDemo extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
menu.add("Item1");
menu.add("Item2");
return true;
}
}
Notice that the onCreateOptionsMenu method returns a Boolean. It should return true for the menu to appear, if returns false the menu will be invisible.
If you add many menu items, more than five items they will appear like this:
public boolean onCreateOptionsMenu(Menu menu)
{
menu.add(1, 1, 0, "Item1");
menu.add(1, 2, 1, "Item2");
menu.add(1, 3, 2, "Item3");
menu.add(1, 4, 3, "Item4");
menu.add(1, 5, 4, "Item5");
menu.add(2, 6, 0, "Item6");
menu.add(2, 7, 1, "Item7");
menu.add(2, 8, 2, "Item8");
menu.add(2, 9, 3, "Item9");
menu.add(2, 10, 4, "Item10");
return true;
}
When you press on the “more” link you will see the rest of the menu items:
The menu.add(int GroupID,int ItemID,int Order,String Title) method has the following parameters:
- GroupID: used to group related menu items to apply some configurations on all items of a certain group once at a time (discussed later) .
- ItemID: an integer to identify the menu item.
- Order: the sort order or the order ID, controls the order of menu items in a menu. Items with lower order id appear first. There are some constants defined for sort order for different kinds of menu items
Like Menu.CATEGORY_SECONDRY for secondry menu items, Menu.CATEGORY_SYSTEM for system menu items and Menu.CATEGORY_ALTERNATIVE for altenative menu items.. - Title: the text of the menu item
We could have written the previous code using the predefined the sort order constants like this:
menu.add(1, 1, Menu.FIRST, "Item1"); menu.add(1, 2, Menu.FIRST+1, "Item2"); menu.add(1, 3, Menu.FIRST+2, "Item3"); menu.add(1, 4, Menu.FIRST+3, "Item4"); menu.add(1, 5, Menu.FIRST+4, "Item5"); menu.add(2, 6, Menu.CATEGORY_SECONDARY, "Item1"); menu.add(2, 7, Menu.CATEGORY_SECONDARY+1, "Item2"); menu.add(2, 8, Menu.CATEGORY_SECONDARY+2, "Item3"); menu.add(2, 9, Menu.CATEGORY_SECONDARY+3, "Item4"); menu.add(2, 10, Menu.CATEGORY_SECONDARY+4, "Item5");
Adding Icons to menu items:
We saw that only the first five items can appear in the options menu and the other items appear in a dialog when you press “more” item.
For the first five items you can add an icon to appear next to the item’s title:
menu.add(1, 1, 0, "Blu-Ray").setIcon(R.drawable.bluray); menu.add(1, 2, 1, "DVD").setIcon(R.drawable.dvd); menu.add(1, 3, 2, "Hard Disk").setIcon(R.drawable.hd); menu.add(1, 4, 3, "Sites").setIcon(R.drawable.sites); menu.add(1, 5, 4, "USB").setIcon(R.drawable.usb); menu.add(2, 6, 5, "Item1"); menu.add(2, 7, 6, "Item2"); menu.add(2, 8, 7, "Item3"); menu.add(2, 9, 8, "Item4"); menu.add(2, 10, 9, "Item5");
Grouping Menu Items:
We saw that the method we use to add menu items us Menu.Add(GroupID,ItemId,OrderID,Title).
The first parameter is GroupID, what is it ? the answer leads us to grouping Menu Items.
Grouping menu items makes it easy to apply some options on some related menu items by applying these options on all menu items that belong to a group once instead of applying them on each single item at a time.
The actions that can be applied on a group can be:
- Menu.removeGroup(int GroupID): to remove all menu items belonging to the same group.
- Menu.setGroupCheckable(int GroupID,Boolean Checakble,Boolean Exclusive): maked a group appear with a check mark beside each item. If the Boolean Exclusive is set to true then the items will appear as if they are in a radiobuttongroup and only one item can be selected at a time. If set to false then they will appear with check boxes beside each item and multiple items can be selected at a time.
- Menu.setGroupEnabled(int GroupID,Boolean Enabled): sets all items in a group to be enabled/disabled.
- Menu.setGroupVisible(int GroupID,Boolean Visible): sets all items in a group to be visible/invisible.
Note: the methods mentioned above can be applied only to menu items in the secondary menu (items that appear when you click the more button).
Adding Alpha-Numeric shortcuts to menu Items:
You can add alphabetic or numeric shortcuts to menu items like this:
menu.add(2, 8, 7, "Item3").setNumericShortcut('3');
menu.add(2, 9, 8, "Item4").setAlphabeticShortcut('c');
then setting the querty mode of the menu to true like this:
menu.setQwertyMode(true);
Handling Menu Items Events:
We can handle menu items events by three ways:
- By implementing onOptionsItemSelected method.
- By implementing listeners to single menu items.
- By using intents.
Using onOptionsItemSelected method:
Implement the onOptionsItemSelected method like this:
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
TextView txt=(TextView)findViewById(R.id.txt);
switch(item.getItemId())
{
case 1:
txt.setText("you clicked on item "+item.getTitle());
return true;
case 2:
txt.setText("you clicked on item "+item.getTitle());
return true;
case 3:
txt.setText("you clicked on item "+item.getTitle());
return true;
}
return super.onOptionsItemSelected(item);
}
Notice that we return true for every handled menu item. And for un handled menu items (outside switch block) we call the super class method.
Using listners:
We can handle options menu items click events by making the activity implementonMenuItemClickListner interface provide an implementation of onMenuItemClick method like this:
public class MenusDemo extends Activity implements OnMenuItemClickListener
Then implement the method:
public boolean onMenuItemClick(MenuItem item) {
TextView txt=(TextView)findViewById(R.id.txt);
txt.append("listner");
return false;
}
Notice that the method returns a Boolean. If it returns true no other callbacks will be executed. If returns false then onOptionsItemSelected callback will be executed directly after this callback.
Using intents:
You can specify an intent to be launched when an options menu item is clicked like this:
menu.add(1, “dialItem”, 1, "Dial").setIcon(R.drawable.dvd).setIntent(new Intent(Intent.ACTION_DIAL));
Note that if you specify an intent for an item and at the same time override theonOptionsItemSelected method and handle the selection for that item, the precedence of execution is to the code in the onOptionsItemSelected.
Meaning that the code in onOptionsItemSelected method will be executed first, if it returns true then the intent will not be launched, if returns false then the intent will launch.
So if you want to use intent for menu items don’t handle it in onOptionsItemSelected method but invoke the parent onOptionsItemSelected.
Scenario 1:
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
menu.add(1, “dialItem”, 1, "Dial").setIcon(R.drawable.dvd).setIntent(new Intent(Intent.ACTION_DIAL)); return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
TextView txt=(TextView)findViewById(R.id.txt);
switch(item.getItemId())
{
case 1:
txt.append("you clicked on item "+item.getTitle());
return true;
case “dialItem”:
txt.setText("you clicked on item "+item.getTitle());
return true;
case 3:
txt.setText("you clicked on item "+item.getTitle());
return true;
}
return super.onOptionsItemSelected(item);
}
This will execute the code of the onOptionsItemSelected method and the dialer intent will not be launched.
Scenario 2:
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
menu.add(1, “dialItem”, 1, "Dial").setIcon(R.drawable.dvd).setIntent(new Intent(Intent.ACTION_DIAL));
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
TextView txt=(TextView)findViewById(R.id.txt);
switch(item.getItemId())
{
case 1:
txt.append("you clicked on item "+item.getTitle());
return true;
case “dialItem”:
txt.setText("you clicked on item "+item.getTitle());
return false;
case 3:
txt.setText("you clicked on item "+item.getTitle());
return true;
}
return super.onOptionsItemSelected(item);
}
This will execute the code of the onOptionsItemSelected method and then dialer intent will be launched.
Scenario 3:
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
menu.add(1, “dialItem”, 1, "Dial").setIcon(R.drawable.dvd).setIntent(new Intent(Intent.ACTION_DIAL)); return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
TextView txt=(TextView)findViewById(R.id.txt);
switch(item.getItemId())
{
case 1:
txt.append("you clicked on item "+item.getTitle());
return true;
case 3:
txt.setText("you clicked on item "+item.getTitle());
return true;
}
return super.onOptionsItemSelected(item);
}
the onOptionsItemSelected does not handle the “dialerItem” selection, so The dialer intent will be launched directly.
Context menus are the menus that appear when you right-click in windows.
In android Context menu are attached to widgets and the appear when you click on the widget for a long time (long click).
To add context menus to widgets you have to do two things:
- Register the widget for a context menu.
- Add menu items to the context menu.
So to register a context menu with a TextView you do it like this:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
txt=(TextView)findViewById(R.id.txt);
registerForContextMenu(txt);
}
We call registerForContextMenu(View V) method to tell the activity that the view is going to have a context menu.
Then we add items to the context menu by overriding the onCreateContext method:
@Override
public void onCreateContextMenu(ContextMenu menu,View v,ContextMenuInfo info)
{
menu.setHeaderTitle(“Context Menu”);
menu.add(“Item”);
}
When you make a long click on the text view the context menu will appear like this:
The onCreateContextMenu method has the following parameters:
- Context Menu: an instance of the context menu.
- View: the view to which the context menu is attached.
- ContextMenuInfo: carries additional information regarding the creation of the context menu.
To handle the context menu items click events you can implement the callback methodonContextItemSelected.
We do it the same way we handle onOptionsItemSelected.
@Override
public boolean onContextItemSelected(MenuItem item)
{
txt.setText(item.getTitle());
return true;
}
and this is how to use context menus in Android.
.
Android offers a third type of menus: Alternative menus which allow multiple applications to use each other. An application menu can contain menu items that point to other applications that deal with a certain data type that is passed from the application by an intent.
This functionality is related to the concept of Content Providers which is in brief the ability of an application to expose its data (stored in a database or a file) by defining a MIME type to it and through a content URI to be accessed by any other application through this URI.
For example if an application name Employees has some data of employees stored within this application context, and another application wants to access this data; then Employeesapplication should declare a content provider to expose its data, with a MIME type:
vnd.android.cursor.item/mina.android.Employees so any other application can access the employees data by calling the Uri content://employees/All to access all employees or the Uricontent://employees/1 to access a single employee instance (for example).
Back to our Alternative menus issue, suppose this Scenario: We have two applications:
- Application A: deals with the employees data.
- Application B: receives the content Uri of the employees and do some calculations on it.
Now we want to add an alternative menu item in an activity in Application A so that when clicked passes a URI of employees and launches an activity in Application B (that manipulates the employees data according to the URI received).
So to add the alternative menu item in the activity of Application A, we write the following inonCreateOptionsMenu method:
public boolean onCreateOptionsMenu(Menu menu) {
//adds a regular menu item
menu.add(“Regular item”);
//create the intent with the Uri of the employees content provider
Intent targetIntent=
new Intent(Intent.ACTION_VIEW, Uri.parse(“content://employees/All”));
targetIntent.addCategory(Intent.CATEGORY_ALTERNATIVE);
menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, //item group
Menu.CATEGORY_ALTERNATIVE, //item id
Menu.CATEGORY_ALTERNATIVE, //item order
this.getComponentName(), //our activity class name
null, //no specific menu items required
targetIntent, // the intent to handle
0, //no flags
null); //Optional array in which to place the menu
//items that were generated for each of
//the specifics that were requested
return true;
}
Here’s what we did:
- Create an intent with the desired action (Intent.ACTION_VIEW) on the specified content provider Uri (content://employees/All).
- Call addIntentOptions method with the specified parameters.
The above code adds a menu item that launches all possible activities in all applications on the device that can deal with the action Intent.ACTION_VIEW on the Uri content://employees/All.
Now in Application B, if we want it to handle such an intent we have to do the following.
Define an activity in the application and specify in the AndroidManifest.xml file that this activity can handle the requests of the employees content provider like this:
<activity android:name=”.ContentProvidersDemo” android:label=”@string/app_name”>
<intent-filter android:label=”Access Employees”>
<action android:name=”android.intent.action.VIEW” />
<category android:name=”android.intent.category.DEFAULT” />
<category android:name=”android.intent.category.ALTERNATIVE” />
<data android:mimeType=”vnd.android.cursor.item/mina.android.Employees” />
</intent-filter>
</activity>
The above IntentFilter means that this activity will respond t0 any implicit intent from any application with the following parameters:
- Action: Intent.ACTION_VIEW.
- Category: android.intent.category.ALTERNATIVE.
- Data of MIME type:vnd.android.cursor.item/mina.android.Employees.
So in Application A when you press on the menu button, you’ll see a menu like this:
When you press on the Access Employees menu item, the activity in Application B will be launched.
.
Gallery Control
· Handling Gallery Events
· Displaying Images
Gallery Control
In Android the Gallery control is a selection control that displays items in a horizontal gallery. the items in the gallery appear beside each other. they can appear separated by a pre-defined space.
we can use the gallery to display String items using a simple ArrayAdapter.
so let’s see how to create a gallery that displays the word “Hello” in several languages:
the layout:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Gallery Demo" /> <Gallery android:id="@+id/gallery" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:spacing="100px" /> </LinearLayout>
and in the OnCreate method
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
gallery=(Gallery)findViewById(R.id.gallery);
//String array holding the values
String [] text=new String[]{"Hello","Hi","Alloha",
"Bonjour","Hallo","¡Hola"};
//Array adapter to display our values in the gallery control
ArrayAdapter arr=new ArrayAdapter(this,
android.R.layout.simple_gallery_item, text);
gallery.setAdapter(arr);
}
the gallery will be like this:
we can increse the spacing between the items by increasing the value of android:spacingproperty.
we can display a scroll bar to indicate the position of the current selected item in the gallery like this:
<Gallery android:id="@+id/gallery" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:spacing="100px" android:scrollbars="horizontal" android:scrollbarFadeDuration="0" android:scrollX="100px" />
setting the android:scrollbarFadeDuration=”0″ makes the scroll bar always visible.
The android:scrollX property defines the initial scroll offset of the scroll bar which is the initial distance that the gallery is scrolled for.
Handling Gallery Events:
since the gallery is a selction Control (a adapter view) so it can register aOnItemSelectedListener to handle the selection of items within the gallery.
final String [] text=new String[]{"Hello","Hi",
"Alloha","Bonjour","Hallo","¡Hola"};
gallery.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView parent, View view,
int position, long id) {
// TODO Auto-generated method stub
TextView txt=(TextView)findViewById(R.id.txt);
txt.setText(text[position].toString());
}
@Override
public void onNothingSelected(AdapterView parent) {
// TODO Auto-generated method stub
}
});
now the final step is to add two navigation buttons: Next and Previous to navigate throught the items in the gallery.
the layout is gonna be like this:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Gallery Demo" android:id="@+id/txt" /> <Gallery android:id="@+id/gallery" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:spacing="100px" android:scrollbars="horizontal" android:scrollbarFadeDuration="0" android:scrollX="100px" /> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_marginTop="5px" > <Button android:text="Previous" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btnPrev" android:onClick="onClick" /> <Button android:text="Next" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btnNext" android:onClick="onClick" /> </LinearLayout> </LinearLayout>
now in order to keep track of the index of the currently selected item we need to define two variables
//Variable to store the number of items in the gallery int ItemsInGallery=0; int CurrentIndex=0;
and the navigation buttons click handlers:
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
switch(v.getId())
{
case R.id.btnNext:
//Increase the index
CurrentIndex++;
//if reached the end of the gallery, then start from the first item
if(CurrentIndex>ItemsInGallery-1)
CurrentIndex=0;
gallery.setSelection(CurrentIndex,true);
txt.setText(String.valueOf(CurrentIndex));
break;
case R.id.btnPrev:
//Decrease the index
CurrentIndex=CurrentIndex-1;
//If reached the first item, then return to the last item in the gallery
if(CurrentIndex<0)
CurrentIndex=ItemsInGallery-1;
gallery.setSelection(CurrentIndex,true);
txt.setText(String.valueOf(CurrentIndex));
break;
}
}
Displaying Images:
The Gallery control is populated by an adapter, so we will create a custom adapter that contains ImageViews to display the images.
our custom adapter must inherit from BaseAdapter class
package mina.android.GalleryDemo;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
public class ImageAdapter extends BaseAdapter {
Context Con;
//array to hold the values of image resources
int [] Resources;
List views;
public ImageAdapter(Context con,int[] resources)
{
Con=con;
Resources=resources;
views=new ArrayList(resources.length);
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return Resources.length;
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return views.get(position);
//return position;
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
//return views.get(position).getId();
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ImageView img=new ImageView(Con);
img.setImageResource(Resources[position]);
views.add(img);
return img;
}
}
then in our activity onCreate Method:
int []res=new int[]{R.drawable.wc,
R.drawable.wc2,R.drawable.wc3,R.drawable.wc4,R.drawable.wc5};
ItemsInGallery=res.length;
ImageAdapter imgAdapter=new ImageAdapter(this, res);
gallery.setAdapter(imgAdapter);
and it will look like this:
Using Toasts and Alerts
· Displaying simple text
· Toasts
· Displaying complex views
· Alerts
· Creating Alerts
· Displaying Custom Views
- Displaying an alert of items
- · Displaying alerts with items with choices
Using Toasts and Alerts
Android offers two methods to display messages to the user: Toasts andAlerts. in this post we’re going to explore both of them.
Toasts:
toasts are pop up messages that lasts for a certain duration and then disappear. a Toast is a transient message that appears and disappears without any interaction from the user and with no notification to the program that it disappeared.
the Toast can display a simple text message or a complex view.
Displaying simple text:
to display a simple toast that displays a text message we use the following code:
Toast toast=Toast.makeText(this, "Hello toast", 2000); toast.setGravity(Gravity.TOP, -30, 50); toast.show();
we create the toast using the static Toast.makeText(Context con,String message, int duration)method to create a toast in the current context to display a text message for a duration specified in milli seconds or we can use the constant values Toast.LENGTH_SHORT to display for a short duration or Toast.LENGTH_LONG for longer duration.
The toast by default appears in the center of the screen, you can change the default position by specifying the Gravity and the X and Y offsets.
finally we call the Show() method to display the toast.
the previous toast will be like this:
Displaying complex views:
Toasts can also display complex views. this is done like this:
First: create a layout xml file in res/layout directory. the file must be named toast_layout.xml.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/toastView" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Hello toast" android:textColor="#000" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/txtDate" android:textColor="#000" /> </LinearLayout>
then from the code
Toast toast=new Toast(this);
LayoutInflater inflater=this.getLayoutInflater();
View toastView=inflater.inflate(R.layout.toast_layout,
(ViewGroup)findViewById(R.id.toastView));
TextView txtDate=(TextView)toastView.findViewById(R.id.txtDate);
txtDate.setText("toast appeared at "
+Calendar.getInstance().getTime().toLocaleString());
toast.setGravity(Gravity.CENTER, 0, 0);
toast.setView(toastView);
toast.show();
the toast will be like this:
Notes:
- In the toast_layout.xml width, if you put any buttons or any control that has a callback, it would appear disabled and the user cannot interact with it.
- The toast can be created in two ways: by calling Toast.makeText method or by specifyinga view via setView method. when you want to display a simple text use the first one otherwise use the second. if you try to interchange or combine between the two methods an exception will be thrown.
Alerts:
another way to show interactive messages is to use Alerts. alerts act as MessageBox orJOptionPane in J2SE. they have buttons that can be used to take decisions.
Creating Alerts:
let’s check this example to create an alert and show it:
//declared as final to be able to reference it in inner class
//declartations of the handlers
final AlertDialog.Builder builder=new AlertDialog.Builder(this);
builder.setTitle("Alert Dialog");
builder.setMessage("This is the alert's body");
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setPositiveButton("OK", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
TextView txt=(TextView)findViewById(R.id.txt);
txt.setText("You clicked Ok");
}
});
builder.setNegativeButton("Cancel", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// TODO Auto-generated method stub
TextView txt=(TextView)findViewById(R.id.txt);
txt.setText("You clicked Cancel");
}
});
builder.setNeutralButton("Do something", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// TODO Auto-generated method stub
TextView txt=(TextView)findViewById(R.id.txt);
txt.setText("Neutral Button Clicked");
AlertDialog ad=builder.create();
ad.cancel();
}
});
builder.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
// TODO Auto-generated method stub
TextView txt=(TextView)findViewById(R.id.txt);
txt.setText(txt.getText()+" the cancel listner invoked");
}
});
builder.show();
the previous code displays the following Alert:
the code may look bulky but it’s very simple.
first we create an instance of AlertDialog.Builder. we use the builder object to construct theAlertDialog object.
we specify the title and the icon of the alert. then the text to display in the body of the message.
the AlertDialog can display up to three buttons:
positive button: represents the OK button.
Negative button: represents the cancel button.
Neutral button: represents a button to perform another functionality other than ok or cancel.
note that there are no restrictions on the use of the three buttons, they can perform the same functionality the difference is just in logical meaning. but the three buttons cause the Alert dialog to dismiss.
we then specify the text and the click handler of each button.
in the neatral button click handler we added the lines AlertDialog ad=builder.create(); andad.cancel();. the first line gets a reference to the current dialog created by the builder to provide additional functionality such as invoking cancel() method.
the cancel method raises the onCancel callback method.
we could have replaced the previous code with the following:
AlertDialog ad=builder.create(); ad.setMessage(message); ad.setIcon(icon); ad.setMessage(message); ad.setButton(text, listener); . . .
Displaying Custom Views:
alerts can display complex views rather than simple text messages. create an xml layout file called alertview.xml like this:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/toastView" android:background="#DAAA" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Hello alerts" android:textColor="#000" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/txtDate" android:textColor="#000" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/btnAlert" android:text="Click" /> </LinearLayout>
to display this view as the alert view we do it like this:
View bodyView=getLayoutInflater().inflate(R.layout.alertview,
(ViewGroup)findViewById(R.id.toastView));
TextView txtDate=(TextView)bodyView.findViewById(R.id.txtDate);
txtDate.setText(Calendar.getInstance().getTime().toLocaleString());
Button btnAlert=(Button)bodyView.findViewById(R.id.btnAlert);
btnAlert.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
TextView txtDate=(TextView)bodyView.findViewById(R.id.txtDate);
txtDate.setText(Calendar.getInstance().getTime().toLocaleString());
TextView txt=(TextView)findViewById(R.id.txt);
txt.setText(Calendar.getInstance().getTime().toLocaleString());
}
});
builder.setView(bodyView);
.
.
.
what is interesting in this approach is that the alert is fully interactive, by clicking the button you can change the value of any view in the alert or in the activity.
you can also set the title of the alert to be a custom view via builder.setCustomTitle(View v)method in the same way described above.
Displaying an alert of items:
alerts can display a list of items from which the user selects from like this:
final String [] items=new String []{"Item 1","Item 2","Item 3","Item 4"};
AlertDialog.Builder builder=new AlertDialog.Builder(this);
builder.setTitle("Items alert");
builder.setItems(items, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// TODO Auto-generated method stub
TextView txt=(TextView)findViewById(R.id.txt);
txt.setText(items[which]);
}
});
builder.show();
this will display an alert like this:
notice that we do not have to specify any buttons because when the user clicks on any item the alert will be dismissed.
if the list items are in an Adapter we can achieve the same result usingbuilder.setAdapter(Adapter ad,OnClickListener listner) method:
final String [] items=new String []{"Item 1","Item 2","Item 3","Item 4"};
ArrayAdapter arr=new ArrayAdapter(this,
android.R.layout.select_dialog_item,items);
AlertDialog.Builder builder=new AlertDialog.Builder(this);
builder.setTitle("Adapter alert");
builder.setAdapter(arr, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// TODO Auto-generated method stub
TextView txt=(TextView)findViewById(R.id.txt);
txt.setText(items[which]);
}
});
or if the items are returned from a database in a cursor we can use
builder.setCursor(Cursor cursor, OnClickListener listner, String labelColumn)
Displaying alerts with items with choices:
we can add items to the alert with choices whether they are single choice (Radio buttons) or multiple choices (Check boxes).
to display single choice items:
final String [] items=new String []{"Item 1","Item 2","Item 3","Item 4"};
AlertDialog.Builder builder=new AlertDialog.Builder(this);
builder.setTitle("List alert");
builder.setSingleChoiceItems(items, 0, new OnClickListener(){
@Override
public void onClick(DialogInterface dialog, int which){
// TODO Auto-generated method stub
TextView txt=(TextView)findViewById(R.id.txt);
txt.setText(items[which]);
}
});
builder.setPositiveButton("OK", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// TODO Auto-generated method stub
}
});
builder.show();
the second parameter of setSingleChoiceItems is an integer specifying the index of the selected item
notice that we added a postive button that when clicked dismisses the alert cause unlike the regular items list the alert won’t be dismissed when an item is selected.
the builder.setSingleChoiceItems method has other overloads that can accept an Adapter or aCursor as parameters that hold the items to be displayed
to display multiple choice items:
final String [] items=new String []{"Item 1","Item 2","Item 3","Item 4"};
AlertDialog.Builder builder=new AlertDialog.Builder(this);
builder.setTitle("List alert");
builder.setMultiChoiceItems(items, null, new OnMultiChoiceClickListener() {
@Override
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
// TODO Auto-generated method stub
TextView txt=(TextView)findViewById(R.id.txt);
txt.setText(txt.getText()+" "+items[which]);
}
});
builder.setPositiveButton("OK", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// TODO Auto-generated method stub
}
});
builder.show();
the second parameter of setMultiChoiceItems is an array of boolean values specifying which items are set selected. if you want no items to be selected then set it to null, otherwise specify an array with the same length of the items with boolean values indicating which item is selected and which is not like this:
new boolean[] {true,false,...}
works in the fashion as single items choice except that the selection here is multiple.
View Flipper and Sliding drawer
· View Flipper
- Sliding Drawer
- Responding to Sliding Drawer Events
View Flipper and Sliding drawer
View Flipper:
Suppose you want to display a news bar in your activity. this news bar displays a single news item at a time then flips and shows next item and so on, then your choice would be Android’sViewFlipper.
ViewFlipper inherits from frame layout, so it displays a single view at a time.
consider this layout:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Flip" android:id="@+id/btn" android:onClick="ClickHandler" /> <ViewFlipper android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/flip" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Item1" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Item2" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Item3" /> </ViewFlipper> </LinearLayout>
Just a ViewFlipper container that contains three text views:
Now we want to flip the views when the button is clicked
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
btn=(Button)findViewById(R.id.btn);
flip=(ViewFlipper)findViewById(R.id.flip);
}
public void ClickHandler(View v)
{
flip.showNext();
}
If we want to flip in reverese direction we could use flip.showPrevious() instead we can add animations to the child views when they appear or disappear:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
btn=(Button)findViewById(R.id.btn);
flip=(ViewFlipper)findViewById(R.id.flip);
//when a view is displayed
flip.setInAnimation(this,android.R.anim.fade_in);
//when a view disappears
flip.setOutAnimation(this, android.R.anim.fade_out);
}
We can also set the ViewFlipper to flip views automatically when the button is clicked:
public void ClickHandler(View v)
{
//specify flipping interval
flip.setFlipInterval(1000);
flip.startFlipping();
}
We can stop the flipping by calling flip.stopFlipping(); method.
Or we can set the flipper to flip autommatically when the activity starts
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
btn=(Button)findViewById(R.id.btn);
flip=(ViewFlipper)findViewById(R.id.flip);
flip.setInAnimation(this,android.R.anim.fade_in);
flip.setOutAnimation(this, android.R.anim.fade_out);
flip.setFlipInterval(1000);
flip.setAutoStart(true);
}
Sliding Drawer:
Remeber Android’s old versions’ (prior to 2.2) launcher screen where we had a sliding pane at the bottom that when dragged upwards displays the applications menu of the phone, that is called the SlidingDrawer control.
Consider this layout:
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content"> <SlidingDrawer android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/drawer" android:handle="@+id/handle" android:content="@+id/content" > <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/handle" android:src="@drawable/tray_handle_normal" /> <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" android:id="@+id/content" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="This is some text" android:id="@+id/txt" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Click Me" android:id="@+id/btn" android:onClick="ClickHandler" /> </LinearLayout> </SlidingDrawer> </FrameLayout>
When the SlidingDrawer is pressed it looks like this:
The SlidingDrawer is a container that when dragged or pressed shows/hides its contents.
As the SlidingDrawer displays one content at a time, it must be declared within FrameLayout
the SlidingDrawer has two key properties:
android:handle: specifies the id of the control that acts as the handle.
android:content: specifies the id of the view that acts as content of the SlidingDrawer, most times will be a container.
you can open/close the drawer from the code like this:
if(drawer.isOpened())
{
drawer.close();
btnToggle.setText("Open");
}
else
{
drawer.open();
btnToggle.setText("Close");
}
You can open/close the drawer with animation using these methods instead
drawer.animateClose(); drawer.animateOpen();
or you can handle the open/close operations automatically using toggle method:
drawer.toggle(); drawer.animateToggle();
you can lock/unlock the SlidingDrawer to enable/disable dragging or clicking of the drawer using these methods:
drawer.lock(); drawer.unlock();
Responding to SlidingDrawer Events:
SlidingDrawer has three key callbacks:
- When the drawer is open, handled by implementing OnDrawerOpenListener.
- When the drawer is close, handled by implementing OnDrawerCloseListener.
- When the drawer is close, handled by implementing OnDrawerScrollListener.
drawer.setOnDrawerOpenListener(new OnDrawerOpenListener() {
@Override
public void onDrawerOpened() {
// TODO Auto-generated method stub
TextView txtStatus=(TextView)findViewById(R.id.txtStatus);
txtStatus.setText("Opened");
ImageView view=(ImageView)drawer.getHandle();
view.setImageResource(R.drawable.tray_handle_selected);
}
});
drawer.setOnDrawerCloseListener(new OnDrawerCloseListener() {
@Override
public void onDrawerClosed() {
// TODO Auto-generated method stub
TextView txtStatus=(TextView)findViewById(R.id.txtStatus);
txtStatus.setText("Closed");
ImageView view=(ImageView)drawer.getHandle();
view.setImageResource(R.drawable.tray_handle_normal);
}
});
drawer.setOnDrawerScrollListener(new OnDrawerScrollListener() {
@Override
public void onScrollStarted() {
// TODO Auto-generated method stub
TextView txtStatus=(TextView)findViewById(R.id.txtStatus);
txtStatus.setText("Scroll start");
}
@Override
public void onScrollEnded() {
// TODO Auto-generated method stub
TextView txtStatus=(TextView)findViewById(R.id.txtStatus);
txtStatus.setText("Scroll end");
}
});
You can make the drawer appear horizontally from right to left by setting the android:orientation property to horizontal in the xml file.
Using SQLite database
- Creating SQLite Database
- · Creating the database
- Upgrading the database
- Managing Foreign-Key Constraints
- Executing SQL statements
- Updating values
- Deleteing rows
- Executing queries
- Managing Cursors
Using SQLite database
Android default Database engine is Lite. SQLite is a lightweight transactional database engine that occupies small amout of disk storage and memory, so its a perfect choice for creating databases on many mobile operating systems such as Android, iOS.
Things to consider when dealing with SQLite:
- Data type integrity is not maintained in SQLite, you can put a value of a certain data type in a column of another dataype (put string in an integer and vice versa).
- Referential integrity is not maintained in SQLite, there is no FOREIGN KEY constraints or JOIN statements.
- SQLite Full Unicode support is optional and not installed by default.
.
The DB has:
Tables:
- Employees
- Dept.
Views:
- ViewEmps: to display employees and their relative departments.
Creating SQLite Database
Default SQLite on Android does not have a management interface or an application to create and manage data bases from, so we’re going to create the database ourselves by code.
First we will create a class that handles all the operations required to deal with the database such as creating the database, creating tables, inserting and deleting records and so on.
The first step is to create a class that inherits from SQLiteOpenHelper class. this class provides two methods to override to deal with the database:
- onCreate(SQLiteDatabase db): invoked when the database is created, this is where we can create tables and columns to them, create views or triggers.
- onUpgrade(SQLiteDatabse db, int oldVersion, int newVersion): invoked when we make a modification to the database such as altering, dropping , creating new tables.
Our class will have the following members
public class DatabaseHelper extends SQLiteOpenHelper {
static final String dbName="demoDB";
static final String employeeTable="Employees";
static final String colID="EmployeeID";
static final String colName="EmployeeName";
static final String colAge="Age";
static final String colDept="Dept";
static final String deptTable="Dept";
static final String colDeptID="DeptID";
static final String colDeptName="DeptName";
static final String viewEmps="ViewEmps";
The Constructor:
public DatabaseHelper(Context context) {
super(context, dbName, null,33);
}
The constructor of the super class has the following parameters:
Context con: the context attached to the database.
dataBaseName: the name of the database.
CursorFactory: some times we may use a class that extends the Cursor class to implement some extra validations or operations on the queries run on the database. In this case we pass an instance of CusrsorFactory to return a reference to our derived class to be used instead of the default cursor,
In this example we are going to use the standard Cursor Interface to retrieve results, so the CursorFactory parameter is going to be null.
Version: the version of the schema of the database.
The constructor creates a new blank database with the specified name and version.
Creating the database:
The first superclass method to override is onCreate(SQLiteDatabase db):
public void onCreate(SQLiteDatabase db) {
// TODO Auto-generated method stub
db.execSQL("CREATE TABLE "+deptTable+" ("+colDeptID+ " INTEGER PRIMARY KEY , "+
colDeptName+ " TEXT)");
db.execSQL("CREATE TABLE "+employeeTable+" ("+colID+" INTEGER PRIMARY KEY AUTOINCREMENT, "+
colName+" TEXT, "+colAge+" Integer, "+colDept+" INTEGER NOT NULL ,FOREIGN KEY ("+colDept+")
REFERENCES "+deptTable+" ("+colDeptID+"));");
db.execSQL("CREATE TRIGGER fk_empdept_deptid " +
" BEFORE INSERT "+
" ON "+employeeTable+
" FOR EACH ROW BEGIN"+
" SELECT CASE WHEN ((SELECT "+colDeptID+" FROM "+deptTable+" WHERE "+colDeptID+"=new."+colDept+" ) IS NULL)"+
" THEN RAISE (ABORT,'Foreign Key Violation') END;"+
" END;");
db.execSQL("CREATE VIEW "+viewEmps+
" AS SELECT "+employeeTable+"."+colID+" AS _id,"+
" "+employeeTable+"."+colName+","+
" "+employeeTable+"."+colAge+","+
" "+deptTable+"."+colDeptName+""+
" FROM "+employeeTable+" JOIN "+deptTable+
" ON "+employeeTable+"."+colDept+" ="+deptTable+"."+colDeptID
);
//Inserts pre-defined departments
InsertDepts(db);
}
The method creates tables with columns,a view and a trigger.
The method is invoked when the database is created. So we create our table and specify the columns.
This method is invoked when the database does not exist on the disk, it’s executed only once on the same device at the first time the application is run on the device.
Upgrading the database:
some times we want to upgrade the database by changing the schema, add new tables or change column data types.
this is done by overriding onUpdate(SQLiteDatabase db,int old Version,int newVerison) method:
Public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub
db.execSQL("DROP TABLE IF EXISTS "+employeeTable);
db.execSQL("DROP TABLE IF EXISTS "+deptTable);
db.execSQL("DROP TRIGGER IF EXISTS dept_id_trigger");
db.execSQL("DROP TRIGGER IF EXISTS dept_id_trigger22");
db.execSQL("DROP TRIGGER IF EXISTS fk_empdept_deptid");
db.execSQL("DROP VIEW IF EXISTS "+viewEmps);
onCreate(db);
}
This method is invoked when the version number specified in the constructor of the class changes.
When you want to append a change to your database you must change the version number in the constructor of the class:
So when you pass the constructor a version number of 2:
public DatabaseHelper(Context context) {
super(context, dbName, null,2);
// TODO Auto-generated constructor stub
}
Instead of 1:
super(context, dbName, null,2);
The application understands that you want to upgrade the database and onUpgrade method will be invoked
A typical implementation of this method is to drop the tables and create them again with the additional modifications.
Managing Foreign-Key Constraints:
We will create a trigger that ensures that when a new Employee is inserted his/her Dept value is present in the original Dept table.
The sql statement to create such a trigger would be like this:
CREATE TRIGGER fk_empdept_deptid Before INSERT ON Employees FOR EACH ROW BEGIN SELECT CASE WHEN ((SELECT DeptID FROM Dept WHERE DeptID =new.Dept ) IS NULL) THEN RAISE (ABORT,'Foreign Key Violation') END; END
In onCreate method we created this trigger like this:
db.execSQL("CREATE TRIGGER fk_empdept_deptid " +
" BEFORE INSERT "+
" ON "+employeeTable+
" FOR EACH ROW BEGIN"+
" SELECT CASE WHEN ((SELECT "+colDeptID+" FROM "+deptTable+" WHERE "+colDeptID+"=new."+colDept+" ) IS NULL)"+
" THEN RAISE (ABORT,'Foreign Key Violation') END;"+
" END;");
Executing SQL statements:
Now let’s begin executing basic sql statements. you can execute any sql statement that is not a query whether it is insert, delete, update or anything using db.execSQL(String statement)method like when we did when creating the database tables:
db.execSQL("CREATE TABLE "+deptTable+" ("+colDeptID+ " INTEGER PRIMARY KEY , "+
colDeptName+ " TEXT)");
Inserting records:
We insert records to the databse using the following code for example to insert records in theDept table:
SQLiteDatabase db=this.getWritableDatabase(); ContentValues cv=new ContentValues(); cv.put(colDeptID, 1); cv.put(colDeptName, "Sales"); db.insert(deptTable, colDeptID, cv); cv.put(colDeptID, 2); cv.put(colDeptName, "IT"); db.insert(deptTable, colDeptID, cv); db.close();
Notice that we need to call this.getWritableDatabase() to open the connection with the database for reading/writing.
the ContentValues.put has two parameters: Column Name and the value to be inserted.
Also it is a good practice to close the database after executing statements.
Updating values:
To execute an update statement we have two ways
- to execute db.execSQL
- to execute db.update method:
public int UpdateEmp(Employee emp)
{
SQLiteDatabase db=this.getWritableDatabase();
ContentValues cv=new ContentValues();
cv.put(colName, emp.getName());
cv.put(colAge, emp.getAge());
cv.put(colDept, emp.getDept());
return db.update(employeeTable, cv, colID+"=?", new String []{String.valueOf(emp.getID())});
}
The update method has the following parameters:
- String Table: the table to update a value in
- ContentValues cv: the content values object that has the new values
- String where clause: the WHERE clause to specify which record to update.
- String[] args: the arguments of the WHERE clause.
Deleteing rows:
As in update to execute a delete statement we have two ways
- to execute db.execSQL
- to execute db.delete method:
public void DeleteEmp(Employee emp)
{
SQLiteDatabase db=this.getWritableDatabase();
db.delete(employeeTable,colID+"=?", new String [] {String.valueOf(emp.getID())});
db.close();
}
The delete method has the same parameters as the update method.
Executing queries:
To execute queries there are two methods:
- Execute db.rawQuery method.
- Execute db.query method.
To execute a raw query to retreive all departments
Cursor getAllDepts()
{
SQLiteDatabase db=this.getReadableDatabase();
Cursor cur=db.rawQuery("SELECT "+colDeptID+" as _id, "+colDeptName+" from "+deptTable,new String [] {});
return cur;
}
The rawQuery method has two parameters:
- String query: the select statement.
- String[] selection args: the arguments if a WHERE clause is included in the select statement.
Notes:
- The result of a query is returned in Cursor object.
- In a select statement if the primary key column (the id column) of the table has a name other than _id then you have to use an alias in the form SELECT [Column Name] as _id
cause the Cursor object always expects that the primary key column has the name _id or it will throw an exception .
Another way to perform a query is to use a db.query method.
A query to select all employees in a certain department from a view would be like this:
public Cursor getEmpByDept(String Dept)
{
SQLiteDatabase db=this.getReadableDatabase();
String [] columns=new String[]{"_id",colName,colAge,colDeptName};
Cursor c=db.query(viewEmps, columns, colDeptName+"=?", new String[]{Dept}, null, null, null);
return c;
}
The db.query has the folowing parameters:
- String Table Name: the name of the table to run the query against.
- String [ ] columns: the projection of the query i.e the columns to retrieve.
- String WHERE clause: where clause, if none pass null.
- String [ ] selection args: the parameters of the WHERE clause.
- String Group by: a string specifying group by clause.
- String Having: a string specifying HAVING clause.
- String Order By by: a string Order By by clause.
Managing Cursors:
Result sets of queries are returned in Cursor objects.
There are some common methdos that you will use with cursors:
- boolean moveToNext(): moves the cursor by one record in the result set, returns false if moved past the last row in the result set.
- boolean moveToFirst(): moves the cursor to the first row in the result set, returns false if the result set is empty.
- boolean moveToPosition(int position): moves the cursor to a certain row index within the boolean result set, returns false if the position is un-reachable
- boolean moveToPrevious():moves the cursor to the preevious row in the result set, returns false if the cursor is past the first row.
- boolean moveToLast():moves the cursor to the lase row in the result set, returns false if the result set is empty.
There are also some useful methods to check the position of a cursor:
boolean isAfterLast(), isBeforeFirst, isFirst,isLast and isNull(columnIndex).
Also if you have a result set of only one row and you need to retreive values of certain columns, you can do it like this:
public int GetDeptID(String Dept)
{
SQLiteDatabase db=this.getReadableDatabase();
Cursor c=db.query(deptTable, new String[]{colDeptID+" as _id",colDeptName},colDeptName+"=?",
new String[]{Dept}, null, null, null);
//Cursor c=db.rawQuery("SELECT "+colDeptID+" as _id FROM "+deptTable+" WHERE "+colDeptName+"=?",
new String []{Dept});
c.moveToFirst();
return c.getInt(c.getColumnIndex("_id"));
}
We have Cursor.getColumnIndex(String ColumnName) to get the index of a column.
Then to get the value of a certain column we have Cursor.getInt(int ColumnIndex) method.
Also there are getShort,getString,getDouble, getBlob to return the value as a byte array.
It’s a good practice to close() the cursor after using it.
- Android default content providers
- content providers basics
- Making Queries
- Inserting, updating and deleting
- Updating info using Content Providers
- Deleting info using Content Providers
In the created a sqlite database We saw that the database file is stored in the file system directories of that application meaning that the database cannot be accessed from another application. You can apply the same thing on any resources within your application (Files, images, videos, …).
But there is a method to expose your data across multiple applications: Content Providers.
Content Providers expose an application’s data across all other applications, you can use content providers to store or retrieve data of one application from any other application.
Android default content providers:
There are many built-in content providers supplied by OS. They are defined in the android.providerpackage, they include:
Browser.
Calllog.
Live Folders.
Contacts Contract.
Media Store.
Settings.
content providers basics:
The concept of content providers is pretty similar to the concept of ASP.NET Web Services they provide data encapsulation through exposing data by URis. Any content provider is invoked by a URi in the form of content://provider_name . for example the URi of the Contacts content provider that retrieves all contacts is in the following form content://contacts/people. If you want to retrieve a particular contact (by its ID) then it would be in this form: content://contacts/people/5.
You do not need to write the URis of the content providers manually as they are stored as constant values in their respective content provider classes.
The Uri of the Contacts phones content provider is defined in:
ContactsContract.CommonDataKinds.Phone.CONTENT_URI
(content://com.android.contacts/data/phones)
The Uri of the browser Bookmarks content provider is defined in
Browser.B*OOKMARKS_URI
(content://browser/bookmarks)
The Media store (Video) stored in external device (SD Card) is defined in
MediaStore.Video.Media.EXTERNAL_CONTENT_URI
(content://media/external/video/media) and so on.
Content providers allow you to perform basic CRUD operations: Create,Read, Update and Delete on data.
Making Queries:
To retrieve data from a content provider we run a sql-like query using ManagedQuery object. The ManagedQuery object returns a cursor holding the result set.
To retrieve a list of all contacts and display them in a ListView
We first define our activity xml layout:
<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”vertical”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
>
<TextView
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:id=”@+id/txt”
/>
<ListView
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:id=”@+id/list”
/>
</LinearLayout>
And define the layout of each row in the ListView:
<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout
xmlns:android=”http://schemas.android.com/apk/res/android”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:orientation=”horizontal”
>
<TextView
android:layout_width=”100px”
android:layout_height=”wrap_content”
android:id=”@+id/txtName”
android:layout_weight=”1″
/>
<TextView
android:layout_width=”100px”
android:layout_height=”wrap_content”
android:id=”@+id/txtNumber”
android:layout_weight=”1″
/>
</LinearLayout>
Remember to add the following permission to the manifest.xml file
<uses-permission android:name=”android.permission.READ_CONTACTS”></uses-permission>
To retrieve the contacts and bind them to the listView:
String [] projection=new String[]{ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME
,ContactsContract.CommonDataKinds.Phone.NUMBER,ContactsContract.CommonDataKinds.Phone._ID};
txt.setText(ContactsContract.PhoneLookup.CONTENT_FILTER_URI.toString());
Uri contacts = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
Cursor managedCursor = managedQuery(contacts,projection,null,null,null);
//Cursor managedCursor =cr.query(contacts, projection, null, null, null);
ListAdapter sca=new SimpleCursorAdapter(this,R.layout.listrow,managedCursor,projection,to);
list.setAdapter(sca);
The above code retrieves all the contacts in the following steps:
- We first define the projection of our query, we define the columns we want to retrieve in the result set.We define a String array containing the names of the columns we want to retreieve.The contacts column names are defined inContactsContract.CommonDataKinds.Phone class.Note that we need to retrieve the _ID column as the cursor that retrieves the data expects such a column to be there.
- We specify the Uri of the content providerContactsContract.CommonDataKinds.Phone.CONTENT_URI
- We retrieve the data by a cursor
Cursor managedCursor = managedQuery(contacts,projection,null,null,null);The cursor is retrieved by executing a managedQuery which has the following parameters: - The Uri of the content provider.
- A String Array of the columns to be retrieved (projection)
- Where clause.
- String array containing selection arguments values.
- String representing the order by clause, we can use it but here it will be null. If we want to sort the contacts by name it would beContactsContract.CommonDataKinds.Phone.DISPLAY_NAME +“ asc”
- Then we create a list adapeter using the cursor and bind the ListView to it.
The previous example retrieves all the contacts but what if we want to retrieve a certain contact by name:
There are two ways:
- Using the same code above but adding a where clause and selection arguments to the managed wuery so it becomes like this:
Cursor managedCursor = managedQuery(contacts,projection,ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME+”=?”
- ,new String[]{“Jack”},null);
This retrieves contact info of a person named Jack.
- The other method is to inject the query in the Uri, instead of using Uri
contacts = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
we use:
Uri contacts=Uri.withAppendedPath(ContactsContract.CommonDataKinds.Phone.CONTENT_FILTER_URI,
- Uri.encode(“Jack”));
This is equivalent to the following Uri content://com.android.contacts/data/phones/Jack.
Inserting,updating and deleting:
To insert data using a content provider there are two methods
First:
Using the Insert() method of your activity’s content resolver object. Like this example t insert a new bookmark to the browser:
ContentValues cv=new ContentValues();
cv.put(Browser.BookmarkColumns.TITLE, “End Gadget”);
cv.put(Browser.BookmarkColumns.URL, “http://www.engadget.com/”);
cv.put(Browser.BookmarkColumns.BOOKMARK,1);
Uri u= getContentResolver().insert(Browser.BOOKMARKS_URI, cv);
We create a ContentValues object and add to it all the required fields, then we callgetContentResolver().insert method which returns the Uri of the newly inserted item.
It would be in this example content://browser/bookmarks/17
We can use the generated Uri to update or delete the item later.
Second:
We can replace the getcontentresolver().insert() method with bulkInsert method if we want to insert multiple items at a time.
The bulkInsert(Uri url,ContentValues[] values) method returns the number of new items created.
Updating info using Content Providers:
To update data using content providers, we use getContnetResolver.Update() method:
This code updates the title of an existing browser bookmark:
ContentValues cv=new ContentValues();
cv.put(Browser.BookmarkColumns.TITLE, “End Gadget mod”);
//uriBook= getContentResolver().insert(Browser.BOOKMARKS_URI, cv);
getContentResolver().update(Browser.BOOKMARKS_URI, cv, BookmarkColumns.TITLE+”=?”, new String[]{“End Gadget”});
the Update method has the following parameters:
- Content Providers Uri
- ContentValues object having the new values
- Where clause
- String array of the where clause arguments values
Deleting info using Content Providers:
To delete info we use getContentResolver.Delete() method
To delete a bookmark:
getContentResolver().delete(Browser.BOOKMARKS_URI,BookmarkColumns.TITLE+”=?”, new String[]{“Mobile Orchard”});
The delete function has the following parameters:
- Content Providers Uri
- Where clause
- String array of the where clause arguments values
Remember to add the following permissions to the manifest.xml to add and read browser bookmarks:
<uses-permission android:name=”android.permission.WRITE_CONTACTS”></uses-permission>
<uses-permission android:name=”com.android.browser.permission.WRITE_HISTORY_BOOKMARKS”></uses-permission>
Building Android Content Providers
- Creating the content type
- Modifying the Manifest.xml file
- Testing the content provider
Building Android Content Providers
.
In this below we’re going to create a content provider to access data from our previous Employees simple application from the SQLite post.
To remind you the database has two tables Employees and Dept:
Remember that any content provider must provide the following:
- A URi from which we can run queries.
- MIME type corresponding to the content.
- Insert() method.
- Update() methd.
- Delete() method.
Creating the content type:
First we will create a new class, I will call it EmployeesContentProvider and choose its super class to be ContentProvider. the class initially will be like this:
package mina.android.DatabaseDemo;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
public class EmployeesContentProvider extends ContentProvider {
DatabaseHelper db;
public static final Uri CONTENT_URI=Uri.parse("content://employees");
@Override
public int delete(Uri arg0, String arg1, String[] arg2) {
// TODO Auto-generated method stub
return 0;
}
@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean onCreate() {
// TODO Auto-generated method stub
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// TODO Auto-generated method stub
return null;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO Auto-generated method stub
return 0;
}
}
We added a member of type DatabaseHelper db to hold a reference to our database.
Also we added a static URi object that represents the URi of our content provider.
It has all the abstract methods implementations of the ContentProvider class. so let’s check each method
onCreate() method:
The onCreate() method is the first method invoked when the content provider is created (similar to the Activity’s onCreate() method). here you can load your database or check for files you may read/write to them.
The method return a Boolean. it should be true if everything is ok, otherwise it should be false.
In our case we will just reference our SQLite database:
@Override
public boolean onCreate() {
// TODO Auto-generated method stub
db=new DatabaseHelper(this.getContext());
if(db==null)
return false;
else
return true;
}
query() method:
the query() method is the method that gets invoked when a content provider data is requested by a URi.
First let’s talk a little about content providers URI.
The content provider URi has the following format:
content://Authority/[(n) path]/[instance indentifier]
explanation:
- The URI starts with content:// scheme.
- The authority is a unique identifier for the content provider.
- The authority can be followed by one or more paths (optional) refer to data paths within the content.
- There can be an instance identifier that refers to a specific data instance.
For example we can have a URi like this:
content://Employees/Marketing//11.
this URi has Employees as the authority, Marketing as a data path and 11 as an instance (employee) identifier.
Back to our query method, we have the following parameters:
- Uri: the URi requested.
- String [] projection: representing the columns (projection) to be retrieved.
- String[] selection: the columns to be included in the WHERE clause.
- String[] selectionArgs: the values of the selection columns.
- String sortOrder: the ORDER BY statement.
the first step in our query method is to parse the client URi.
we expect the URi to be in one of the following forms:
- content://employees/: retrieves all employees.
- content://employees/id: retrieves a certain employee by ID.
- content://employess/IT: retreives employees of IT Dept.
- content://employess/HR: retrieves employees of HR Dept.
- content://employees/Sales: retreives employees of sales Dept.
So we will add some constatnt values to our class to refer to the above URis:
//authority and paths public static final String AUTHORITY="employees"; public static final String ITPATH="IT"; public static final String HRPATH="HR"; public static final String SALESPATH="Sales"; //URiMatcher to match client URis public static final int ALLEMPLOYEES=1; public static final int SINGLEEMPLOYEE=2; public static final int IT=3; public static final int HR=4; public static final int SALES=5;
Then we’re going to define a URiMatcher object that matches the client URI
static final UriMatcher matcher=new UriMatcher(UriMatcher.NO_MATCH);
static{
matcher.addURI(AUTHORITY,null,ALLEMPLOYEES);
matcher.addURI(AUTHORITY, ITPATH, IT);
matcher.addURI(AUTHORITY, HRPATH, HR);
matcher.addURI(AUTHORITY, SALESPATH, SALES);
//you can use '*' as a wild card for any text
matcher.addURI(AUTHORITY, "#", SINGLEEMPLOYEE);
}
The static initializer block loads the URimatcher objects with the values to match when the class initializes.
So let’s write our query method:
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder builder=new SQLiteQueryBuilder();
builder.setTables(DatabaseHelper.viewEmps);
String order=null;
Cursor result=null;
if(sortOrder!=null)
order=sortOrder;
int match=matcher.match(uri);
switch(match)
{
case ALLEMPLOYEES:
//content://employees//id
result=builder.query(db.getWritableDatabase(), projection, selection, selectionArgs, null, null, sortOrder);
break;
case SINGLEEMPLOYEE:
//content://employees//id
Listsegments=uri.getPathSegments();
String empID=segments.get(0);
result=db.getEmpByID(empID);
break;
case IT:
//content://employees//IT
result=db.getEmpByDept("IT");
result=builder.query(db.getReadableDatabase(), projection, db.colDeptName+"=?", new String[]{"IT"}, null, null, sortOrder);
break;
case HR:
//content://employees//HR
result=db.getEmpByDept("HR");
result=builder.query(db.getReadableDatabase(), projection, db.colDeptName+"=?", new String[]{"HR"}, null, null, sortOrder);
break;
case SALES:
//content://employees//Sales
result=db.getEmpByDept("Sales");
result=builder.query(db.getReadableDatabase(), projection, db.colDeptName+"=?", new String[]{"Sales"}, null, null, sortOrder);
break;
}
return result;
}
the function just parses the URi and returns the data in a cursor.
Insert() method:
the insert methods inserts a new record to the db;
the insert method has the following form:
public Uri insert(Uri uri, ContentValues values) {
return null;
}
The method has two parameters:
- URi uri: the URi of the content provider, we need to check it’s correct.
- ContentValues values: object holding the info of the new item to be inserted.
The method returns the URi of the newly inserted item to be used for further manipulations.
so here’s the implentation:
@Override
public Uri insert(Uri uri, ContentValues values) {
int match=matcher.match(uri);
//not the Uri we're expecting
long newID=0;
if(match!=1)
throw new IllegalArgumentException("Wrong URi "+uri.toString());
if(values!=null)
{
newID=db.getWritableDatabase().insert(DatabaseHelper.employeeTable, DatabaseHelper.colName, values);
return Uri.withAppendedPath(uri, String.valueOf(newID));
}
else
return null;
}
We first check the Uri if it is not correct, throw an exception.
then check the content values object, if null return null otherwise insert the new item and return the URi with the id of the new item.
The Update() method:
The update method updates existing record(s) and returns the number of updated rows.
A trick rises from the fact that you need to specify whether to update a collection of records or a single record, based on the URi.
The method has the following parameters:
- URi uri: the URi of the content provider, we need to check it’s correct.
- ContentValues values: object holding the info of the new item to be inserted.
- String Selection : the filter to match the rows to update
- String [] selectionArgs : the values of the filter parameters
Here’s the implementation of the update method:
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
int match=matcher.match(uri);
//not the Uri we're expecting
int rows=0;
//update single instance
if(match==2)
{
if(values!=null)
{
Listsegments=uri.getPathSegments();
String empID=segments.get(0);
rows=db.getWritableDatabase().update(DatabaseHelper.employeeTable, values,DatabaseHelper.colID+"=?", new String []{empID});
}
}
//update all emps in a certain dept
else if(match==3 ||match==4||match==5)
{
Listsegments=uri.getPathSegments();
String deptName=segments.get(0);
int DeptID=db.GetDeptID(deptName);
rows=db.getWritableDatabase().update(db.employeeTable, values,db.colDept+"=?", new String []{String.valueOf(DeptID)});
}
return rows;
}
The Delete() method:
The delete method has the following parameters:
- Uri uri: the URi of the content provider.
- String Condition: the condition of the delete statement.
- String[] args: the delete condition arguments
So here’s the implementation:
@Override
public int delete(Uri uri, String where, String[] args) {
int match=matcher.match(uri);
//expecting the URi to be in the form of
if(match==1)
{
SQLiteDatabase dataBase=db.getWritableDatabase();
return dataBase.delete(db.employeeTable, where, args);
}
else
return 0;
}
We just check for the URi and perform a delete command.
The getType() method:
The last mthod to implement is getType() method which returns the
MIME type associated with the URi passed to it.
If the URi is of a group of employees, then the MIME type is a collection type, otherwise it’s of an instance type
@Override
public String getType(Uri uri) {
int match=matcher.match(uri);
// single employee
if(match==2)
{
return "mina.android.Employee";
}
//collection of employees
else
{
return "mina.android.Employees";
}
}
Modifying the Manifest.xml file:
The last thing we need to do is to add an entry in our application’s manifest.xml file to register our class as a content provider class.
So add this entry just below the <application>
<provider android:name="mina.android.DatabaseDemo.EmployeesContentProvider" android:authorities="employees"/>
When an application requests data through our content provider, Android system will search all the manifest files of all aplications on the device and when it finds such an entry, it will process the request.
Testing the content provider:
Now suppose you are in another activity and you want to use our activity.
Testing queries:
To make a query to retrieve all employees:
Uri empsUri=Uri.parse("content://employees");
Cursor cursor=getContentResolver().query(empsUri, null, null, null, null);
Cursor cursor=getContentResolver().query(empsUri, null, null, null, null);
The cursor should have all the records.
To retrieve a certain employee by ID or all employees in a certain department:
Uri empUri=Uri.parse("content://employees//5");
Uri empDeptUri=Uri.parse("content://employees//Sales");
Inserting:
Uri empsUri=Uri.parse("content://employees");
ContentValues cvs=new ContentValues();
cvs.put("EmployeeName", "Mark Anderson");
cvs.put("Age", 35);
cvs.put("Dept", 1);
// URi of the new inserted item
Uri newEmp=getContentResolver().insert(empsUri, cvs);
Updating:
To update a single employee:
//Uri with the id of the employee
Uri empsUri=Uri.parse("content://employees/8");
Cursor cursor=getContentResolver().query(empsUri, null, null, null, null);
txt.setText(String.valueOf(cursor.getCount()));
ContentValues cvs=new ContentValues();
cvs.put("EmployeeName", "Mina Samy mod");
cvs.put("Age", 35);
cvs.put("Dept", 1);
// number of rows modified
int rowsNumber=getContentResolver().update(empsUri, cvs, "EmployeeID=?", new String[]{"8"});
To update all employees in a certain department
Uri empsUri=Uri.parse("content://employees/Sales");
Cursor cursor=getContentResolver().query(empsUri, null, null, null, null);
txt.setText(String.valueOf(cursor.getCount()));
ContentValues cvs=new ContentValues();
cvs.put("EmployeeName", "mod");
cvs.put("Age", 35);
cvs.put("Dept", 1);
int rowsNumber=getContentResolver().update(empsUri, cvs, "colDept=?", new String[]{"1"});
As a matter of fact, in both cases we don’t need to specify the wher clause and the where parameters as they are implicitly specified in the URi. so we just can replace the update statement to be like this:
int rowsNumber=getContentResolver().update(empsUri, cvs, null,null);
Deleting:
I left the delete operation open to any criteria, you can delete a single employee or employees of a certain department or even all employees
Uri empsUri=Uri.parse("content://employees");
// delete employee of id 8
int rowsNumber=getContentResolver().delete(empsUri,"EmployeeID=?",new String[]{"8"});
Final Word:
Creating a content provider for a certain type of data can be done in many ways, this example can be implemented in several variations.
Another thing is that you need to create a strongly typed class for you data model to be used by clients accessing your content. in this example when I tested the query i wrote the column names of the database as strings like this: “EmployeeID” and “EmployeeName”.
this is not ideal in a production release of an application. I should created a class library that holds all the info about the database to be used by other client applications.
Handlers&Async Tasks
Multi-Threading concept is essential in most platforms. it provides maximum utilization of the processor. threading is used when the program executes time consuming processes (such as calling a web service) and to give a good user experience by unblocking the UI.
Android provides threading techniques to perform time consuming tasks in a background thread with coordination with the UI thread to update the UI.
Android provides the following methods of threading:
- Handlers.
- Async. Tasks.
Handlers:
When you create an object from the Handler class, it processes
Messages and Runnable objects associated with the current thread MessageQueue. the message queue holds the tasks to be executed in FIFO (First In First Out) mannser. you will need only ine Handler per activity where the background thread will communicate with to update the UI.
The Handler is associated with the thread from which it’s been created
We can communicate with the Handler by two methods:
- Messages.
- Runnable objects.
In this post we will demonstrate how to use both using a simple example which is updating the text of a TextView using multiple threads.
Using Messages:
The steps of using a Handler are as follows:
- You create a Handler object with an asscociated callbackmethod to handle the received messages (it is the method where the UI updatewill be done).
- From the background thread you will need to send messages to thehandler.
Here’s the code of our activity:
public class MainActivity extends Activity {
TextView txt;
// our handler
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
//display each item in a single line
txt.setText(txt.getText()+"Item "+System.getProperty("line.separator"));
}
};
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
txt=(TextView)findViewById(R.id.txt);
}
@Override
protected void onStart() {
super.onStart();
// create a new thread
Thread background=new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<10;i++)
{
try {
Thread.sleep(1000); b.putString("My Key", "My Value:
"+String.valueOf(i));
// send message to the handler with the current message handler
handler.sendMessage(handler.obtainMessage());
} catch (Exception e) {
Log.v("Error", e.toString());
}
}
}
});
background.start();
}
}
After running the following code the TextView will display the following,
Each second a new line is written:
This example is pretty basic, it just sends the same message for a number of times.
What if we want the message sent to hold data that’s changed each time the message is sent, the answer is to use Message.setData(Bundle bundle) method by creating a Bundle object and adding the data to it like this:
public class MainActivity extends Activity {
TextView txt;
// our handler
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
// get the bundle and extract data by key
Bundle b = msg.getData();
String key = b.getString("My Key");
txt.setText(txt.getText() + "Item " + key
+System.getProperty("line.separator"));
}
};
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
txt = (TextView) findViewById(R.id.txt);
}
@Override
protected void onStart() {
super.onStart();
// create a new thread
Thread background = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
Message msg = new Message();
Bundle b = new Bundle();
b.putString("My Key", "My Value: " + String.valueOf(i));
msg.setData(b);
// send message to the handler with the current message handler
handler.sendMessage(msg);
} catch (Exception e) {
Log.v("Error", e.toString());
}
}
}
});
background.start();
}
}
We put a string to the bundle and send a message with that bundle. in the handler method we receive the bundle and get the value with the predefined key.
After executing that code the text view would look like this:
Using Runnables:
another way to use Handlers is to pass them a Runnable by using the
Handler.post() method like this:
Runnable r=new Runnable() {
@Override
public void run() {
txt.setText("Runnable");
}
};
handler.post(r);
This will add the Runanble object to the message queue to be executed by the handler.
Sending Messages in a timely manner:
we can use handlers to send messages or post runnables at time intervals using
The following methods:
- handler.sendEmptyMessageAtTime(int what,long uptimeMillis):sends an
empty message at a specific time in milli-seconds, can be defined by using the
SystemClock.uptimeMillis() method to get the time since the device boot
in milli-seconds and concatinating to it. - handler.sendEmptyMessageDelayed(int what,long delayMillis):sends an
empty message after a certain amount of time in milli-seconds. - handler.sendMessageAtTime(Message msg,long uptimeMillis).
- handler.sendMessageDelayed(Message msg,long delayMillis).
- handler.postAtTime(Runnable r,long uptimeMillis).
- handler.postAtTime(Runnable r,Object token,long uptimeMillis):posts a
runnable with an object as a distinguishing token. - handler.postDelayed(Runnable r,long delayMillis).
All the above messages return a boolean indicating whether the message or the runnable has been placed successfully in the message queue.
Removing Call backs:
If you want to remove a runnable or a message from the message queue, you can use the following methods:
- handler.removeCallbacks(Runnable r).
- handler.removeCallbacks(Runnable r,Object token).
- handler.removeCallbacksAndMessages(Object token).
- handler.removeMessages(int what).
- handler.removeMessages(int what,Object object)
In the previous post we saw one way to deal with threads in Android, which is by using Handlers. In this post we’ll see how to use another technique which is using AsyncTaskclass.
AsyncTask is an abstract class that provides several methods managing the interaction between the UI thread and the background thread. it’s implementation is by creating a sub class that extends AsyncTask and implementing the different protected methods it provides.
Let’s demonstrate how to user AsyncTask by creating a simple activity that has two buttons and a progress bar:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:id="@+id/btn" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Show Progress" ></Button> <ProgressBar android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/progress" style="?android:attr/progressBarStyleHorizontal" ></ProgressBar> <Button android:id="@+id/btnCancel" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Cancel" ></Button> </LinearLayout>
Here we have two buttons: one to start progress and the other to stop it.
Creating the AsyncTask sub class:
The first step in implementing AsyncTask is to create a sub class like this:
class ProgressTask extends AsyncTask<Params, Progress, Result>{
}
The AsyncTask declaration has three Varargs parameters which are:
- Params: parameter info passed to be used by the AsyncTask.
- Progress: the type of progress that the task accomplishes.
- The result returned after the AsyncTask finishes.
These parameters are of type Varargs which provide the flexibility to pass dynamic sized arrays as parameters.
In our example our class will be like this:
class ProgressTask extends AsyncTask<Integer, Integer, Void>{
}
The parameter and the progress are of type Integer and the result is Void as our tasks does not return anthing (returns null).
The second step is overriding the protected methods defined by the AsyncTask class that handle the execution life cycle of the AsyncTask.
We have five methods to implement which are:
- onPreExecute: the first method called in the AsyncTask, called on the UI thread.
- doInBackground: the method that executes the time consuming tasks and publish the task progress, executed in background thread.
- onProgressUpdate: method that updates the progress of the AsyncTask, run on the UI thread.
- onPostExecute: the final method that gets called after doInBackground finishes, here we can update the UI with the results of the AsyncTask.
- onCancelled: gets called if the AsyncTask.cancel() methods is called, terminating the execution of the AsyncTask.
Starting the AsyncTask:
To start the AsyncTask we create an instance of it, then call the execute() method passing the initial parameters like this:
ProgressTask task=new ProgressTask(); // start progress bar with initial progress 10 task.execute(10);
Implementing the AsyncTask:
class ProgressTask extends AsyncTask<Integer, Integer, Void>{
@Override
protected void onPreExecute() {
// initialize the progress bar
// set maximum progress to 100.
progress.setMax(100);
}
@Override
protected void onCancelled() {
// stop the progress
progress.setMax(0);
}
@Override
protected Void doInBackground(Integer... params) {
// get the initial starting value
int start=params[0];
// increment the progress
for(int i=start;i<=100;i+=5){
try {
boolean cancelled=isCancelled();
//if async task is not cancelled, update the progress
if(!cancelled){
publishProgress(i);
SystemClock.sleep(1000);
}
} catch (Exception e) {
Log.e("Error", e.toString());
}
}
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
// increment progress bar by progress value
progress.setProgress(values[0]);
}
@Override
protected void onPostExecute(Void result) {
// async task finished
Log.v("Progress", "Finished");
}
}
Here are the steps:
- onPreExecute() method first gets called initializing the maximum value of the progress bar.
- doInBackground(Integer… params) methods gets called by obtaining the initial start value of the progress bar then incrementing the value of the progress bar every second and publishing the progress as long as the async task is not cancelled.
- onProgressUpdate(Integer… values) method is called each time progress is published from doInBackground, thus incrementing the progress bar.
- onPostExecute(Void result) is called after doInBackground finished execution.
- void onCancelled() is called if task.cancel(true) is called from the UI thread. it may interrupt the execution preventing onPostExecute from being executed.
The onClick handler of our buttons is like this:
@Override
public void onClick(View v) {
ProgressTask task=new ProgressTask();
switch(v.getId()){
case R.id.btn:
task.execute(10);
break;
case R.id.btnCancel:
task.cancel(true);
break;
}
}
The Difference between Handler and AsyncTask:
After we saw both Handlers and AsyncTasks a question may evolve: what’s the difference between the two and when to use one of them over the other ?
The Handler is associated with the application’s main thread. it handles and schedules messages and runnables sent from background threads to the app main thread.
AsyncTask provides a simple method to handle background threads in order to update the UI without blocking it by time consuming operations.
The answer is that both can be used to update the UI from background threads, the difference would be in your execution scenario. You may consider using handler it you want to post delayed messages or send messages to the MessageQueue in a specific order.
You may consider using AsyncTask if you want to exchange parameters (thus updating UI) between the app main thread and background thread in an easy convinient way.
Using Preferences
· Saving Preferences
· Saving Activity-level preferences
· Reading preferences values
- Saving Application-level preferences
- Sharing preferences across applications
- Creating Preferences Activities
Using Preferences
We saw before that we can persist an application’s data using SQLite database. Android offers another way to store user’s data through using preferences.
Android preferences is a key/value entries that store data that can be specific to a certain activity or shared among all activities within the application.
the data are stored in a xml file within the application folders.
Saving Preferences
We can save preferences in three ways:
- Preferences can be retrieved only by a single activity.
- Preferences can be shared and retrieved among all activities within the application.
- Preferences can be shared and retrieved through all applications on the device.
Saving Activity-level preferences:
To save preferences that are accessed only from a single activity, we do it like this:
SharedPreferences prefs=getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor=prefs.edit();
editor.putString("pref 1", "some text");
editor.commit();
We get a SharedPreferences object by calling getPreferences(int mode) method which takes an integer value as a parameter, the mode value can be one of the following:
- Context.MODE_PRIVATE (0): a file creating mode that makes the created file only accessible by applications with the same user ID (access the file from the same application context, will desctribe later).
- Context.MODE_WORLD_READABLE (1): file mode makes the file readable from other applications.
- Context.MODE_WORLD_WRITEABLE (2): file mode allows other applications to write to the file.
Then we get an instance of SharedPreferences.Editor and write the preference value witheditor.putString(String key, String value) method.
shared preferences allows you to insert preferences using the following methods:
- editor.putBoolean(String key, boolean value).
- editor.putFloat(String key,float value).
- editor.putInt(String key, int value).
- editor.putLong(String key, long value)
- editor.putString(String key, String value)
Then we call edit.commit() to save the preferences to the file. commit returns a boolean indicating the result of saving, true if successful and false if failed.
Reading preferences values:
To read preferences values:
SharedPreferences prefs=getPreferences(Context.MODE_PRIVATE);
String val=prefs.getString("pref 1", "some text");
We use sharedpreferences.getString(String key, String defaultValue) (or get boolean/float/int) to return the value stored with a specific key or defaultValue if not found.
Saving Application-level preferences:
To save preferences that can be retrieved from all activities in the application we do it like this:
SharedPreferences prefs= getSharedPreferences("demopref", Context.MODE_WORLD_READABLE);
SharedPreferences.Editor editor=prefs.edit();
editor.putString("demostring", "hello");
editor.commit();
Same as the code above, but the difference is that we give our preferences file a name (demopref in this case) so that other activities can reference that preferences file.
Sharing preferences across applications:
We can store preferences in one application and read them in another application, this is done reading the preferences file by loading them through the first application’s context.
Let’s assume we have two applications:
- Application 1 with package name “com.mina.prefdemo”.
- Application2 with package name “com.mina.demoapp”.
If application1 creates a preferences file with the name “demopref” and inserts a String preference with the key/value “demostring/hello”.
now we access this file and value from application 2 like this:
Context con;
try {
con = createPackageContext("com.minasamy.prefdemo", 0);
SharedPreferences pref=con.getSharedPreferences("demopref", Context.MODE_PRIVATE);
String x=pref.getString("demostring", "not found");
txt.setText(x);
} catch (NameNotFoundException e) {
Log.e(Tag, e.toString());
}
Creating Preferences Activities:
Android provides another nice way of presenting and saving preferences. you can create Activities that extend PreferenceActivity.
PreferenceActivity is an activity that displays a set of built-in preferences related widgets that are defined in xml file.
The preference activity can be divided to several PreferenceCategory each containing a set of related preferences.
The preferences widgets that Android provide are:
- CheckBoxPreference: displays a check box widget.
- EditTextPreference: displays an EditText widget to save user prefs.
- RingtonePreference: displays a list with the ringtones on the device.
- ListPreference: displays a list of key/value items.
Each one of these preferences widgets is associated with a preference key. it’s value is persisted instantly as the widget selection changes.
we can construct our preferences screen xml (saved in res/xml directory) layout like this:
<?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <PreferenceCategory android:title="Catogory one" android:summary="sample summary"> <CheckBoxPreference android:title="Enable" android:key="pref_enable" android:summary="enables a preference"/> <EditTextPreference android:summary="Edit text prefrence" android:title="Edit" android:key="pref_edit"/> </PreferenceCategory> <PreferenceCategory android:title="Category2" android:summary="sample summary"> <RingtonePreference android:key="pref_ring" android:title="Ringtones preference"/> <ListPreference android:key="pref_list" android:title="List Preference" android:dialogTitle="List Pref Dialog" android:entries="@array/pref_items" android:entryValues="@array/pref_items_values"/> </PreferenceCategory>
Then from our activity:
public class PrefActivity extends PreferenceActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.prefs);
}
}
The activity will look like this:
The ListPreference can be associated with String array resources as it’s key/value entries
<string-array name="pref_items"> <item>Item1</item> <item>Item2</item> <item>Item3</item> <item>Item4</item> <item>Item5</item> </string-array> <string-array name="pref_items_values"> <item>1</item> <item>2</item> <item>3</item> <item>4</item> <item>5</item> </string-array>
To reference the preference widgets programmatically:
EditTextPreference pref_edit=(EditTextPreference)findPreference("pref_edit");
Calling Web Services
- Requesting SOAP web service
- Requesting REST web service
- Connecting to a web service over a Secure Sockets Layer (SSL): protocol
Calling Web Services
One of the most common functionalities required in mobile applications is to call a web service to retrieve data. This process involves requesting the web service with parameters, receiving the response and parsing it to obtain data.
Today the most common web services types are SOAP and REST. Android does not provide a built in SOAP client, there are many third party libraries that can be used, but we’ll see how to call a SOAP web service with native android APIs.
Requesting SOAP web service:
Before proceeding to the code, let’s take a look at the SOAP structure:
A soap request can be something like this:
POST /InStock HTTP/1.1 Host: www.example.org Content-Type: application/soap+xml; charset=utf-8 Content-Length: length SOAPAction: "http://www.w3schools.com/GetItems" <?xml version="1.0"?> <soap:Envelope xmlns:soap="http://www.w3.org/2001/12/soap-envelope" soap:encodingStyle="http://www.w3.org/2001/12/soap-encoding"> <soap:Header> <m:Trans xmlns:m="http://www.w3schools.com/transaction/" soap:mustUnderstand="1">234 </m:Trans> </soap:Header> <soap:Body> <m:GetPrice xmlns:m="http://www.w3schools.com/prices"> <m:Item>Apples</m:Item> </m:GetPrice> </soap:Body></soap:Envelope>
The SOAP request/response is sent as a SOAP Envelope which consists of a SOAP Header and a SOAP Body.
- SOAP Header: optional component of the envelop, contains application specific information, such as authentication.
- SOAP Body: the actual message sent to/received from the service.
- The header can contain a SOAP Action which identifies the desired function to be called by the service.
Calling the service:
To call the SOAP web service you have to do the following:
First: construct the SOAP envelope manually like this:
String envelope="<?xml version=\"1.0\" encoding=\"utf-8\"?>"+ "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"+ "<soap:Body>"+ "<GetItems xmlns=\"http://tempuri.org/\">"+ "<startDate>%s</ startDate>"+ "<getAll>%s</getAll>"+ "</Items>"+ "</soap:Body>"+ "</soap:Envelope>";
where %s are place holders where you substitute request parameters in like this
String requestEnvelope=String.format(envelope, "10-5-2011","true");
Second: call the web service like this:
Second: call the web service like this:
String CallWebService(String url,
String soapAction,
String envelope) {
final DefaultHttpClient httpClient=new DefaultHttpClient();
// request parameters
HttpParams params = httpClient.getParams();
HttpConnectionParams.setConnectionTimeout(params, 10000);
HttpConnectionParams.setSoTimeout(params, 15000);
// set parameter
HttpProtocolParams.setUseExpectContinue(httpClient.getParams(), true);
// POST the envelope
HttpPost httppost = new HttpPost(url);
// add headers
httppost.setHeader("soapaction", soapAction);
httppost.setHeader("Content-Type", "text/xml; charset=utf-8");
String responseString="";
try {
// the entity holds the request
HttpEntity entity = new StringEntity(envelope);
httppost.setEntity(entity);
// Response handler
ResponseHandler<string> rh=new ResponseHandler<string>() {
// invoked when client receives response
public String handleResponse(HttpResponse response)
throws ClientProtocolException, IOException {
// get response entity
HttpEntity entity = response.getEntity();
// read the response as byte array
StringBuffer out = new StringBuffer();
byte[] b = EntityUtils.toByteArray(entity);
// write the response byte array to a string buffer
out.append(new String(b, 0, b.length));
return out.toString();
}
};
responseString=httpClient.execute(httppost, rh);
}
catch (Exception e) {
Log.v("exception", e.toString());
}
// close the connection
httpClient.getConnectionManager().shutdown();
return responseString;
}
After calling this function, you will have the response as a String, something like this:
<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <GetItemsResponse xmlns="http://tempuri.org/"> <GetItemsResult> <Items> <Item> <name>string</name> <description>string</ description > </iPhoneCategory> <iPhoneCategory> <name>string</name> <description>string</ description > </ Item > </Items> </GetItemsResult> </ GetItemsResponse > </soap:Body> </soap:Envelope>
This response needs to be parsed to extract the data.
Requesting REST web service:
REST web services are much simpler, you request the service by calling a URL with the parameters. like this
http://example.com/resources/getitems
an example of calling a REST web service:
String callWebErvice(String serviceURL){
// http get client
HttpClient client=new DefaultHttpClient();
HttpGet getRequest=new HttpGet();
try {
// construct a URI object
getRequest.setURI(new URI(serviceURL));
} catch (URISyntaxException e) {
Log.e("URISyntaxException", e.toString());
}
// buffer reader to read the response
BufferedReader in=null;
// the service response
HttpResponse response=null;
try {
// execute the request
response = client.execute(getRequest);
} catch (ClientProtocolException e) {
Log.e("ClientProtocolException", e.toString());
} catch (IOException e) {
Log.e("IO exception", e.toString());
}
try {
in=new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
} catch (IllegalStateException e) {
Log.e("IllegalStateException", e.toString());
} catch (IOException e) {
Log.e("IO exception", e.toString());
}
StringBuffer buff=new StringBuffer("");
String line="";
try {
while((line=in.readLine())!=null)
{
buff.append(line);
}
} catch (IOException e) {
Log.e("IO exception", e.toString());
return e.getMessage();
}
try {
in.close();
} catch (IOException e) {
Log.e("IO exception", e.toString());
}
// response, need to be parsed
return buff.toString();
}
Connecting to a web service over a Secure Sockets Layer (SSL): protocol:
Android default HttpClinet does not support SSL connections, so if you have a secured web service, you need to connect to it via javax.net.ssl.HttpsURLConnection.
if you want to call a SSL SOAP web service:
String CallWebService(String url,
String soapAction,
String envelope) throws IOException {
URL address=new URL(url);
URLConnection connection=address.openConnection();
HttpsURLConnection post=(HttpsURLConnection)connection;
post.setDoInput(true);
post.setDoOutput(true);
post.setRequestMethod("POST");
post.setRequestProperty("SOAPAction", soapAction);
post.setRequestProperty( "Content-type", "text/xml; charset=utf-8" );
post.setRequestProperty( "Content-Length", String.valueOf(envelope.length()));
post.setReadTimeout(4000);
OutputStream outStream=post.getOutputStream();
Writer out=new OutputStreamWriter(outStream);
out.write(envelope);
out.flush();
out.close();
InputStream inStream = post.getInputStream();
BufferedInputStream in = new BufferedInputStream(inStream,4);
StringBuffer buffer=new StringBuffer();
// read 4 bytes a time
byte[] buffArray=new byte[4];
int c=0;
while((c=in.read(buffArray))!=-1){
for(int i=0;i<c;i++)
buffer.append((char)buffArray[i]);
}
return buffer.toString();
}
.
- Parsing XML
- Parsing the response with DOM Parser
- Parsing the response with SAX Parser
- Parsing JSON respone
In a previous last post we saw how to call REST and SOAP web services. The web service reponse can be one of the following:
- XML.
- SOAP.
- JSON.
Parsing XML
Android offers three types of XML parsers:
- DOM Parser.
- Pull Parser.
- SAX Parser.
we’ll demonstrate each using the following xml example:
<?xml version=”1.0″?>
<person>
<firstname>Jack</firstname>
<lastname>smith</lastname>
<age>28</age>
</person>
which we need to parse to create an object from Person class:
public class Person{
public String firstName;
public String lastName;
public int age;
}
Parsing the response with DOM Parser:
Android provides org.w3c.dom library that contains classes used to parse xml by constructing a document and
matching each node to parse the info.
to parse our example response with DOM parser, we implement a function like this
void parseByDOM(String response) throws ParserConfigurationException, SAXException, IOException{
Person person=new Person();
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new InputSource(new StringReader(response)));
// normalize the document
doc.getDocumentElement().normalize();
// get the root node
NodeList nodeList = doc.getElementsByTagName(“person”);
Node node=nodeList.item(0);
// the node has three child nodes
for (int i = 0; i < node.getChildNodes().getLength(); i++) {
Node temp=node.getChildNodes().item(i);
if(temp.getNodeName().equalsIgnoreCase(“firstname”)){
person.firstName=temp.getTextContent();
}
else if(temp.getNodeName().equalsIgnoreCase(“lastname”)){
person.lastName=temp.getTextContent();
}
else if(temp.getNodeName().equalsIgnoreCase(“age”)){
person.age=Integer.parseInt(temp.getTextContent());
}
}
Log.e(“person”, person.firstName+ ” “+person.lastName+” “+String.valueOf(person.age));
}
The previous method is good, it retrieves the info correctly, but it requires that you are familiar with the xml structure so that you know the order of each xml node.
luckily Android provides a better approach of parsing using SAX parser.
Parsing the response with SAX Parser:
Android provides org.xml.sax package that has that provides the event-driven SAX parser.
to parse the previous response with SAX parser, we have to create a class extendingDefaultHandler and override the following methods:
- startDocument(): invoked when the xml document is open, there we can initialize any member variables.
- startElement(String uri, String localName, String qName, Attributes attributes): invoked when the parser encounters a xml node, here we can initialize specific instances of our person object.
- endElement(String uri, String localName, String Name): invoked when the parser reaches the closing of a xml tag. here the element value would have been completely read.
- characters(char[] ch, int start, int length): this method is called when the parser reads characters of a node value.
so our parsing class will be like this:
/*
* SAX parser to parse persons response
*/
public class PersonParser extends DefaultHandler
{
// arraylist to store person objects
ArrayList persons;
// temporary person object
Person tempPerson;
// string builder acts as a buffer
StringBuilder builder;
/**
* Initialize the arraylist
* @throws SAXException
*/
@Override
public void startDocument() throws SAXException {
pesons=new ArrayList();
}
/**
* Initialize the temp person object which will hold the parsed info
* and the string builder that will store the read characters
* @param uri
* @param localName
* @param qName
* @param attributes
* @throws SAXException
*/
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
if(localName.equalsIgnoreCase.equals(“person”)){
tempPerson=new Person();
builder=new StringBuilder();
}
}
/**
* Finished reading the person tag, add it to arraylist
* @param uri
* @param localName
* @param qName
* @throws SAXException
*/
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
// finished reading a person, add it to the arraylist
if(localName.toLowerCase().equals(“person”))
{
this.persons.add(tempPerson);
}
// finished reading “firstname” tag assign it to the temp person
else if(localName.toLowerCase().equals(“firstname”)){
tempPerson.firstName=builder.toString();
}
// finished reading “lastname” tag assign it to the temp person
else if(localName.toLowerCase().equals(“lastname”)){
tempPerson.lastName=builder.toString();
}
// finished reading “age” tag assign it to the temp person
else if(localName.toLowerCase().equals(“age”)){
tempPerson.age=Integer.parseInt(builder.toString());
}
}
/**
* Read the value of each tag
* @param ch
* @param start
* @param length
* @throws SAXException
*/
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
// read the characters and append them to the buffer
String tempString=new String(ch, start, length);
builder.append(tempString);
}
}
the code is pretty easy, the parser iterates over each node, you check the current node name and take an action.
then we call the parser like this:
public ArrayList getPersons(final String response) throws ParserConfigurationException, SAXException, IOException
{
BufferedReader br=new BufferedReader(new StringReader(response));
InputSource is=new InputSource(br);
PersonParser parser=new PersonParser();
SAXParserFactory factory=SAXParserFactory.newInstance();
SAXParser sp=factory.newSAXParser();
XMLReader reader=sp.getXMLReader();
reader.setContentHandler(parser);
reader.parse(is);
ArrayList persons=parser.persons;
return persons;
}
Parsing JSON respone:
what if our repspone was JSON instead of xml. it would be something like this:
“persons”
[
{
“person”{
“firstName”: “John”,
“lastName”: “Smith”,
“age”: 25
}
}
{
“person”{
“firstName”: “Catherine”,
“lastName”: “Jones”,
“age”: 35
}
}
]
this response is a JSON Array with the name “persons”, this array consists of “person” JSON Objects.
to parse such a reponse:
public ArrayList<Person> getMessage(String response){
JSONObject jsonResponse;
ArrayList<Person> arrPersons=new ArrayList<Person>;
try {
// obtain the reponse
jsonResponse = new JSONObject(response);
// get the array
JSONArray persons=jsonResponse.optJSONArray(“persons”);
// iterate over the array and retrieve single person instances
for(int i=0;i<persons.length();i++){
// get person object
JSONObject person=persons.getJSONObject(i);
// get first name
String firstname=person.optString(“firstname”);
// get last name
String lastname=person.optString(“lastname”);
// get the age
int age=person.optInt(“age”);
// construct the object and add it to the arraylist
Person p=new Person();
p.firstName=firstname;
p.lastName=lastname;
p.age=age;
arrPersons.add(p);
}
} catch (JSONException e) {
e.printStackTrace();
}
return arrPersons;
}
much easier than the previous methods.
notice that we used the methods optJSONArray,optString,optInt instead of using getString,getIntbecause the opt methods return empty strings or zero integers if no elements are found. while the get methods throw an exception if the element is not found.
Android Services
· Creating a service
- Calling a service
- Binding to a service through an Binder object
Android Services
Android Service is used for long-running processes that do not require user interaction, such as calling a web service and parsing response. Or processes that need to be running even if the application that started the service is not on the foreground such as playing mp3 files in a music player.
we need to distinguish between A Service and a Thread or an AsyncTask: Threads or Async task perform their tasks in a background thread thus they do not block the main thread, while a service performs it’s work in the main thread. so if a service is performing an intensive task such as calling a web service, it may block the main thread until it finishes. So for intensive tasks a service should run it’s work in a background thread.
A service runs in the same process of the application and keeps running until stopped by itself, stopped by the user or killed by the system if it needs memory.
Creating a service:
to create a service we create a class that extends android.app.Service and it would be like this:
public class DemoService extends Service {
@Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return null;
}
}
next we need to define our service in our AndroidManifest.xml file:
<service android:name="DemoService"></service>
The service life cycle has the following events
- onCreate(): called when the service is created.
- onStart(): Called when the service starts by a call to startService(Intent intent).
- onDestroy(): Called as the service is terminates.
Calling a service:
A service can be called from an activity in two ways:
- By calling startService(Intent intent).
- By binding to the service through an Binder object.
calling startService(Intent intent):
to start a service from an activity using this method, we create an intent and start the service like this:
Intent intent=new Intent(this,DemoService.class); startService(intent);
the startService(intent) method causes the onStart() method of the service to be called, so the service can execute it’s work like this:
public class DemoService extends Service {
@Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
doSomething();
}
public void doSomething(){
// do some work
}
}
the service will keep running until it stops itself via stop stopSelf() after finishing work:
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
doSomething();
stopSelf();
}
or it can be stopped from the activity via stopService(Intent intent).
Binding to a service through an Binder object:
As the service runs in the same process of the application the service has only one instance (singleton) instance running. you may want to keep reference to this instance to perform periodical tasks or to call the service methods themselves.
to make the service bind-able we extends Binder class and return an instance of it in the service’sonBind(Intent intent) method:
public class DemoService extends Service {
private final IBinder binder = new LocalBinder();
@Override
public IBinder onBind(Intent arg0) {
return binder;
}
public class LocalBinder extends Binder {
DemoService getService() {
return DemoService.this;
}
}
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
doSomething();
stopSelf();
}
public void doSomething(){
// do something
}
}
then we bind the service from our activity by first creating a ServiceConnection object to handle the service connection/disconnection then binding to the service by an intent like this:
public class MainActivity extends Activity {
DemoService mService;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
ServiceConnection serviceConn=new ServiceConnection() {
/**
* service unbound, release from memory
**/
@Override
public void onServiceDisconnected(ComponentName name) {
mService=null;
}
/**
* service is bound, start it's work
**/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService=((LocalBinder)service).getService();
mService.doSomething();
}
};
@Override
protected void onResume() {
super.onResume();
// bind to the service by an intent
Intent intent=new Intent(this,DemoService.class);
// AUTO CREATE: creates the service and gives it an importance so that it won't be killed
// unless any process bound to it (our activity in this case) is killed to
bindService(intent, serviceConn, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
/ unbind the service whena ctivity is destroyed
unbindService(serviceConn);
}
}
notice that we unbind the service in the activity’s onDestroy() method to disconnect from the service and stop it from executing any further
Implementing remote Android Services with AIDL
- Defining the AIDL file
- Defining the Service
- Consuming the service at the client application
Implementing remote Android Services with AIDL
Use of Android services to do time consuming operations in the background. in this post we will see how can a client application call the methods of a service defined in another application. this is achieved through Android Interface Definition Language (AIDL).
AIDL is a java like language that enables you to define an interface that both the application defining the service and the client application implement it.
the interface defines the functions that are needed to be called in the client application.
Defining the AIDL file:
AIDL syntax is similar to that of Java, we can use the following data types in AIDL:
- primitive data types: int, long, char, boolean,….
- String.
- CharSequence.
- List (ArrayList,Vector,…).
- the AIDL file is defined as follows:
open a notepad file and paste the following code in it: - package com.mina.servicedemo;
- // service interface
- interface IRemoteService {
- //sample method
- String sayHello(String message);
}
take care of the package name com.mina.servicedemo.
we defined a methods sayHello(String message) that returns a string.
- save the file with the name IRemoteService and change it’s extension to .aidl.
- copy the file to the src folder of your project.
10.once you save and build the file, Android generates an interface java file with the name IRemoteService.java in the gen folder if the project.
Defining the Service:
now we want our service to expose this interface to client applications, so we return an implementation of the service in the onBind() method of our service:
package com.mina.servicedemo;
import com.mina.servicedemo.IRemoteService.Stub;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.widget.Toast;
public class DemoService extends Service {
@Override
public IBinder onBind(Intent arg0) {
return mBinder;
}
// implementation of the aidl interface
private final IRemoteService.Stub mBinder=new Stub() {
@Override
public String sayHello(String message) throws RemoteException {
return “Hello “+message;
}
};
}
}
the last thing to do in the service is to make its exported attribute in the AndroidManifest.xml file set to true like this:
<service android:name=”DemoService” android:exported=”true”></service>
our app structure can be like this:
Consuming the service at the client application:
now to our client application where we want to invoke methods from our service. the client application is a separate application with a different package name than that where the service is defined.
the client application needs a reference to the AIDL interface defined in the original applcation, this is done through the following steps:
- in the client applicatio create a package with the same package name of that the service is defined in: com.mina.servicedemo.
- copy the AIDL file in this package.
- save and build and a new file called IRemoteService.java is generated. your app structure should be like this:
and we invoke the servcice methods in our activity like this:
package com.mina.serviceclient;
import com.mina.servicedemo.IRemoteService;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
public class MainActivity extends Activity {
IRemoteService mRemoteService;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Intent serviceIntent=new Intent();
serviceIntent.setClassName(“com.mina.servicedemo”, “com.mina.servicedemo.DemoService”);
boolean ok=bindService(serviceIntent, mServiceConnection,Context.BIND_AUTO_CREATE);
Log.v(“ok”, String.valueOf(ok));
}
private ServiceConnection mServiceConnection=new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
// TODO Auto-generated method stub
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// get instance of the aidl binder
mRemoteService = IRemoteService.Stub.asInterface(service);
try {
String message=mRemoteService.sayHello(“Mina”);
Log.v(“message”, message);
} catch (RemoteException e) {
Log.e(“RemoteException”, e.toString());
}
}
};
}
Implementing Search activities
Most Android phones have a search button. this button is used to search contacts,applications or anything on the phone. We can make use of the search functionality in our apps.
.
Creating database:
our database has two tables: Countries and Names:
public class DBHelper extends SQLiteOpenHelper {
public DBHelper(Context context) {
super(context, "DemoDB", null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
StringBuilder builder=new StringBuilder();
// countries table
builder.append("CREATE TABLE Countries ");
builder.append("(_id INTEGER PRIMARY KEY AUTOINCREMENT,");
builder.append("NAME TEXT) ");
db.execSQL(builder.toString());
// Names table
// Virtual table for full text search
builder.setLength(0);
builder.append("CREATE VIRTUAL TABLE NAMES USING FTS3");
builder.append("(");
builder.append("name TEXT) ");
db.execSQL(builder.toString());
builder=new StringBuilder();
//dummy data
InsertData(db);
}
void InsertData(SQLiteDatabase db)
{
ContentValues cv=new ContentValues();
cv.put("NAME","USA");
db.insert("Countries", "NAME", cv);
cv.put("NAME","UK");
db.insert("Countries", "NAME", cv);
cv.put("NAME","Spain");
db.insert("Countries", "NAME", cv);
cv.put("NAME","ITALY");
db.insert("Countries", "NAME", cv);
cv.put("NAME","Germany");
db.insert("Countries", "NAME", cv);
cv=new ContentValues();
cv.put("name","John");
db.insert("NAMES", "name", cv);
cv.put("name","Jack");
db.insert("NAMES", "name", cv);
cv.put("name","Ann");
db.insert("NAMES", "name", cv);
cv.put("name","Adam");
db.insert("NAMES", "name", cv);
cv.put("name","Sarah");
db.insert("NAMES", "name", cv);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub
}
}
notice that the Names table is a VIRTUAL table. we created it as virtual to make use of Full Text Search (FTS3) feature in SQLite. this feature makes queries faster than that in regular tables.
then we add two functions to retrieve all rows from both tables:
/**
* Return all countries
* @return
*/
public ArrayListgetCountries(){
ArrayList countries=new ArrayList();
SQLiteDatabase db=this.getReadableDatabase();
Cursor c=db.rawQuery("select * from Countries", null);
while(c.moveToNext()){
String country=c.getString(1);
countries.add(country);
}
c.close();
return countries;
}
/**
* Return all names
* @return
*/
public ArrayListgetNames(){
ArrayList names=new ArrayList();
Cursor c=this.getReadableDatabase().rawQuery("select * FROM Names", null);
while(c.moveToNext()){
String name=c.getString(0);
names.add(name);
}
c.close();
return names;
}
and another two functions to retrieve data based on a search string:
/**
* Return all countries based on a search string
* @return
*/
public ArrayListgetCountriesSearch(String query){
ArrayList countries=new ArrayList();
SQLiteDatabase db=this.getReadableDatabase();
Cursor c=db.rawQuery("select * from Countries where NAME LIKE '%"+query+"%'", null);
while(c.moveToNext()){
String country=c.getString(1);
countries.add(country);
}
c.close();
return countries;
}
/**
* Return all names based on a search string
* we use the MATCH keyword to make use of the full text search
* @return
*/
public ArrayListgetNamesSearch(String query){
ArrayList names=new ArrayList();
Cursor c=this.getReadableDatabase().rawQuery("select * FROM Names WHERE name MATCH '"+query+"'", null);
while(c.moveToNext()){
String name=c.getString(0);
names.add(name);
}
c.close();
return names;
}
Implementing The activity:
then we will create our activity that has a list view like this:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <ListView android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/list"/> </LinearLayout>
we load data from database like this:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
list=(ListView)findViewById(R.id.list);
DBHelper helper=new DBHelper(this);
ArrayList items=helper.getNames();
ArrayAdapter adapter=new ArrayAdapter(this, android.R.layout.simple_list_item_1,items);
list.setAdapter(adapter);
}
Handling the search dialog:
In order to handle the search dialog ourselves we need to create a xml file with search configurations such as the search dialog title, voice search capabilities, content provider for auto complete and so on. we create a file with the name searchable.xml in res/xmldirectory:
<?xml version="1.0" encoding="utf-8"?> <searchable xmlns:android="http://schemas.android.com/apk/res/android" android:label="@string/app_name" android:hint="@string/hint" > </searchable>
the android:hint attribute denotes a string that acts as a water mark on the search text box.
then we need to add an Intent Filter in out app’s AndroidManifest.xml file to our activity to handle the search dialog:
<activity android:name=".MainActivty" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <intent-filter> <action android:name="android.intent.action.SEARCH" /> </intent-filter> <meta-data android:name="android.app.searchable" android:resource="@xml/searchable"/> </activity>
Understanding the Search process:
when you press the search button, type some text and click on search the activit’sonSearchRequested() function is called, then an Intent with the action Intent.ACTION_SEARCH is created and you activity is re-created with this intent.
the search intent has you search string as a string extra with the name SearchManager.QUERY. also it can carry a bundle of other extras with the name SearchManager.APP_DATA.
what if the device doesn’t have a Search button:
not all Android devices have a search button, so we can start the search dialog manually by calling the activity’s onSearchRequested() from a button or a menu item:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add("Search").setOnMenuItemClickListener(new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
//launch the search dialog
onSearchRequested();
return true;
}
});
return true;
}
adding extras to the search dialog:
we can pass some extra data as a bundle with our search dialog or an initial search string by overriding the activity’s onSearchRequested():
@Override
public boolean onSearchRequested() {
Bundle bundle=new Bundle();
bundle.putString("extra", "exttra info");
// search initial query
startSearch("Country", false, bundle, false);
return true;
}
Handling the search query:
we said before that the search query is passed as a String extra when our activity is re-created. so we can handle the searcgh string in our onCreate() like this:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
list=(ListView)findViewById(R.id.list);
DBHelper helper=new DBHelper(this);
Intent intent=getIntent();
// if the activity is created from search
if(intent.getAction().equals(Intent.ACTION_SEARCH)){
// get search query
String query=intent.getStringExtra(SearchManager.QUERY);
ArrayList items=helper.getNamesSearch(query);
//get extras, just for demonstration
Bundle bundle=intent.getBundleExtra(SearchManager.APP_DATA);
String info=bundle.getString("extra");
Log.v("extra", info);
//bind the list
ArrayAdapter adapter=new ArrayAdapter(this, android.R.layout.simple_list_item_1,items);
list.setAdapter(adapter);
}
//activity created normally
else{
ArrayList items=helper.getNames();
ArrayAdapter adapter=new ArrayAdapter(this, android.R.layout.simple_list_item_1,items);
list.setAdapter(adapter);
}
helper.close();
}
we just extract the search string and any other extras and perform our search logic based on the search string.
