NOTE:

NOTE: Of late, I have been getting requests for very trivial problems that many of you are facing in your day-to-day work. This blog is not to solve your "project" problems - surely not a "Support" site.
I just love to share my knowledge in my spare time and would appreciate any questions or feedback on the articles and code I have shared. I also do appreciate thought-provoking questions that would lead me to write more articles and share.
But please do not put your day-to-day trivial problems here. Even if you do, you most probably would not get a response here.
Thanks

Search This Blog

x

Tuesday 29 September 2009

Content Provider - Android Developer Tutorial (Part 10)


Content Providers are again one of the fundamental building blocks of the Android Platform. So far, we have looked at Activities, Services, which are basic building blocks. We have also looked at intents, notifications and variations of the same. None of these have dealt with stored data. Content Provider brings us to the concept of how to deal with data stored in the Android mobile esp. if the data has to be shared across applications.

Before we move on to shared data, let us understand the basics of data storage support provided by Android. 

There are 4 ways we can store data:

  • 1.    Preferences
  • 2.    Files
  • 3.    RDBMS (SQLite)
  • 4.    Network

Preferences – Preferences are used to store user preferences for a single application or across applications in a mobile. They are typically name-value pairs accessible to the context.

Files – Files can be stored directly on to the mobile or to extended storage mediums. They are by default not accessible to applications other than the one that created it.

Database (RDBMS) – Android support creation of databases based on SQLite. These are again private to the application that created it.

Network – Android provides API support to store data over the network on to a server, probably
Note that all these are various forms of storing data and most often for a single application. Irrespective of how data is stored, if it has to be shared across applications, Here comes the concept of Content Providers.

Content Providers are the only way to share data across Android applications. They store and retrieve data thus making it accessible to all. Android platform provides default implementations of content providers for data types like audio, video, images, contact information etc.

In the sample I would like to first show how to work with existing content providers like the contact information. We will first view the existing contacts on the phone. We will insert a new contact (hardcoded name and phone number), update the same contact and delete the same contact, in this example. Note that the update and delete will not work till we create a contact through this example.

Introduction to Content Providers:

Irrespective of how the data is stored, Content Providers give a uniform interface to access the data. Data is exposed as a simple table with rows and columns where row is a record and column is a particular data type with a specific meaning. Like a row could be about a single person and the columns could be the person’s first name, number, address, email id etc.

Each record is identified by a unique _ID field which is the key to the record. Each content provider exposes a unique URI that identifies its data set uniquely. This URI is equivalent to a table name in a database. The URI consists of various parts: eg: content://com.colllabera.labs.sai/tasks/123 is a unique URI. content:// is a standard prefix. com.collabera.labs.sai is the authority, tasks is the table name, 123 is the unique _ID.

For the native content providers, these unique URIs are declared as constants in an interface. So, in our program we will be using constants like People.CONTENT_URI which internally translates to content://contacts/people

Let us now look at the code to view all the existing contacts:

//Here is the button to click for viewing the contacts
Button view = (Button)findViewById(R.id.viewButton);
//The method / class that gets invoked when the View button is clicked
view.setOnClickListener(new OnClickListener() {
    public void onClick(View v){
       displayContacts();
       Log.i("NativeContentProvider", "Completed Displaying Contact list");
    }
});
//Here is the displayContacts() method
private void displayContacts() {
      String[] columns = new String[] {People.NAME,People.NUMBER};
      Uri mContacts = People.CONTENT_URI;
      Cursor mCur = managedQuery(mContacts, // Contact URI
                  columns,    // Which columns to return
                  null,       // Which rows to return
                  null,       // Where clause parameters
                  null        // Order by clause
                  );
      if (mCur.moveToFirst()) {
            String name = null;
            String phoneNo = null;
            do {
              name = mCur.getString(mCur.getColumnIndex(People.NAME));
              phoneNo = mCur.getString(mCur.getColumnIndex(People.NUMBER));
              Toast.makeText(NativeContentProvider.this, name + " " + phoneNo, Toast.LENGTH_SHORT).show();
            } while (mCur.moveToNext());
      }
}

