So, I've been used Realm for a while. For now, I have a task to share the login data with my other apps.
Since the login data is stored using Realm. I choose to use Content Provider.
I found an example: https://speakerdeck.com/androhi/realm-with-contentprovider
Unfortunately, I was unable to make it work. This is my Content Provider in app A
static final String[] sColumns = new String[]{
"LoginResultData"
};
public Cursor query(#NonNull Uri uri, #Nullable String[] projection, #Nullable String selection,
#Nullable String[] selectionArgs, #Nullable String sortOrder) {
Realm mRealm = Realm.getDefaultInstance();
RealmQuery<LoginResultData> query = mRealm.where(LoginResultData.class);
LoginResultData result = query.findFirst();
String json = new Gson().toJson(result);
MatrixCursor matrixCursor = new MatrixCursor(sColumns);
Object[] rowData = new Object[]{json};
matrixCursor.addRow(rowData);
return matrixCursor;
}
App B (which need to get the login data) got hang when I
getContentResolver.query(uri, null, null, null, null);
I don't know why but it worked well when I use SQlite. So I'm assuming that Realm doesn't work well with Content Provider smh. Is that true?
If not, please show me a sample to using Content Provider with Realm.
Thanks!
Content Provider works well with RealmDB.
All you need to do is to override the CRUD methods inside the ContentProvider. Please take a look at this content provider class below. 3 things to note:
RealmDB is initialized in the onCreate() method of the
ContentProvider (not the app activity)
You override the CRUD methods(Query, Insert, Delete, Update) in a manner appropriate for RealmDB. Check below samples.
This is all you need to do. In the rest of the code, you will be using native components like recyclerview, adapter, loaders, services. Everywhere you need a query, you will call it with getContentResolver.query(uri, null, null, null, null);
TaskProvider.java
package com.example.rgher.realmtodo.data;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.content.ComponentName;
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.net.Uri;
import android.support.annotation.Nullable;
import android.text.format.DateUtils;
import android.util.Log;
import io.realm.DynamicRealm;
import io.realm.Realm;
import io.realm.RealmConfiguration;
import io.realm.RealmMigration;
import io.realm.RealmResults;
import io.realm.RealmSchema;
import com.example.rgher.realmtodo.data.DatabaseContract.TaskColumns;
public class TaskProvider extends ContentProvider {
private static final String TAG = TaskProvider.class.getSimpleName();
private static final int CLEANUP_JOB_ID = 43;
private static final int TASKS = 100;
private static final int TASKS_WITH_ID = 101;
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
// content://com.example.rgher.realmtodo/tasks
sUriMatcher.addURI(DatabaseContract.CONTENT_AUTHORITY,
DatabaseContract.TABLE_TASKS,
TASKS);
// content://com.example.rgher.realmtodo/tasks/id
sUriMatcher.addURI(DatabaseContract.CONTENT_AUTHORITY,
DatabaseContract.TABLE_TASKS + "/#",
TASKS_WITH_ID);
}
#Override
public boolean onCreate() {
//Innitializing RealmDB
Realm.init(getContext());
RealmConfiguration config = new RealmConfiguration.Builder()
.schemaVersion(1)
.migration(new MyRealmMigration())
.build();
Realm.setDefaultConfiguration(config);
manageCleanupJob();
return true;
}
#Nullable
#Override
public String getType(Uri uri) {
return null; /* Not used */
}
#Nullable
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
int match = sUriMatcher.match(uri);
//Get Realm Instance
Realm realm = Realm.getDefaultInstance();
MatrixCursor myCursor = new MatrixCursor( new String[]{TaskColumns._ID, TaskColumns.DESCRIPTION
, TaskColumns.IS_COMPLETE, TaskColumns.IS_PRIORITY
, TaskColumns.DUE_DATE
});
try {
switch (match) {
//Expected "query all" Uri: content://com.example.rgher.realmtodo/tasks
case TASKS:
RealmResults<RealmTask> tasksRealmResults = realm.where(RealmTask.class).findAll();
for (RealmTask myTask : tasksRealmResults) {
Object[] rowData = new Object[]{myTask.getTask_id(), myTask.getDescription(), myTask.getIs_complete()
, myTask.getIs_priority(), myTask.getDue_date()};
myCursor.addRow(rowData);
Log.v("RealmDB", myTask.toString());
}
break;
//Expected "query one" Uri: content://com.example.rgher.realmtodo/tasks/{id}
case TASKS_WITH_ID:
Integer id = Integer.parseInt(uri.getPathSegments().get(1));
RealmTask myTask = realm.where(RealmTask.class).equalTo("task_id", id).findFirst();
myCursor.addRow(new Object[]{myTask.getTask_id(), myTask.getDescription(), myTask.getIs_complete(), myTask.getIs_priority(), myTask.getDue_date()});
Log.v("RealmDB", myTask.toString());
break;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
myCursor.setNotificationUri(getContext().getContentResolver(), uri);
} finally {
realm.close();
}
return myCursor;
}
#Nullable
#Override
public Uri insert(Uri uri, final ContentValues contentValues) {
//COMPLETE: Expected Uri: content://com.example.rgher.realmtodo/tasks
//final SQLiteDatabase taskDb = mDbHelper.getReadableDatabase();
int match = sUriMatcher.match(uri);
Uri returnUri;
//Get Realm Instance
Realm realm = Realm.getDefaultInstance();
try {
switch (match) {
case TASKS:
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
Number currId = realm.where(RealmTask.class).max(TaskColumns._ID);
Integer nextId = (currId == null) ? 1 : currId.intValue() + 1;
RealmTask myNewTask = realm.createObject(RealmTask.class, nextId);
myNewTask.setDescription(contentValues.get(TaskColumns.DESCRIPTION).toString());
myNewTask.setIs_complete((Integer) contentValues.get(TaskColumns.IS_COMPLETE));
myNewTask.setIs_priority((Integer) contentValues.get(TaskColumns.IS_PRIORITY));
myNewTask.setDue_date((Long) contentValues.get(TaskColumns.DUE_DATE));
}
});
returnUri = ContentUris.withAppendedId(DatabaseContract.CONTENT_URI, '1');
break;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
}finally {
realm.close();
}
return returnUri;
}
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
//Expected Uri: content://com.example.rgher.realmtodo/tasks/{id}
Realm realm = Realm.getDefaultInstance();
int match = sUriMatcher.match(uri);
int nrUpdated = 0;
try {
switch (match) {
case TASKS_WITH_ID:
Integer id = Integer.parseInt(uri.getPathSegments().get(1));
RealmTask myTask = realm.where(RealmTask.class).equalTo("task_id", id).findFirst();
realm.beginTransaction();
myTask.setIs_complete(Integer.parseInt(values.get(TaskColumns.IS_COMPLETE).toString()));
if (values.get(TaskColumns.DUE_DATE) != null) {
myTask.setDue_date(Long.valueOf(values.get(TaskColumns.DUE_DATE).toString()));
}
nrUpdated++;
realm.commitTransaction();
break;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
} finally {
realm.close();
}
if (nrUpdated != 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return nrUpdated;
}
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0;
Realm realm = Realm.getDefaultInstance();
try {
switch (sUriMatcher.match(uri)) {
case TASKS:
selection = (selection == null) ? "1" : selection;
RealmResults<RealmTask> tasksRealmResults = realm.where(RealmTask.class).equalTo(selection, Integer.parseInt(selectionArgs[0])).findAll();
realm.beginTransaction();
tasksRealmResults.deleteAllFromRealm();
count++;
realm.commitTransaction();
break;
case TASKS_WITH_ID:
Integer id = Integer.parseInt(String.valueOf(ContentUris.parseId(uri)));
RealmTask myTask = realm.where(RealmTask.class).equalTo("task_id", id).findFirst();
realm.beginTransaction();
myTask.deleteFromRealm();
count++;
realm.commitTransaction();
break;
default:
throw new IllegalArgumentException("Illegal delete URI");
}
} finally {
realm.close();
}
if (count > 0) {
//Notify observers of the change
getContext().getContentResolver().notifyChange(uri, null);
}
return count;
}
}
// Example of REALM migration
class MyRealmMigration implements RealmMigration {
#Override
public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
RealmSchema schema = realm.getSchema();
if (oldVersion != 0) {
schema.create(DatabaseContract.TABLE_TASKS)
.addField(DatabaseContract.TaskColumns._ID, Integer.class)
.addField(DatabaseContract.TaskColumns.DESCRIPTION, String.class)
.addField(DatabaseContract.TaskColumns.IS_COMPLETE, Integer.class)
.addField(DatabaseContract.TaskColumns.IS_PRIORITY, Integer.class);
oldVersion++;
}
}
}
You can find the full working app here
https://github.com/rgherta/RealmTodo
Good luck
Related
I wanted to calculate the total price of the products in the cart using SQLite. When I set the price in textview using Settext(), it does not return the sum value. This is the gettotalprice method in DBhelper class.
public int getTotalExpenses()
{
int total = 0;
SQLiteDatabase database = this.getReadableDatabase();
Cursor cursor = database.rawQuery("SELECT SUM("+ OrderContract.OrderEntry.COLUMN_PRICE + ") FROM " + OrderContract.OrderEntry.TABLE_NAME, null);
if (cursor.moveToFirst())
{
total = cursor.getInt(4);
}
while (cursor.moveToNext());
return total;
}
OrderProvider class:
package com.example.myapp;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class OrderProvider extends ContentProvider {
// this constant is needed in order to define the path of our modification in the table
public static final int ORDER=100;
public DBHelper mhelper;
public static UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sUriMatcher.addURI(OrderContract.CONTENT_AUTHORITY,OrderContract.PATH,ORDER);
}
#Override
public boolean onCreate() {
mhelper = new DBHelper(getContext());
return true;
}
#Override
public Cursor query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
SQLiteDatabase database = mhelper.getReadableDatabase();
Cursor cursor;
int match = sUriMatcher.match(uri);
switch (match){
case ORDER:
cursor = database.query(OrderContract.OrderEntry.TABLE_NAME, projection, selection, selectionArgs, null,null, sortOrder);
break;
default:
throw new IllegalArgumentException("CANT QUERY");
}
cursor.setNotificationUri(getContext().getContentResolver(),uri);
return cursor;
}
#Override
public String getType(Uri uri) {
return null;
}
#Override
public Uri insert(Uri uri, ContentValues values) {
int match = sUriMatcher.match(uri);
switch (match) {
case ORDER:
return insertCart(uri, values);
default:
throw new IllegalArgumentException("Cant insert data");
}
}
private Uri insertCart(Uri uri, ContentValues values) {
String name = values.getAsString(OrderContract.OrderEntry.COLUMN_NAME);
if(name == null){
throw new IllegalArgumentException("Name is Required");
}
String quantity = values.getAsString(OrderContract.OrderEntry.COLUMN_QUANTITY);
if(quantity == null){
throw new IllegalArgumentException("Quantity is Required");
}
String price = values.getAsString(OrderContract.OrderEntry.COLUMN_PRICE);
if(price == null){
throw new IllegalArgumentException("Price is Required");
}
//insert values into order
SQLiteDatabase database = mhelper.getWritableDatabase();
long id = database.insert(OrderContract.OrderEntry.TABLE_NAME, null, values);
if(id == 0){
return null;
}
getContext().getContentResolver().notifyChange(uri,null);
return ContentUris.withAppendedId(uri,id);
}
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
//delete data once order is made
int rowsDeleted;
SQLiteDatabase database = mhelper.getWritableDatabase();
int match = sUriMatcher.match(uri);
switch (match) {
case ORDER:
rowsDeleted = database.delete(OrderContract.OrderEntry.TABLE_NAME, selection, selectionArgs);
break;
default:
throw new IllegalArgumentException("Cannot delete");
}
if (rowsDeleted!=0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return rowsDeleted;
}
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
}
OrderContract class:
public class OrderContract {
public OrderContract() {
}
//content authority requires package name
public static final String CONTENT_AUTHORITY = "com.example.myapp";
public static final Uri BASE_URI = Uri.parse(("content://" +CONTENT_AUTHORITY));
//same as table name
public static final String PATH = "orders" ;
public static abstract class OrderEntry implements BaseColumns{
public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_URI,PATH);
public static final String TABLE_NAME = "orders" ;
public static final String _ID = BaseColumns._ID ;
public static final String COLUMN_NAME = "name" ;
public static final String COLUMN_QUANTITY = "quantity" ;
public static final String COLUMN_PRICE = "price" ;
}
}
Cart activity:
sofaname=findViewById(R.id.sofaname);
sofaprice=findViewById(R.id.sofaprice);
sofadesc=findViewById(R.id.sofadesc);
plusquantity = findViewById(R.id.addquantity);
minusquantity = findViewById(R.id.subquantity);
quantitynumber = findViewById(R.id.quantity);
addtocart = findViewById(R.id.addtocart);
ImageSlider imageSlider = findViewById(R.id.sofaslider1);
List<SlideModel> slideModels = new ArrayList<>();
slideModels.add(new SlideModel(R.drawable.card14));
slideModels.add(new SlideModel(R.drawable.card6));
slideModels.add(new SlideModel(R.drawable.card7));
imageSlider.setImageList(slideModels,false);
imageSlider.setClickable(false);
DB = new DBHelper(Sofa1.this);
String Name = DB.getProductNamePrice("SELECT F_Name FROM Furniture WHERE F_Type = 'Sofa';");
String Price = DB.getProductNamePrice("SELECT F_Price FROM Furniture WHERE F_Type = 'Sofa';");
String Desc = DB.getProductNamePrice("SELECT F_Description FROM Furniture WHERE F_Type = 'Sofa';");
sofaname.setText(Name);
sofaprice.setText(Price);
sofadesc.setText(Desc);
plusquantity.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(quantity<5){
//sofaprice
int baseprice= Integer.parseInt(sofaprice.getText().toString());
quantity++;
displayquantity();
totalprice = baseprice * quantity;
String setnewprice = (String.valueOf(totalprice));
sofaprice.setText(setnewprice);
}
}
});
minusquantity.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int baseprice=0;
String Price = DB.getProductNamePrice("SELECT F_Price FROM Furniture WHERE F_Type = 'Sofa';");
baseprice = Integer.parseInt(Price);
if(quantity>1) {
quantity--;
displayquantity();
totalprice = baseprice * quantity;
String setnewprice = (String.valueOf(totalprice));
sofaprice.setText(setnewprice);
}
}
});
addtocart.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent =new Intent(Sofa1.this,Cart.class);
startActivity(intent);
// once this button is clicked we want to save our values in the database and send those values
// right away to summary activity where we display the order info
SaveCart();
totalamount = (TextView) findViewById(R.id.total);
int totalAmount = DB.getTotalExpenses();
totalamount.setText(String.valueOf(totalAmount));
}
});
}
private boolean SaveCart() {
String name = sofaname.getText().toString();
String price = sofaprice.getText().toString();
String quantity = quantitynumber.getText().toString();
ContentValues values = new ContentValues();
values.put(OrderContract.OrderEntry.COLUMN_NAME,name);
values.put(OrderContract.OrderEntry.COLUMN_PRICE,price);
values.put(OrderContract.OrderEntry.COLUMN_QUANTITY,quantity);
if(mcurrentcarturi == null){
Uri newUri = getContentResolver().insert(OrderContract.OrderEntry.CONTENT_URI, values);
if(newUri == null){
Toast.makeText(this, "Failed to add to cart", Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(this, "Product added to cart", Toast.LENGTH_SHORT).show();
}
}
hasallrequiredvalues = true;
return hasallrequiredvalues;
}
private void displayquantity() {
quantitynumber.setText(String.valueOf(quantity));
}
#Override
public #NotNull Loader<Cursor> onCreateLoader(int id, Bundle args) {
String[] projection = {OrderContract.OrderEntry._ID,
OrderContract.OrderEntry.COLUMN_NAME,
OrderContract.OrderEntry.COLUMN_PRICE,
OrderContract.OrderEntry.COLUMN_QUANTITY};
return new CursorLoader(this, mcurrentcarturi, projection, null, null, null);
}
#Override
public void onLoadFinished(#NotNull Loader<Cursor> loader, Cursor cursor) {
if(cursor==null || cursor.getCount() < 1){
return;
}
if(cursor.moveToFirst()){
int name = cursor.getColumnIndex(OrderContract.OrderEntry.COLUMN_NAME);
int price = cursor.getColumnIndex(OrderContract.OrderEntry.COLUMN_PRICE);
int quantity = cursor.getColumnIndex(OrderContract.OrderEntry.COLUMN_QUANTITY);
String nameofsofa = cursor.getString(name);
String priceofsofa = cursor.getString(price);
String quantityofsofa = cursor.getString(quantity);
sofaname.setText(nameofsofa);
sofaprice.setText(priceofsofa);
quantitynumber.setText(quantityofsofa);
}
}
#Override
public void onLoaderReset(#NotNull Loader<Cursor> loader) {
sofaname.setText("");
sofaprice.setText("");
quantitynumber.setText("");
}
As your query is effectively
SELECT SUM(price) FROM orders;
Only a single column will be returned, thus using total = cursor.getInt(4); should result in an exception as the column is out of bounds, there will only be 1 column and it's index will be 0.
Thus try changing to use :-
public int getTotalExpenses()
{
int total = 0;
SQLiteDatabase database = this.getReadableDatabase();
Cursor cursor = database.rawQuery("SELECT SUM("+ OrderContract.OrderEntry.COLUMN_PRICE + ") FROM " + OrderContract.OrderEntry.TABLE_NAME, null);
if (cursor.moveToFirst())
{
total = cursor.getInt(0); //<<<<<<<<<< CHANGED
}
//while (cursor.moveToNext()); //<<<<<<<<<< COMMENTED OUT NOT NEEDED
cursor.close(); //<<<<<<<<<< ADDED should always close cursor when finished with them
return total;
}
I am new to Android. I wanted to practice my knowledge about Content Provide, but my app somehow crush and tell me there is an unknown URL content. I try to search for solution but still can't fix the problem...
The logcat message:
2020-05-25 20:10:24.547 15120-15120/com.example.punchinandout E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.punchinandout, PID: 15120
java.lang.IllegalArgumentException: Unknown URL content://com.example.punchinandout.data/time
at android.content.ContentResolver.insert(ContentResolver.java:1535)
at com.example.punchinandout.MainActivity$1.onClick(MainActivity.java:65)
at android.view.View.performClick(View.java:6256)
at android.view.View$PerformClick.run(View.java:24701)
at android.os.Handler.handleCallback(Handler.java:789)
at android.os.Handler.dispatchMessage(Handler.java:98)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6541)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
My provider in Mainifest:
<provider
android:authorities="com.example.punchinandout"
android:name=".data.TimeProvider"
android:exported="false"/>
My Content Provider class:
package com.example.punchinandout.data;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.util.Log;
import com.example.punchinandout.data.TimeContract.TimeEntry;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class TimeProvider extends ContentProvider {
private static final String TAG = TimeProvider.class.toString();
private static final int TIME = 100;
private static final int TIME_ID = 101;
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
//Determine which content URI we have.
static{
sUriMatcher.addURI(TimeContract.CONTENT_AUTHORITY, TimeContract.CONTENT_PATH, TIME);
sUriMatcher.addURI(TimeContract.CONTENT_AUTHORITY, TimeContract.CONTENT_PATH, TIME_ID);
}
private TimeHelper mDbHelper;
#Override
public boolean onCreate() {
mDbHelper = new TimeHelper(getContext());
return true;
}
#Nullable
#Override
public Cursor query(#NonNull Uri uri, #Nullable String[] projection, #Nullable String selection, #Nullable String[] selectionArgs, #Nullable String sortOrder) {
SQLiteDatabase database = mDbHelper.getReadableDatabase();
Cursor cursor;
int match = sUriMatcher.match(uri);
switch (match){
case TIME:
cursor = database.query(TimeEntry.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
break;
case TIME_ID:
selection = TimeEntry._ID + "=?";
selectionArgs = new String[]{String.valueOf(ContentUris.parseId(uri))};
cursor = database.query(TimeEntry.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
break;
default:
throw new IllegalArgumentException("Cannot query with unknown URI: " + uri);
}
cursor.setNotificationUri(getContext().getContentResolver(), uri); //Set notification URI on cursor, so that the cursor will update when the URI is updated.
return cursor;
}
#Nullable
#Override
public Uri insert(#NonNull Uri uri, #Nullable ContentValues values) {
int match = sUriMatcher.match(uri);
switch (match){
case TIME:
return insertTimeRecord(uri, values);
default:
throw new IllegalArgumentException("Insertion is no support for " + uri);
}
}
private Uri insertTimeRecord(Uri uri, ContentValues values){
//Sanity check
if (values.containsKey(TimeEntry.COLUMN_RECORD)){
String time_record = values.getAsString(TimeEntry.COLUMN_RECORD);
if (time_record == null){
throw new IllegalArgumentException("Time record cannot be empty.");
}
}
//If there are no values to update, then don't try to update the database.
if (values.size() == 0){
return null;
}
SQLiteDatabase database = mDbHelper.getWritableDatabase();
long newRowId = database.insert(TimeEntry.TABLE_NAME, null, values);
//Check if the insertion is success.
if (newRowId == -1){
Log.e(TAG, "Insertion fail with " + uri);
return null;
}
getContext().getContentResolver().notifyChange(uri, null); //Notify all listener the data has changed
return ContentUris.withAppendedId(uri, newRowId);
}
#Override
public int update(#NonNull Uri uri, #Nullable ContentValues values, #Nullable String selection, #Nullable String[] selectionArgs) {
int match = sUriMatcher.match(uri);
switch (match){
case TIME:
return updateTimeRecord(uri, values, selection, selectionArgs);
case TIME_ID:
selection = TimeEntry._ID + "=?";
selectionArgs = new String[]{String.valueOf(ContentUris.parseId(uri))};
return updateTimeRecord(uri, values, selection, selectionArgs);
default:
throw new IllegalArgumentException("Update fail with " + uri);
}
}
private int updateTimeRecord(Uri uri, ContentValues values, String selection, String[] selectionArgs){
//Sanity check
if (values.containsKey(TimeEntry.COLUMN_RECORD)){
String time_record = values.getAsString(TimeEntry.COLUMN_RECORD);
if (time_record == null){
return 0; //If the data is not inputted, update nothing in database.
}
}
//If there are no values to update, then don't try to update the database.
if (values.size() == 0){
return 0;
}
SQLiteDatabase database = mDbHelper.getWritableDatabase();
int rowUpdated = database.update(TimeEntry.TABLE_NAME, values, selection, selectionArgs);
if (rowUpdated != 0){
getContext().getContentResolver().notifyChange(uri, null); //Notify all listener the data has changed
}
return rowUpdated;
}
#Override
public int delete(#NonNull Uri uri, #Nullable String selection, #Nullable String[] selectionArgs) {
int match = sUriMatcher.match(uri);
switch (match){
case TIME:
return deleteTimeRecord(uri, selection, selectionArgs);
case TIME_ID:
selection = TimeEntry._ID + "=?";
selectionArgs = new String[]{String.valueOf(ContentUris.parseId(uri))};
return deleteTimeRecord(uri, selection, selectionArgs);
default:
throw new IllegalArgumentException("Delete fail with " + uri);
}
}
private int deleteTimeRecord(Uri uri, String selection, String[] selectionArgs){
SQLiteDatabase database = mDbHelper.getWritableDatabase();
int rowDelected = database.delete(TimeEntry.TABLE_NAME, selection, selectionArgs);
if (rowDelected != 0){
getContext().getContentResolver().notifyChange(uri, null); //Notify all listener the data has changed
}
return rowDelected;
}
#Nullable
#Override
public String getType(#NonNull Uri uri) {
int match = sUriMatcher.match(uri);
switch (match){
case TIME:
return TimeEntry.CONTENT_LIST_TYPE;
case TIME_ID:
return TimeEntry.CONTENT_ITEM_TYPE;
default:
throw new IllegalArgumentException("Unknown URI " + uri + " with match " + match);
}
}
}
My Contract class:
package com.example.punchinandout.data;
import android.content.ContentResolver;
import android.net.Uri;
import android.provider.BaseColumns;
public class TimeContract {
private TimeContract(){};
public static final String CONTENT_AUTHORITY = "com.example.punchinandout.data";
public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
public static final String CONTENT_PATH = "time";
public static final class TimeEntry implements BaseColumns {
public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, CONTENT_PATH);
//MIME type of all records.
public static final String CONTENT_LIST_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + CONTENT_PATH;
//MIME type of single record.
public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + CONTENT_PATH;
public static final String TABLE_NAME = "time";
public static final String _ID = BaseColumns._ID;
public static final String COLUMN_RECORD = "record";
}
}
Can someone help me? :(
I have fixed the problem!!
My provider in Mainifest is that:
<provider
android:authorities="com.example.punchinandout"
android:name=".data.TimeProvider"
android:exported="false"/>
And I change to :
<provider
android:authorities="com.example.punchinandout.data"
android:name=".data.TimeProvider"
android:exported="false"/>
Let the authorities be the same as the authority in Contract class and the problem is fixed!
Sorry for not placed the Mainifest in the question at first time as I had no idea where the problem in my app before.
Anyone knows how to use cursorLoader with DBFlow ? I seen this issue but this is not added to DBFlow.
Thanks.
You can find official docs here or you can implement it the way i have
DBFlow ver used 3
//I have edited my answer & provided easier way for content provider part below
add this to manifest inside application
<provider
android:authorities="com.hashx19.pristinekashmir.mycontentprovider"
android:exported="false"
android:name=".MyContentProvider"/>
Create java file named MyContentProvider & copy below code in it
& replace AUTHORITY ,ENDPOINT, AppDatabase(Your database name) ,TableClassName as per you project.
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.CursorIndexOutOfBoundsException;
import android.net.Uri;
import com.hashx19.pristinekashmir.MySQLiteHelper;
import com.raizlabs.android.dbflow.annotation.ConflictAction;
import com.raizlabs.android.dbflow.config.FlowManager;
import com.raizlabs.android.dbflow.structure.ModelAdapter;
import java.util.Arrays;
import java.util.HashSet;
/**
* Created by Filu on 8/25/2016.
*/
public class MyContentProvider extends ContentProvider {
public static final String AUTHORITY = "com.hashx19.pristinekashmir.mycontentprovider";
private static final String ENDPOOINT = "feeds";
// #ContentUri(path = ENDPOOINT, type = ContentUri.ContentType.VND_MULTIPLE + ENDPOOINT)
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY
+ "/" + ENDPOOINT);
private static final int feeds_CONTENT_URI = 0;
private static final UriMatcher MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
static {
MATCHER.addURI(AUTHORITY, ENDPOOINT, feeds_CONTENT_URI);
}
;
#Override
public final String getType(Uri uri) {
String type = null;
switch(MATCHER.match(uri)) {
case feeds_CONTENT_URI: {
type = "vnd.android.cursor.dir/" +ENDPOINT;
break;
}
default: {
throw new IllegalArgumentException("Unknown URI" + uri);
}
}
return type;
}
#Override
public boolean onCreate() {
return false;
}
#Override
public final Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
android.database.Cursor cursor = null;
switch(MATCHER.match(uri)) {
case feeds_CONTENT_URI: {
cursor = FlowManager.getDatabase("AppDatabase").getWritableDatabase().query("TableClassName", projection, selection, selectionArgs, null, null, sortOrder);
break;
}
}
if (cursor != null) {
cursor.setNotificationUri(getContext().getContentResolver(), uri);
}
return cursor;
}
#Override
public final Uri insert(Uri uri, ContentValues values) {
switch(MATCHER.match(uri)) {
case feeds_CONTENT_URI: {
ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName("AppDatabase", "TableClassName"));
final long id = FlowManager.getDatabase("AppDatabase").getWritableDatabase().insertWithOnConflict("TableClassName", null, values, ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getInsertOnConflictAction()));
getContext().getContentResolver().notifyChange(uri, null);
return ContentUris.withAppendedId(uri, id);
}
default: {
throw new IllegalStateException("Unknown Uri" + uri);
}
}
}
#Override
public final int delete(Uri uri, String selection, String[] selectionArgs) {
switch(MATCHER.match(uri)) {
case feeds_CONTENT_URI: {
long count = FlowManager.getDatabase("AppDatabase").getWritableDatabase().delete("TableClassName", selection, selectionArgs);
if (count > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return (int) count;
}
default: {
throw new IllegalArgumentException("Unknown URI" + uri);
}
}
}
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
switch(MATCHER.match(uri)) {
case feeds_CONTENT_URI: {
ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName("AppDatabase", "TableClassName"));
long count = FlowManager.getDatabase("AppDatabase").getWritableDatabase().updateWithOnConflict("TableClassName", values, selection, selectionArgs, ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getUpdateOnConflictAction()));
if (count > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return (int) count;
}
default: {
throw new IllegalStateException("Unknown Uri" + uri);
}
}
}
}
then when overriding Loader methods do something like this
getLoaderManager().initLoader(1, null, this);
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
String selection, sortOrder;
String[] selectionArgs, projection;
selection = ...;
selectionArgs = ...;
sortOrder = ...;
projection= new String[]{"id","date", "link","title","content","excerpt","author",};
CursorLoader cursorLoader = new CursorLoader(getContext(),MyContentProvider.CONTENT_URI, projection,null,null,null);
return cursorLoader;
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
TableClass post = new TableClass();
while (!cursor.isAfterLast()) {
try{
post.setId(cursor.getInt(cursor.getColumnIndex("id")));
}catch (NullPointerException e){
e.printStackTrace();
}catch (CursorIndexOutOfBoundsException c){
c.printStackTrace();
}
}
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
}
editted
Figured out easier way to implement content provider .
add this to your manifest / or modify this way if you already have added Provider code .
modify your AppDatabase Class as
#ContentProvider(authority = AppDatabase.AUTHORITY,
database = AppDatabase.class,
baseContentUri = AppDatabase.BASE_CONTENT_URI)
#Database(name = AppDatabase.NAME, version = AppDatabase.VERSION)
public class AppDatabase {
public static final String NAME = "AppDatabase"; // we will add the .db extension
public static final int VERSION = 2;
public static final String AUTHORITY = "com.hashx19.pristinekashmir.dbflowcontentprovider";
public static final String BASE_CONTENT_URI = "content://"; }
modify each table you want to use as provider as
#TableEndpoint(name = PostData.ENDPOINT, contentProvider = AppDatabase.class)
#Table(database = AppDatabase.class ,allFields = true ,name = PostData.ENDPOINT)
public class PostData extends BaseModel {
public static final String ENDPOINT = "PostData";
#ContentUri(path = ENDPOINT, type = ContentUri.ContentType.VND_MULTIPLE + ENDPOINT)
public static final Uri CONTENT_URI = Uri.parse(AppDatabase.BASE_CONTENT_URI + AppDatabase.AUTHORITY
+ "/" + ENDPOINT);
#PrimaryKey
public int id;
public String image;
}
For using Content provider as in Cursor Loader use TableName.CONTENT_URI as in this case
CursorLoader cursorLoader = new CursorLoader(getContext(),PostData.CONTENT_URI,projection,null,null,null);
Have a good day. I'm learning Android development so i'm creating a simple CRUD application, right now i have my main activity that has the listview (MarcaActivity) with the next code:
package com.example.crudapp.activity;
import android.app.Activity;
import android.app.LoaderManager;
import android.content.CursorLoader;
import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import com.example.crudapp.MarcaCursorAdapter;
import com.example.crudapp.R;
import com.example.crudapp.cp.MarcaCP;
public class MarcaActivity extends Activity implements
LoaderManager.LoaderCallbacks<Cursor> {
MarcaCursorAdapter crsAdap;
ListView lstMarcas;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_marca);
lstMarcas = (ListView)findViewById(R.id.lstMarcas);
lstMarcas.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
Intent i = new Intent(MarcaActivity.this, MarcaDetailActivity.class);
Uri todoUri = Uri.parse(MarcaCP.CONTENT_URI + "/" + arg3);
i.putExtra("DET", todoUri);
i.putExtra("ACTION", "EXISTING");
startActivity(i);
}
});
getLoaderManager().initLoader(0, null, this);
fillData();
}
protected void onRestoreInstanceState(Bundle savedInstanceState) {
getLoaderManager().restartLoader(0, null, this);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.insertMnu:
Intent i = new Intent(this, MarcaDetailActivity.class);
i.putExtra("ACTION", "NEW");
startActivityForResult(i, 0);
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public boolean onContextItemSelected(MenuItem item) {
/*switch (item.) {
case DELETE_ID:
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item
.getMenuInfo();
Uri uri = Uri.parse(MarcaCP.CONTENT_URI + "/"
+ info.id);
getContentResolver().delete(uri, null, null);
fillData();
return true;
}*/
return super.onContextItemSelected(item);
}
private void fillData() {
crsAdap = new MarcaCursorAdapter(this, R.layout.template_marcas, null ,
new String[]{MarcaCP.Marca.NOMBRE, MarcaCP.Marca.RANKING}, new int[]{R.id.nomMarca, R.id.rankMarca});
lstMarcas.setAdapter(crsAdap);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.marca, menu);
return true;
}
#Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
return new CursorLoader(this, MarcaCP.CONTENT_URI,
new String[]{MarcaCP.Marca.ID, MarcaCP.Marca.NOMBRE, MarcaCP.Marca.CODIGO, MarcaCP.Marca.RANKING}, null, null, null);
}
#Override
public void onLoadFinished(Loader<Cursor> arg0, Cursor arg1) {
crsAdap.swapCursor(arg1);
}
#Override
public void onLoaderReset(Loader<Cursor> arg0) {
crsAdap.swapCursor(null);
} }
Then, i have the activity that manages the detail view where i can update or add items, called MarcaDetailActivity:
package com.example.crudapp.activity;
import android.app.Activity;
import android.content.ContentValues;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import com.example.crudapp.MarcaCursorAdapter;
import com.example.crudapp.R;
import com.example.crudapp.cp.MarcaCP;
public class MarcaDetailActivity extends Activity {
MarcaCursorAdapter crsAdap;
private Integer idReg;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.detalle_marca);
Bundle extras = getIntent().getExtras();
if(extras != null && extras.get("ACTION").equals("EXISTING")) //Invocamos fillData
fillData((Uri)extras.get("DET"));
else
cleanForm();
}
public void cleanForm() {
((Button)findViewById(R.id.todo_edit_button)).setVisibility(View.INVISIBLE);
((Button)findViewById(R.id.todo_insert_button)).setVisibility(View.VISIBLE);
}
public void fillData(Uri uri) {
String[] projection = { MarcaCP.Marca.CODIGO,
MarcaCP.Marca.CLASIFICACION, MarcaCP.Marca.NOMBRE, MarcaCP.Marca.RANKING};
//builder.
Cursor cur = getContentResolver().query(uri, projection, null, null, null);
if(cur != null) {
cur.moveToFirst();
((EditText)findViewById(R.id.detNombre)).setText(cur.getString(cur.getColumnIndex(MarcaCP.Marca.NOMBRE)));
((EditText)findViewById(R.id.detCodigo)).setText(cur.getString(cur.getColumnIndex(MarcaCP.Marca.CODIGO)));
setIdReg(cur.getInt(cur.getColumnIndex(MarcaCP.Marca.ID)));
((EditText)findViewById(R.id.todo_edit_button)).setVisibility(View.VISIBLE);
((EditText)findViewById(R.id.todo_insert_button)).setVisibility(View.INVISIBLE);
String clasif = cur.getString(cur.getColumnIndex(MarcaCP.Marca.CLASIFICACION));
int rank = cur.getInt(cur.getColumnIndex(MarcaCP.Marca.RANKING));
Spinner spRnk = ((Spinner)findViewById(R.id.detRanking));
Spinner spClasf = ((Spinner)findViewById(R.id.detClasificacion));
//Buscamos setear los spinners en sus valores
for(short ps = 0; ps < spRnk.getCount(); ps++) {
String tmpRnk = (String)spRnk.getItemAtPosition(ps);
if(tmpRnk.equalsIgnoreCase("" + rank))
spRnk.setSelection(ps);
}
//Buscamos setear los spinners en sus valores
for(short ps = 0; ps < spClasf.getCount(); ps++) {
String tmpClas = (String)spClasf.getItemAtPosition(ps);
if(tmpClas.equalsIgnoreCase(clasif))
spClasf.setSelection(ps);
}
cur.close();
}
}
private boolean validate() {
boolean res = true;
return res;
}
public void guardar(View view) {
if(validate()) {
ContentValues cv = new ContentValues();
cv.put(MarcaCP.Marca.CLASIFICACION, (String)((Spinner)findViewById(R.id.detClasificacion)).getSelectedItem() );
cv.put(MarcaCP.Marca.CODIGO, ((TextView)findViewById(R.id.detCodigo)).getText().toString() );
cv.put(MarcaCP.Marca.NOMBRE, ((TextView)findViewById(R.id.detNombre)).getText().toString() );
cv.put(MarcaCP.Marca.RANKING, Integer.parseInt("" + ((Spinner)findViewById(R.id.detRanking)).getSelectedItem() ));
getContentResolver().insert(MarcaCP.CONTENT_URI, cv);
}
Toast.makeText(MarcaDetailActivity.this, "Registro registrado",
Toast.LENGTH_SHORT).show();
//Refrescamos el grid y cerramos la actividad
//((MarcaActivity)this.getCallingActivity()).refrescarLista()
//getLoaderManager().restartLoader(0, null, MarcaActivity.class);
this.finish();
}
public void regresar() {
Intent i = new Intent(MarcaDetailActivity.this, MarcaActivity.class);
i.putExtra("REFRESH", true);
startActivity(i);
}
public void actualizar(View view) {
if(validate()) {
ContentValues cv = new ContentValues();
cv.put(MarcaCP.Marca.CLASIFICACION, (String)((Spinner)findViewById(R.id.detClasificacion)).getSelectedItem() );
cv.put(MarcaCP.Marca.CODIGO, (String)((TextView)findViewById(R.id.detCodigo)).getText() );
cv.put(MarcaCP.Marca.NOMBRE, (String)((TextView)findViewById(R.id.detNombre)).getText() );
cv.put(MarcaCP.Marca.ID, getIdReg() );
cv.put(MarcaCP.Marca.RANKING, Integer.parseInt("" + ((Spinner)findViewById(R.id.detRanking)).getSelectedItem() ));
getContentResolver().update(MarcaCP.CONTENT_URI, cv, null, null);
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.marca, menu);
return true;
}
public Integer getIdReg() {
return idReg;
}
public void setIdReg(Integer idReg) {
this.idReg = idReg;
} }
As you can see in the method guardar of MarcaDetailActivity, first i insert the item using my custom content provider, then i use Toast to show a confirm message, and finally finishing the activity, but when i do that, the listview isn't refreshed via the cursorloader. I read here (stackoverflow) and in other pages that i have to call
getLoaderManager().restartLoader(LIST_ID, null, this);
But in all examples that i found all the stuff (inserting data in the database and refreshing the list) is in the same activity, but in this case i have an activity with the list and the second activity that inserts the item, so i think that i have to call restartLoader from my detail activity, but i don't know how to do that or if that's the right way to do this. I was thinking about saving a reference to the list activity in extras when i create the intent to call the detail activity and have a public method that calls restartLoader but i don't know if that is the best approach. What's the best way to refresh the cursor loader from my detail activity?
And there's my content provider, MarcaCP:
package com.example.crudapp.cp;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
import com.example.crudapp.util.ConnHandler;
public class MarcaCP extends ContentProvider {
private static final int TODOS = 10;
private static final int TODO_ID = 20;
public static final Uri CONTENT_URI = Uri.parse("content://provider.marcacp/marcas");
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sURIMatcher.addURI("provider.marcacp", "marcas", TODOS);
sURIMatcher.addURI("provider.marcacp", "marcas" + "/#", TODO_ID);
}
public static class Marca {
public static final String TABLA = "MARCA";
public static final String ID = "_id";
public static final String CODIGO = "codigo";
public static final String NOMBRE = "nombre";
public static final String CLASIFICACION = "clasificacion";
public static final String RANKING = "ranking";
}
private ConnHandler conn;
SQLiteDatabase dbObj;
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int uriType = sURIMatcher.match(uri);
int rowsDeleted = 0;
switch (uriType) {
case TODOS:
rowsDeleted = dbObj.delete(Marca.TABLA, selection, selectionArgs);
break;
case TODO_ID:
String id = uri.getLastPathSegment();
if (TextUtils.isEmpty(selection)) {
rowsDeleted = dbObj.delete(Marca.TABLA,
Marca.ID + " = " + id,
null);
} else {
rowsDeleted = dbObj.delete(Marca.TABLA,
Marca.ID + " =" + id
+ " and " + selection,
selectionArgs);
}
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsDeleted;
}
#Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
return null;
}
#Override
public Uri insert(Uri uri, ContentValues values) {
int uriType = sURIMatcher.match(uri);
long id = 0;
switch (uriType) {
case TODOS:
id = dbObj.insert(Marca.TABLA, null, values);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return Uri.parse("marca/" + id);
}
#Override
public boolean onCreate() {
conn = new ConnHandler(getContext());
dbObj = conn.getDb();
return true;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// Uisng SQLiteQueryBuilder instead of query() method
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
checkColumns(projection);
queryBuilder.setTables(Marca.TABLA);
int uriType = sURIMatcher.match(uri);
switch (uriType) {
case TODOS:
break;
case TODO_ID:
// Digamos para ver el detalle de un registro
queryBuilder.appendWhere(Marca.ID + " = "
+ uri.getLastPathSegment());
break;
default:
throw new IllegalArgumentException("Unknown URIx: " + uri);
}
Cursor cur = queryBuilder.query(dbObj, projection, selection,
selectionArgs, null, null, sortOrder);
return cur;
}
#Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
int uriType = sURIMatcher.match(uri);
int rowsUpdated = 0;
switch (uriType) {
case TODOS:
rowsUpdated = dbObj.update(Marca.TABLA,
values,
selection,
selectionArgs);
break;
case TODO_ID:
String id = uri.getLastPathSegment();
if (TextUtils.isEmpty(selection)) {
rowsUpdated = dbObj.update(Marca.TABLA,
values,
Marca.ID + " =" + id,
null);
} else {
rowsUpdated = dbObj.update(Marca.TABLA,
values,
Marca.ID + " =" + id
+ " and "
+ selection,
selectionArgs);
}
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsUpdated;
}
private void checkColumns(String[] projection) {
/*String[] available = { TodoTable.COLUMN_CATEGORY,
TodoTable.COLUMN_SUMMARY, TodoTable.COLUMN_DESCRIPTION,
TodoTable.COLUMN_ID };
if (projection != null) {
HashSet<String> requestedColumns = new HashSet<String>(Arrays.asList(projection));
HashSet<String> availableColumns = new HashSet<String>(Arrays.asList(available));
// Check if all columns which are requested are available
if (!availableColumns.containsAll(requestedColumns)) {
throw new IllegalArgumentException("Unknown columns in projection");
}
}*/
} }
It's already registered in the AndroidManifest.xml:
<provider android:name=".cp.MarcaCP" android:authorities="provider.marcacp" />
Regards.
Try getContentResolver().notifyChange(MarcaCP.CONTENT_URI, null, false) from the second activity before finishing it.
It's a bit late to answer this.
since in your ContentProvider, you have
getContext().getContentResolver().notifyChange
which is great and correct!
so there is no need to called
getContentResolver().notifyChange(MarcaCP.CONTENT_URI, null, false) from the second activity before finishing it.
the problem is that instead of using getLoaderManager().initLoader
which is deprecated and not life-cycled aware.
try use
LoaderManager.getInstance(this).initLoader
this: is a class that maintains its own android.arch.lifecycle.Lifecycle and android.arch.lifecycle.ViewModelStore. For instance, androidx.fragment.app.FragmentActivity or androidx.fragment.app.Fragment.
so that when you pop back to the first activity and OnResume() called, there is no need to restart the loader again.
P.s. also a great reminder. Do not close any cursor related to the onLoadFinished(#NonNull Loader<Cursor> loader, Cursor cursor)
if doing so will also cause the loader won't update itself after data changed in DB.
I want to implement a search method in my application. I have a database, I want to search in that database using a quick search box. Please help me to achieve this task.
Three files are relevant, DataProvider, DataIndex, changes to the AndroidManifest.
In my case the data objects I want to look for in my database are 'Locations' data objects, hence the name of my classes, but you can apply it for your logic without problem.
LocationProvider.java:
package com.myapp.android.search;
import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import com.myapp.android.MyApp;
import com.myapp.android.model.Location;
import java.util.ArrayList;
/**
* Provides search suggestions for a list of words and their definitions.
*/
public class LocationProvider extends ContentProvider {
public static String AUTHORITY = "myapp_locations";
private static final int SEARCH_SUGGEST = 0;
private static final int SHORTCUT_REFRESH = 1;
private static final UriMatcher sURIMatcher = buildUriMatcher();
/**
* The columns we'll include in our search suggestions. There are others that could be used
* to further customize the suggestions, see the docs in {#link SearchManager} for the details
* on additional columns that are supported.
*/
private static final String[] COLUMNS = {
"_id", // must include this column
SearchManager.SUGGEST_COLUMN_TEXT_1,
SearchManager.SUGGEST_COLUMN_TEXT_2,
SearchManager.SUGGEST_COLUMN_INTENT_DATA,
};
/**
* Sets up a uri matcher for search suggestion and shortcut refresh queries.
*/
private static UriMatcher buildUriMatcher() {
UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, SEARCH_SUGGEST);
matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", SEARCH_SUGGEST);
matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_SHORTCUT, SHORTCUT_REFRESH);
matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_SHORTCUT + "/*", SHORTCUT_REFRESH);
return matcher;
}
#Override
public boolean onCreate() {
Resources resources = getContext().getResources();
// LocationIndex.getInstance(this.getContext()).ensureLoaded(resources);
return true;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
if (!TextUtils.isEmpty(selection)) {
throw new IllegalArgumentException("selection not allowed for " + uri);
}
if (selectionArgs != null && selectionArgs.length != 0) {
throw new IllegalArgumentException("selectionArgs not allowed for " + uri);
}
if (!TextUtils.isEmpty(sortOrder)) {
throw new IllegalArgumentException("sortOrder not allowed for " + uri);
}
switch (sURIMatcher.match(uri)) {
case SEARCH_SUGGEST:
String query = null;
if (uri.getPathSegments().size() > 1) {
query = uri.getLastPathSegment().toLowerCase();
}
return getSuggestions(query, projection);
case SHORTCUT_REFRESH:
String shortcutId = null;
if (uri.getPathSegments().size() > 1) {
shortcutId = uri.getLastPathSegment();
}
return refreshShortcut(shortcutId, projection);
default:
throw new IllegalArgumentException("Unknown URL " + uri);
}
}
private Cursor getSuggestions(String query, String[] projection) {
String processedQuery = query == null ? "" : query.toLowerCase();
ArrayList<Location> words = LocationIndex.getInstance().getMatches(processedQuery);
MatrixCursor cursor = new MatrixCursor(COLUMNS);
long id = 0;
for (Location word : words) {
cursor.addRow(columnValuesOfWord(id++, word));
}
return cursor;
}
private Object[] columnValuesOfWord(long id, Location loc) {
return new Object[] {
id, // _id
loc.getTitle(), // text1
loc.getDescription(), // text2
loc.getTitle(), // intent_data (included when clicking on item)
};
}
/**
* Note: this is unused as is, but if we included
* {#link SearchManager#SUGGEST_COLUMN_SHORTCUT_ID} as a column in our results, we
* could expect to receive refresh queries on this uri for the id provided, in which case we
* would return a cursor with a single item representing the refreshed suggestion data.
*/
private Cursor refreshShortcut(String shortcutId, String[] projection) {
return null;
}
/**
* All queries for this provider are for the search suggestion and shortcut refresh mime type.
*/
public String getType(Uri uri) {
switch (sURIMatcher.match(uri)) {
case SEARCH_SUGGEST:
return SearchManager.SUGGEST_MIME_TYPE;
case SHORTCUT_REFRESH:
return SearchManager.SHORTCUT_MIME_TYPE;
default:
throw new IllegalArgumentException("Unknown URL " + uri);
}
}
public Uri insert(Uri uri, ContentValues values) {
throw new UnsupportedOperationException();
}
public int delete(Uri uri, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException();
}
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException();
}
}
LocationIndex.java
package com.myapp.android.search;
import com.myapp.android.model.Location;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
public class LocationIndex {
private boolean mLoaded = false;
private static final LocationIndex sInstance = new LocationIndex();
private ArrayList<Location> locations;
private final ConcurrentHashMap<String, ArrayList<Location>> mDict = new ConcurrentHashMap<String, ArrayList<Location>>();
public static LocationIndex getInstance() {
return sInstance;
}
public synchronized void loadWords(ArrayList<Location> locations) { //throws IOException, Resources resources
if (mLoaded) return;
this.locations = locations;
for (Iterator<Location> iter=locations.iterator();iter.hasNext();) {
Location loc = iter.next();
if (loc!=null) addLocation(loc);
}
mLoaded = true;
}
public ArrayList<Location> getMatches(String query) {
ArrayList<Location> list = mDict.get(query);
return list == null ? new ArrayList<Location>() : list;
}
private void addLocation(Location loc) {
final int len = loc.getTitle().length();
for (int i = 0; i < len; i++) {
final String prefix = loc.getTitle().substring(0, len - i);
addMatch(prefix, loc);
}
}
private void addMatch(String query, Location loc) {
ArrayList<Location> matches = mDict.get(query);
if (matches == null) {
matches = new ArrayList<Location>();
mDict.put(query.toLowerCase(), matches);
}
matches.add(loc);
}
public ConcurrentHashMap<String, ArrayList<Location>> getmDict() {
return mDict;
}
}
AndroidManifest.xml
add the following to your manifest...
<!-- Provides search suggestions for words and their definitions. -->
<provider android:name=".search.LocationProvider"
android:authorities="myapp_locations"
android:syncable="false"/>
<provider android:name=".content.LocalFileContentProvider"
android:authorities="com.myapp.android.localfile"
android:syncable="false"/>
and the intent filter in the activities you want to activate the search for (maybe all):
<intent-filter>
<action android:name="android.intent.action.SEARCH"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>