Tạo ContentProvider
1. Giới thiệu ContentProvider hiểu nôm na là thành phần nằm giữa ứng dụng và data source (hay database), và công việc của nó là quản lí các truy cập đến dữ liệu. Vậy tại sao lại dùng ContentProvider? Một ứng dụng thật tuyệt vời nếu nó có thể chia sẻ dữ liệu của nó cho các ứng dụng khác, và bạn ...
1. Giới thiệu ContentProvider hiểu nôm na là thành phần nằm giữa ứng dụng và data source (hay database), và công việc của nó là quản lí các truy cập đến dữ liệu. Vậy tại sao lại dùng ContentProvider?
- Một ứng dụng thật tuyệt vời nếu nó có thể chia sẻ dữ liệu của nó cho các ứng dụng khác, và bạn có thể làm việc đó rất đơn giản với ContentProvider. Tuy nhiên, nếu bạn không có nhu cầu chia sẻ dữ liệu thì cũng có thể dùng ContentProvider để load dữ liệu lên UI với CursorLoader. Với việc dùng ContentProvider ta có thể thay đổi dữ liệu mà không ảnh hưởng nhiều đến code trên ứng dụng. Để biết thêm về ContentProvider có thể tham khảo thêm tại link sau https://developer.android.com/guide/topics/providers/content-provider-basics.html
2. Tạo một ContentProvider
- Sử dụng AndroidStudio tạo project và đặt tên là ContentProvider
- Thêm 1 lớp java BooksProvider.java
- Thêm đoạn code sau vào file BooksProvider.java
public class BooksProvider extends ContentProvider { public static final String PROVIDER_NAME = "com.example.android.provider.Books"; public static final Uri CONTENT_URI = Uri.parse("content://" + PROVIDER_NAME + "/books"); public static final String _ID = "_id"; public static final String TITLE = "title"; public static final String ISBN = "isbn"; private static final int BOOKS = 1; private static final int BOOK_ID = 2; private static final UriMatcher uriMatcher; static { uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI(PROVIDER_NAME, "books", BOOKS); uriMatcher.addURI(PROVIDER_NAME, "books/#", BOOK_ID); } // For database use private SQLiteDatabase booksDB; private static final String DATABASE_NAME = "books.db"; private static final String DATABASE_TABLE = "titles"; private static final int DATABASE_VERSION = 1; private static final String DATABASE_CREATE = "CREATE TABLE " + DATABASE_TABLE + " (" + _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + TITLE + " TEXT NOT NULL, " + ISBN + " TEXT NOT NULL);"; private static class DatabaseHelper extends SQLiteOpenHelper { public DatabaseHelper (Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase sqLiteDatabase) { sqLiteDatabase.execSQL(DATABASE_CREATE); } @Override public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) { // this for example, shouldn't use in future project sqLiteDatabase.execSQL("DROP TABLE IF EXISTS " + DATABASE_TABLE); onCreate(sqLiteDatabase); } } @Override public int delete(Uri uri, String s, String[] strings) { // s = selection // strings = selectionArgs int count = 0; switch (uriMatcher.match(uri)) { case BOOKS: count = booksDB.delete( DATABASE_TABLE, s, strings); break; case BOOK_ID: String id = uri.getPathSegments().get(1); count = booksDB.delete( DATABASE_TABLE, _ID + " = " + id + (!(TextUtils.isEmpty(s)) ? " AND (" + s + ")" : ""), strings); break; default: throw new IllegalArgumentException("Unknown URI " + uri); } getContext().getContentResolver().notifyChange(uri, null); return count; } @Nullable @Override public String getType(Uri uri) { switch (uriMatcher.match(uri)) { // -- get all books -- case BOOKS: return ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + PROVIDER_NAME; // -- get a particular book -- case BOOK_ID: return ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + PROVIDER_NAME; default: throw new IllegalArgumentException("Unsupported URI: " + uri); } } @Nullable @Override public Uri insert(Uri uri, ContentValues contentValues) { // -- Add a new book -- long rowId = booksDB.insert( DATABASE_TABLE, "", contentValues); // -- if added successfully -- if (rowId > 0) { Uri _uri = ContentUris.withAppendedId(CONTENT_URI, rowId); getContext().getContentResolver().notifyChange(_uri, null); return _uri; } throw new SQLException("Failed to insert row into " + uri); } @Override public boolean onCreate() { Context context = getContext(); DatabaseHelper dbHelper = new DatabaseHelper(context); booksDB = dbHelper.getWritableDatabase(); return !(booksDB == null); } @Nullable @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteQueryBuilder sqlBuilder = new SQLiteQueryBuilder(); sqlBuilder.setTables(DATABASE_TABLE); if (uriMatcher.match(uri) == BOOK_ID) { // --- if getting a particular book --- sqlBuilder.appendWhere( _ID + " = " + uri.getPathSegments().get(1)); } if (sortOrder == null || sortOrder == "") { sortOrder = TITLE; } Cursor c = sqlBuilder.query( booksDB, projection, selection, selectionArgs, null, null, sortOrder); // --- register to watch a content URI for changes --- c.setNotificationUri(getContext().getContentResolver(), uri); return c; } @Override public int update(Uri uri, ContentValues contentValues, String selection, String[] selectionArgs) { int count = 0; switch (uriMatcher.match(uri)) { case BOOKS: count = booksDB.update( DATABASE_TABLE, contentValues, selection, selectionArgs); break; case BOOK_ID: count = booksDB.update( DATABASE_TABLE, contentValues, _ID + " = " + uri.getPathSegments().get(1) + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ")" : ""), selectionArgs); break; default: throw new IllegalArgumentException("Unknown URI " + uri); } getContext().getContentResolver().notifyChange(uri, null); return count; } }
Tiếp theo ta đăng ký BooksProvider trong file AndroidManifest.xml lưu ý: authorities phải trùng với PROVIDER_NAME trong BooksProvider.java
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.maidaidien.contentprovider"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <provider android:authorities="com.example.android.provider.Books" android:name=".BooksProvider"/> </application> </manifest>
3. Giải thích
- getType() : trả về kiểu MIME của dữ liệu theo Uri xác định
- onCreate() : được gọi khi provider được bắt đầu
- query() : nhận request từ client. kết qủa trả về là Cursor object
- insert(): thêm 1 record mới vào content provider
- delete() : xóa 1 record từ content provider
- update(): update lại 1 record trong content provider
đầu tiên ta khai báo hằng số trong BooksProvider class
public static final String PROVIDER_NAME = "com.example.android.provider.Books"; public static final Uri CONTENT_URI = Uri.parse("content://" + PROVIDER_NAME + "/books"); public static final String _ID = "_id"; public static final String TITLE = "title"; public static final String ISBN = "isbn"; private static final int BOOKS = 1; private static final int BOOK_ID = 2; private static final UriMatcher uriMatcher; static { uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI(PROVIDER_NAME, "books", BOOKS); uriMatcher.addURI(PROVIDER_NAME, "books/#", BOOK_ID); }
đoạn code trên ta dùng 1 đối tượng UriMatcher để phân tích uri được truyền vào thuộc kiểu nào ví dụ: uri truyền vào là "content://com.example.android.provider.Books/books" thì sẽ trả về BOOKS còn "content://com.example.android.provider.Books/books/2" sẽ trả về BOOK_ID ContentProvider trên sử dụng SQLiteDatabase để quản lí dữ liệu SQLiteOpenHelper là class dùng để tạo, upgrade database phương thức getType() trả về kiểu dữ liệu của ContentProvider: trả về kiểu ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + PROVIDER_NAME; tương ứng là trả về nhiều sách. Trả về kiểu ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + PROVIDER_NAME; đối với 1 đối tượng sách nhất định nào đó. Trong phương thức onCreate() ta dùng lớp DatabaseHelper để tạo database Trong phương thức query() ta dùng đối tượng lớp SQLiteQueryBuilder để query vào database. Trước tiên ta kiểm tra Uri dùng UriMatcher để biết ta đang query toàn bộ database hay query 1 record
if (uriMatcher.match(uri) == BOOK_ID) { // --- if getting a particular book --- sqlBuilder.appendWhere( _ID + " = " + uri.getPathSegments().get(1)); }
nếu là query 1 record ta sẽ lấy id của sách cần lấy từ uri truyền vào. Phương thức insert() sau khi insert ta sẽ kiểm tra xem có insert thành công không. Nếu thành công ta gọi phương thức notifyChange() của ContentResolver() để thông báo các hàng đã được update. (tương tự cho delete và update) 4. Sử dụng ta thêm đoạn code sau trong file layout activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_awidth="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.example.maidaidien.contentprovider.MainActivity" android:orientation="vertical"> <TextView android:layout_awidth="match_parent" android:layout_height="wrap_content" android:text="ISBN"/> <EditText android:id="@+id/txtISBN" android:layout_awidth="match_parent" android:layout_height="wrap_content"/> <TextView android:layout_awidth="match_parent" android:layout_height="wrap_content" android:text="Title"/> <EditText android:id="@+id/txtTitle" android:layout_awidth="match_parent" android:layout_height="wrap_content"/> <Button android:id="@+id/btnAdd" android:layout_awidth="match_parent" android:layout_height="wrap_content" android:text="Add Title"/> <Button android:id="@+id/btnRetrieve" android:layout_awidth="match_parent" android:layout_height="wrap_content" android:text="Retrieve titles"/> </LinearLayout>
sau đó trong MainActivity.java ta thêm đoạn code sau trong phương thức onCreate()
Button btnAdd = (Button) findViewById(R.id.btnAdd); btnAdd.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // -- add a book -- ContentValues values = new ContentValues(); values.put(BooksProvider.TITLE, ((EditText) findViewById(R.id.txtTitle)).getText().toString()); values.put(BooksProvider.ISBN, ((EditText) findViewById(R.id.txtISBN)).getText().toString()); Uri uri = getContentResolver().insert(BooksProvider.CONTENT_URI, values); Toast.makeText(getBaseContext(), uri.toString(), Toast.LENGTH_SHORT).show(); } }); Button btnRetrieve = (Button) findViewById(R.id.btnRetrieve); btnRetrieve.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // --- retrieve the titles --- Uri allTitles = Uri.parse( "content://" + BooksProvider.PROVIDER_NAME + "/books"); Cursor c = getContentResolver().query( allTitles, null, null, null, BooksProvider.TITLE + " desc"); if (c.moveToFirst()) { do { Log.d("ContentProvider", c.getString(c.getColumnIndex(BooksProvider._ID)) + ", " + c.getString(c.getColumnIndex(BooksProvider.TITLE)) + ", " + c.getString(c.getColumnIndex(BooksProvider.ISBN))); } while (c.moveToNext()); } } });