12/08/2018, 14:32

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

  1. Sử dụng AndroidStudio tạo project và đặt tên là ContentProvider
  2. Thêm 1 lớp java BooksProvider.java
  3. 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

  1. getType() : trả về kiểu MIME của dữ liệu theo Uri xác định
  2. onCreate() : được gọi khi provider được bắt đầu
  3. query() : nhận request từ client. kết qủa trả về là Cursor object
  4. insert(): thêm 1 record mới vào content provider
  5. delete() : xóa 1 record từ content provider
  6. 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());
            }
        }
    });
0