Here we are using the Activity.managedQuery(..) to create and execute a query against the provided URI. The comments against the parameters in the code is self-explanatory. This returns a cursor object that can be iterated using the two methods moveToFirst() and moveToNext(). For simplicity sake, I have just toasted the contact name and phone number retrieved. An advanced tutorial can start a new activity that can display this in a ListView.

Now, we can move on to creating a new contact. While the button related code will be very similar to the above, let us look at the actual createContact() method.

    private void createContact(String name, String phone) {
      ContentValues contact = new ContentValues();
      contact.put(People.NAME, name);
      insertUri = getContentResolver().insert(People.CONTENT_URI, contact);
      Log.d(getClass().getSimpleName(),insertUri.toString());
      Uri phoneUri = Uri.withAppendedPath(insertUri, People.Phones.CONTENT_DIRECTORY);
      contact.clear();
      contact.put(People.Phones.TYPE, People.TYPE_MOBILE);
      contact.put(People.NUMBER, phone);
      updateUri = getContentResolver().insert(phoneUri, contact);
      Toast.makeText(NativeContentProvider.this, "Created a new contact: " + name + " " + phone, Toast.LENGTH_SHORT).show();
      Log.d(getClass().getSimpleName(),updateUri.toString());
    }

Here we need to understand 2 new classes: ContentResolver and ContentValues. A ContentResolver provides applications access to the content data / model. We can get a handle to a ContentResolver by calling the getContentResolver() method within the Activity. This provides methods to insert, update and delete data. In order to insert data, we need to provide it through a ContentValues object. A ContentValues Object is nothing but a name, value pair where the name of the column is to be mentioned. So, we pass the URI and the ContentValues to insert() method which returns a unique URI with the new ID created. Once we get the ID of the new person/contact inserted, we insert his/her mobile phone details into the related Phones table by using the returned insertUri. The insertUri which is unique to the new record is stored as a class variable to use it in the delete method later. The phoneUri is also stored for updating the same in the updateContact() method later.

Note that People is a class that has implemented various interfaces like android.priovider.BaseColumns, android.provider.Contacts.Phones, android.provider.Contact.PeopleColumns etc. These constants come from the interfaces.

With the above understanding let us see the update and delete methods:

   private void updateContact(String phone) {
      if (updateUri == null) {
            Toast.makeText(NativeContentProvider.this, "There is nothing to update, Please create a contact and then click update", Toast.LENGTH_LONG).show();
      } else {
            ContentValues newPhone = new ContentValues();
            newPhone.put(People.Phones.TYPE, People.TYPE_MOBILE);
            newPhone.put(People.NUMBER, phone);
            getContentResolver().update(updateUri, newPhone, null,null);
            Toast.makeText(NativeContentProvider.this, "Updated the phone number to: " + phone, Toast.LENGTH_SHORT).show();
            Log.i(getClass().getSimpleName(), "Updated the phone number");
      }
    }
   
    private void deleteContact() {
      if (updateUri == null) {
            Toast.makeText(NativeContentProvider.this, "Please create a contact by clicking create button, then I can delete the same", Toast.LENGTH_LONG).show();
           
      } else {
            getContentResolver().delete(insertUri, null, null);
            Toast.makeText(NativeContentProvider.this, "Deleted contact at: " + insertUri.toString(), Toast.LENGTH_SHORT).show();
            updateUri = null;
            insertUri = null;
            Log.i(getClass().getSimpleName(),"Deleted the contact inserted by this program");
      }
    }

These methods only manipulate the freshly created record, for simplicity sake. They call upon the update() and delete() method on the ContentResolver.

The complete code for this example is available here.

Please note that you must add the following permissions to the AndroidManifest.xml file to be able to access the contacts.

<uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission>
<uses-permission android:name="android.permission.WRITE_CONTACTS"></uses-permission>

Otherwise you get a SecurityException.

UPDATE: Please note that the article talks about the Contacts API before SDK 2.0. For the newer updated version, please check this article on New Content Provider

Thursday 24 September 2009

Remote Service | Android Developer Tutorial (Part 9)


Services typically are required to run for a long time and hence should run in their own thread. Such services can be invoked by any number of clients who want to connect to the service, invoke a few methods on the service and finally release the service, probably to serve more clients or close down.

