Ejemplo simple de ContentProvider Android
Un ContentProvider de Android es una especie de BD que una aplicación abre al resto de aplicaciones. Por ejemplo, si hacemos una aplicación con una BD SQLite está solamente estará disponible para la propia aplicación pero la podriamos abrir/ofrecer al resto del sistema a través de un content provider. Pero ojo¿solamente bases de datos? No, los content providers pretenden como su nombre indica proveer contenido y lo mismo puede ser una BD que unos ficheros, que unos datos arbitrarios. Lo peculiar del Content Provider es que la clase que ofrece para ello es prácticamente clavada a un CRUD de SQLite. Pero insisto no tiene por qué ser una base de datos lo que haya detrás.
¿Cómo monto esto?
Creas una aplicación normal con su activity normal. Dentro de la aplicación puedes añadir un ContentProvider, si estás en eclipse tira del asistente y te generará la entrada que hace falta en el Manifest de Android. En el cliente que vaya a usar el ContentProvider debes declararlo también, como un permiso. A partir de ahí, las peticiones del Cliente al Provider se hacen a través de... chan chan URIs, vamos, URL con parámetros. Un provider se asemeja algo a un recurso REST en la forma de acceder.
ContentProvider super simple
El ContentProvider que he desarrollado aquí no es más que una prueba de concepto muy simple para probar tres cosas:
- Lo esencial: desde un cliente invocar a un ContentProvider.
- Proveer datos ajenos a SQL: crear un cursor con mis datos arbitrarios.
- UriMatcher: jugar un poco con los matcher para aplicar distintas queries
package info.pello.android.contentprovider; import android.content.ContentProvider; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.UriMatcher; import android.database.Cursor; import android.database.MatrixCursor; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.util.Log; /** * A content provider to offer Student data to clients. * It extends ContentProvider class providing a common way * to manage data with a CRUD-like methods set. * @author Pello Xabier Altadill Izura * @greetz any */ public class StudentContentProvider extends ContentProvider { // We set uriMatcher to get params passed to URIs. // So we can give different values depending on those params private UriMatcher uriMatcher; // Our data: private MatrixCursor mCursor; /** * default constructor. */ public StudentContentProvider() { } /** * called when provider is started, so we use it to initialize data. */ @Override public boolean onCreate() { Log.d("PELLODEBUG","CP> onCreate, init data."); initUris(); initData(); return true; } /** * init content provider Uris * we set some kind of uri patterns to route them to different queries */ private void initUris() { uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); // This will match: content://info.pello.android.contentprovider.provider.Students/students/ uriMatcher.addURI("info.pello.android.contentprovider.provider.Students", "students/", 1); // This will match: content://info.pello.android.contentprovider.provider.Students/students/2 uriMatcher.addURI("info.pello.android.contentprovider.provider.Students", "students/*/", 2); } /** * we add some data to our "database" */ private void initData () { mCursor = new MatrixCursor(new String[] {"_id","name","description"}); mCursor.addRow(new Object[] {2,"Velasco","A future iPhone developer"}); mCursor.addRow(new Object[] {1,"JR","Android developer"}); mCursor.addRow(new Object[] {3,"Vigor","VB6 developer"}); } /** * we query the database, depending on uriMatcher we can execute * different queries. * The parameters of the query are the same of a SQLite helper query. */ @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Log.d("PELLODEBUG","CP> query " + uri+ " match:" + uriMatcher.match(uri)); switch (uriMatcher.match(uri)) { case 1: Log.d("PELLODEBUG","query to 1. "); break; case 2: Log.d("PELLODEBUG","query to 2. " + uri.getLastPathSegment()); break; default: break; } return mCursor; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { Log.d("PELLODEBUG","CP> " + uri); // Implement this to handle requests to delete one or more rows. throw new UnsupportedOperationException("Not yet implemented"); } @Override public String getType(Uri uri) { Log.d("PELLODEBUG","CP> " + uri); // TODO: Implement this to handle requests for the MIME type of the data // at the given URI. throw new UnsupportedOperationException("Not yet implemented"); } @Override public Uri insert(Uri uri, ContentValues values) { Log.d("PELLODEBUG","CP> insert " + uri); mCursor.addRow(new Object[]{ values.getAsLong("_id"), values.getAsString("name"), values.getAsString("description") }); Uri resultUri = Uri.parse("content://info.pello.android.contentprovider.provider.Students/students"); return resultUri; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { Log.d("PELLODEBUG","CP> " + uri); return 0; } }
En el manifest debe quedar declarado el ContentProvider, y en mi caso en el activity principal he instanciado el ContentProvider y todo ha ido bien a la primera. Se pueden aplicar permisos y restricciones pero eso queda pendiente de probar. OJO, por defecto el permiso es READ WRITE cuando ofreces un ContentProvider.
El cliente
El cliente lo he simplificado al máximo ya que lo que me interesaba era mostrar un par de cosas, cómo hacer una query y como hacer un insert. La query la hace con un parámetro, en el provider lo distingue y lo saca sin más, luego hace la misma query, genera un cursor y lo retorna al cliente para que lo pinte. El cliente al final NO SABE que hay detrás del provider; el recibe un cursor.
En el caso del insert prepara una instancia especial para pasar los valores, una uri (que te puede servir o no) y a correr. El insert retorna un URI que identifica el nuevo registro. Ese uri lo generas con un ID para distinguir el nuevo registro ¿Qué de dónde sale el ID? tú mismo, si tienes un SQLite puedes hacer un autonumérico o como tú te lo montes. Al cliente le da igual.
package info.pello.android.contentproviderclient; import android.net.Uri; import android.os.Bundle; import android.app.Activity; import android.content.ContentValues; import android.database.Cursor; import android.util.Log; import android.view.Menu; import android.view.View; import android.widget.TextView; /** * Example of using a content provider. * @author Pello Xabier Altadill Izura * @greetz to any living real content provider. */ public class MainActivity extends Activity { private TextView textViewResult; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textViewResult = (TextView) findViewById(R.id.textViewResult); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } /** * called when button is clicked * It will try to get data from Content Provider. * @param v */ public void getData(View v) { Log.d("PELLODEBUG","Client> button pressed."); String result = ""; String uriString = "content://info.pello.android.contentprovider.provider.Students/students/1"; Uri uri = Uri.parse(uriString); // We finally make the request to the content provider Cursor cursor = getContentResolver().query( uri, // The content URI of the words table new String[]{""}, "", // The columns to return for each row new String[]{""}, // Selection criteria ""); // We get results in the cursor instance. while (cursor.moveToNext()) { result += cursor.getLong(0) + ", "; result += cursor.getString(1) + ", "; result += cursor.getString(2) + "\n"; } textViewResult.setText(result); } /** * called when second button is clicked * It will try to get data from Content Provider. * @param v */ public void insertData (View v) { Log.d("PELLODEBUG","Client> button pressed."); String result = ""; String uriString = "content://info.pello.android.contentprovider.provider.Students/students/insert"; Uri uri = Uri.parse(uriString); ContentValues contentValues = new ContentValues(); contentValues.put("_id", System.currentTimeMillis()); contentValues.put("name","Example name"); contentValues.put("description","Example Description"); // We finally make the request to the content provider Uri resultUri = getContentResolver().insert( uri, // The content URI contentValues ); Log.d("PELLODEBUG","Result Uri after insert: " + uri.toString()); textViewResult.setText("Result uri created: " + uri.toString()); } }
Conviene ver la línea del manifest donde el cliente pide permiso para usar el content provider.
.... ...
En la red tienes muchos más ejemplos, la mayoría suelen usar el tema de SQLite, en mi caso ńo quería más que mostrar una prueba de concepto.