Here, I would like to introduce you to the concept of connecting to a remote service and the kind of support provided by the android platform for the same.

We have earlier seen how local services can be created and used. The difference between the two mainly is that the local service runs in the same process as the application that started it and hence the life of the local service is dependent on the life of the said application while remote service can run in its own process. This causes a challenge of inter-process communication. If one process wants to communicate with another process, the object that is passed between the two needs to be marshaled.

For this purpose, Android provides the AIDL (Android Interface Definition Language) tool that handles the marshaling as well as the communication.

The service has to declare a service interface in an aidl file and the AIDL tool will automatically create a java interface corresponding to the aidl file. The AIDL tool also generates a stub class that provides an abstract implementation of the service interface methods. The actual service class will have to extend this stub class to provide the real implementation of the methods exposed through the interface.

The service clients will have to invoke the onBind() method on the service to be able to connect to the service. The onBind() method returns an object of the stub class to the client. Here are the code related code snippets:

The AIDL file:
package com.collabera.labs.sai;

interface IMyRemoteService {

      int getCounter();
}

Once you write this AIDL file (.aidl) in eclipse, it will automatically generate the Remote interface corresponding to this file. The remote interface will also provide a stub inner class which has to have an implementation provided by the RemoteService class. The stub class implementation within the service class is as given here:

private IMyRemoteService.Stub myRemoteServiceStub = new IMyRemoteService.Stub() {
            public int getCounter() throws RemoteException {
                  return counter;
            }
      };
The onBind() method in the service class:
      public IBinder onBind(Intent arg0) {
            Log.d(getClass().getSimpleName(), "onBind()");
            return myRemoteServiceStub;
      }

Now, let us quickly look at the meat of the service class before we move on to how the client connects to this service class. My RemoteService class is just incrementing a counter in a separate thread. This thread is created in the onStart() method as this gets certainly called whether the service is connected to by a call to startService(intent). Please read the lifecycle of a service if this needs more clarity. Here are the over-ridden onCreate(), onStart() and onDestroy() methods. Note that the resources are all released in the onDestroy() method.

      public void onCreate() {
            super.onCreate();
            Log.d(getClass().getSimpleName(),"onCreate()");
      }
      public void onStart(Intent intent, int startId) {
            super.onStart(intent, startId);
            serviceHandler = new Handler();
            serviceHandler.postDelayed(myTask, 1000L);
            Log.d(getClass().getSimpleName(), "onStart()");
      }
      public void onDestroy() {
            super.onDestroy();
            serviceHandler.removeCallbacks(myTask);
            serviceHandler = null;
            Log.d(getClass().getSimpleName(),"onDestroy()");
      }

A little explanation: In the onStart() method, I created a new Handler object that will spawn out a new task that implements the Runnable interface. This thread does the job of incrementing the counter. Here is the code for the Task class – an inner class of the RemoteService class.

class Task implements Runnable {
      public void run() {
            ++counter;
            serviceHandler.postDelayed(this,1000L);
            Log.i(getClass().getSimpleName(), "Incrementing counter in the run method");
      }
}

An object of this Task class is passed to the serviceHandler object as a message that needs to be executed after 1 second. The Task class implements the run() method in which we repeatedly post the same message to the serviceHandler. Thus, this becomes a repeated task till all the messages in the serviceHandler queue are deleted by calling the removeCallbacks() method on the serviceHandler in the destroy() method of the RemoteService class.

Note that the onDestroy() method thus stops this thread and set the serviceHandler to null. This completes the implementation of the RemoteService class. The complete code is downloadable here.
Now coming to the client class - Here, for simplicity sake, I have put the start, stop, bind, release and invoke methods all in the same client. While in reality, one client may start and another can bind to the already started service.

There are 5 buttons one each for start, stop, bind, release and invoke actions. A client needs to bind to a service before it can invoke any method on the service.
Here are the start and the bind methods.

private void startService(){
     if (started) {
       Toast.makeText(RemoteServiceClient.this, "Service already started", Toast.LENGTH_SHORT).show();
     } else {
       Intent i = new Intent();
       i.setClassName("com.collabera.labs.sai", "com.collabera.labs.sai.RemoteService");
       startService(i);
       started = true;
       updateServiceStatus();
       Log.d( getClass().getSimpleName(), "startService()" );
      }
                 
  }

An explicit intent is created and the service is started with the Context.startService(i) method.
Rest of the code is to update some status on the UI. There is nothing specific to a remote service invocation here. It is on the bindService() method that we see the difference from a local service.

private void bindService() {
     if(conn == null) {
        conn = new RemoteServiceConnection();
        Intent i = new Intent();
        i.setClassName("com.collabera.labs.sai", "com.collabera.labs.sai.RemoteService");
        bindService(i, conn, Context.BIND_AUTO_CREATE);
        updateServiceStatus();
        Log.d( getClass().getSimpleName(), "bindService()" );
     } else {
       Toast.makeText(RemoteServiceClient.this, "Cannot bind - service already bound", Toast.LENGTH_SHORT).show();
     }
}

Here we get a connection to the remote service through the RemoteServiceConnection class which implements ServiceConnection Interface. The connection object is required by the bindService() method – an intent, connection object and the type of binding are to be specified. So, how do we create a connection to the RemoteService? Here is the implementation:

class RemoteServiceConnection implements ServiceConnection {
      public void onServiceConnected(ComponentName className,
      IBinder boundService ) {
remoteService = IMyRemoteService.Stub.asInterface((IBinder)boundService);
            Log.d( getClass().getSimpleName(), "onServiceConnected()" );
      }

      public void onServiceDisconnected(ComponentName className) {
            remoteService = null;
            updateServiceStatus();
            Log.d( getClass().getSimpleName(), "onServiceDisconnected" );
      }
};

The Context.BIND_AUTO_CREATE ensures that a service is created if one did not exist although the onstart() will be called only on explicit start of the service.

Once the client is bound to the service and the service has already started, we can invoke any of the methods that are exposed by the service. Here we have only one method and that is getCounter(). In this example, the invocation is done by clicking the invoke button. That would update the counter text that is below the button. 

Let us see the invoke method:

private void invokeService() {
     if(conn == null) {
        Toast.makeText(RemoteServiceClient.this, "Cannot invoke - service not bound", Toast.LENGTH_SHORT).show();
     } else {
        try {
            int counter = remoteService.getCounter();
            TextView t = (TextView)findViewById(R.id.notApplicable);
            t.setText( "Counter value: "+Integer.toString( counter ) );
            Log.d( getClass().getSimpleName(), "invokeService()" );
        } catch (RemoteException re) {
            Log.e( getClass().getSimpleName(), "RemoteException" );
        }
     }
}    

Once we use the service methods, we can release the service. This is done as follows (by clicking the release button):

private void releaseService() {
      if(conn != null) {
            unbindService(conn);
            conn = null;
            updateServiceStatus();
            Log.d( getClass().getSimpleName(), "releaseService()" );
      } else {
            Toast.makeText(RemoteServiceClient.this, "Cannot unbind - service not bound", Toast.LENGTH_SHORT).show();
      }
}

Finally we can stop the service by clicking the stop button. After this point no client can invoke this service.

private void stopService() {
      if (!started) {
            Toast.makeText(RemoteServiceClient.this, "Service not yet started", Toast.LENGTH_SHORT).show();
      } else {
            Intent i = new Intent();
            i.setClassName("com.collabera.labs.sai", "com.collabera.labs.sai.RemoteService");
            stopService(i);
            started = false;
            updateServiceStatus();
            Log.d( getClass().getSimpleName(), "stopService()" );
      }
}
These are the basics of working with a remote service on Android platform. All the best!


Addendum (updated on 6 Jan 2011) – based on many questions related to Remote services. If the client accessing the remote service is in a separate package or application, it has to include the .aidl file along with the package structure as in the Service provider.
I have written a sample remote client in a completely different application, while the remote service is the same one provided above. You may download the client app here.


NOTE on Code for download. The complete Server and client code is downloadable here. While the client alone in a separate app is here. If this client code has to work, you have to download the previous server code as well, and then use the client to start, stop, bind etc.