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);
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.
I try to Unit test my db in Android library, here's how I do it. Providers are definied in Manifest. I run it with RobolectricTestRunner
#RunWith(CustomRobolectricTestRunner.class)
public class FileDatabaseTest {
private ContentResolver contentResolver;
#Before
public void setUp() {
Robolectric.buildContentProvider(FileIndexContentProvider.class);
contentResolver = RuntimeEnvironment.application.getContentResolver();
FileDatabase fileDatabase = mock(FileDatabase.class);
Mockito.doAnswer(new Answer() {
public Object answer(InvocationOnMock invocation) {
return null;
}
}).when(fileDatabase).close();
}
public void insertFileIndex() {
ContentValues contentValues = new ContentValues();
contentValues.put(FileContract.FileIndexTable.FILE_PATH, "path");
contentValues.put(FileContract.FileIndexTable.FILENAME, "filename");
contentValues.put(FileContract.FileIndexTable.STATUS, FileIndexEntry.FileStatus.ACTIVE.toString());
contentValues.put(FileContract.FileIndexTable.CREATE_DATE, System.currentTimeMillis());
contentResolver.insert(FileContract.FileIndexTable.CONTENT_URI, contentValues);
}
#Test
public void testInsertFile() {
insertFileIndex();
Cursor cursor = contentResolver.query(FileContract.FileIndexTable.CONTENT_URI,
FileContract.FileIndexTable.PROJECTION, null, null, null, null);
assertNotNull(cursor);
assertEquals(cursor.getCount(), 1);
cursor.moveToFirst();
FileIndexEntry fileIndexEntry = new FileIndexEntry(cursor);
assertThat(fileIndexEntry.filename, equalTo("filename"));
}
}
Cursor that gets returned by query is null. This happens in all tests that work on db.
public class FileIndexContentProvider extends ContentProviderBase {
private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
private static final int FILE_INDEX = 100;
private static final int FILE_INDEX_ID = 101;
static {
URI_MATCHER.addURI(FileContract.AUTHORITY, FileIndexTable.TABLE_NAME, FILE_INDEX);
URI_MATCHER.addURI(FileContract.AUTHORITY, FileIndexTable.TABLE_NAME + "/*", FILE_INDEX_ID);
}
/**
* Static method to obtain table name from a Uri.
*/
public static String tableForUri(Uri uri) {
final int match = URI_MATCHER.match(uri);
switch (match) {
case FILE_INDEX:
case FILE_INDEX_ID:
return FileIndexTable.TABLE_NAME;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
}
#Override
public boolean onCreate() {
databaseHelper = new FileDatabase(getContext());
return true;
}
#Override
public String getTableForUri(Uri uri) {
return tableForUri(uri);
}
#Override
public String getType(#NonNull Uri uri) {
final int match = URI_MATCHER.match(uri);
switch (match) {
case FILE_INDEX:
return FileIndexTable.CONTENT_TYPE;
case FILE_INDEX_ID:
return FileIndexTable.CONTENT_ITEM_TYPE;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
}
Here is the ContentProvider code. It tested well in an app, but when I started moving it into library issues started.
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
So I am trying to refresh a list fragment when an item is deleted. The way I have it right now restarts the loader which causes a stutter in the UI when the loader is actually restarting.
I am restarting the loader in the listViewLongClick() method.
Here is my code for the adapter and list fragment:
public class EntriesListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Cursor> {
private SimpleCursorAdapter adapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_entries_list, container, false);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
initButton();
fillData();
listViewLongClick()
}
private void listViewLongClick() { assignmentsListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView<?> adapterView, final View view, int i, long l) {
String entryId = ((TextView) view.findViewById(R.id.assignment_id)).getText().toString();
Uri uri = ElicitContract.Assignments.buildAssignmentIdUri(assignmentId);
mContentResolver.delete(uri, null, null);
getLoaderManager().restartLoader(0, null, AssignmentsListFragment.this);
fillData();
return true;
});
}
private void fillData() {
String[] from = new String[]{EntriesContract.EntriesColumns.ENTRIES_TITLE, EntriesContract.EntriesColumns.ENTRIES_DETAIL};
int[] to = new int[]{R.id.entries_title, R.id.entries_description};
getLoaderManager().initLoader(0, null, this);
adapter = new SimpleCursorAdapter(getActivity(), R.layout.custom_entries, null, from, to, 0);
setListAdapter(adapter);
}
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
String[] projection = {EntriesContract.EntriesColumns.ENTRIES_ID, EntriesContract.EntriesColumns.ENTRIES_TITLE, EntriesContract.EntriesColumns.ENTRIES_DETAIL};
CursorLoader cursorLoader = new CursorLoader(getActivity(), EntriesContract.ENTRIES_BASE_CONTENT_URI, projection, null, null, null);
return cursorLoader;
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
adapter.swapCursor(data);
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
adapter.swapCursor(null);
}
#Override
public void onResume() {
super.onResume();
fillData();
}
}
Here is my content provider code:
public class ElicitProvider extends ContentProvider {
private static final String TAG = ElicitProvider.class.getSimpleName();
private EntriesDatabase entriesDatabase; // Get a copy of the database.
private static final UriMatcher sUriMatcher = buildUriMatcher();
private static final int ENTRIES = 1;
private static final int ENTRIES_ID = 2;
private static UriMatcher buildUriMatcher() {
final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
final String authority = EntriesContract.CONTENT_AUTHORITY;
matcher.addURI(authority, "entries", ENTRIES);
matcher.addURI(authority, "entries/*", ENTRIES_ID);
return matcher;
}
#Override
public boolean onCreate() {
entriesDatabase = new EntriesDatabase(getContext()); // Creating a new instance of the Elicit Database.
return true;
}
#Override
public String getType(Uri uri) {
final int match = sUriMatcher.match(uri);
switch (match) {
case ENTRIES:
return EntriesContract.Entries.CONTENT_ENTRIES_TYPE;
case ENTRIES_ID:
return EntriesContract.Entries.CONTENT_ENTRIES_ITEM_TYPE;
default:
throw new IllegalArgumentException("Unknown Uri: " + uri);
}
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
final SQLiteDatabase db = entriesDatabase.getReadableDatabase();
final int match = sUriMatcher.match(uri);
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(EntriesContract.ENTRIES_PATH);
switch (match) {
case ENTRIES:
break;
case ENTRIES_ID:
String id = EntriesContract.Entries.getEntryId(uri);
queryBuilder.appendWhere(BaseColumns._ID + "=" + id);
break;
default:
throw new IllegalArgumentException("Unknown Uri: " + uri);
}
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
return cursor;
}
#Override
public Uri insert(Uri uri, ContentValues contentValues) {
final SQLiteDatabase db = entriesDatabase.getWritableDatabase();
final int match = sUriMatcher.match(uri);
long recordId;
switch (match) {
case ENTRIES:
recordId = db.insertOrThrox(EntriesDatabase.Tables.ENTRIES, null, contentValues);
return EntriesContract.Entxies.buildentryIdUri(String.valueOf(recordId));
default:
throw new IllegalArgumentEception("Unknown Uri: " + uri);
}
}
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
if (uri.equals(EntriesContract.BASE_CONTENT_URI)) {
deleteDatabase();x
return 0;
}
final SQLiteDatabase db = entriesDatabase.getWritableDatabase();
final int match = sUriMatcher.match(uri);
switch (match) {
case ENTRIES_ID:
String id = uri.getLastPathSegment();
String selectionCriteria = BaseColumns._ID + "=" + id + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ")" : "");
return db.delete(EntriesDatabase.Tables.ENTRIES, selectionCriteria, selectionArgs);
default:
throw new IllegalArgumentException("Unknown Uri: " + uri);
}
}
#Override
public int update(Uri uri, ContentValues contentValues, String selection, String[] selectionArgs) {
final SQLiteDatabase db = entriesDatabase.getWritableDatabase();
final int match = sUriMatcher.match(uri);
String selectionCriteria = selection;
switch (match) {
case ENTRIES:
break;
case ENTRIES_ID:
String id = EntriesContract.Entries.getentryId(uri);
selectionCriteria = BaseColumns._ID + "=" + id + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ")" : "");
break;
default:
throw new IllegalArgumentException("Unknown Uri: " + uri);
}
int updateCount = db.update(EntriesDatabase.Tables.ENTRIES, contentValues, selectionCriteria, selectionArgs);
return updateCount;
}
// Delete the instance of the database and create a new one
public void deleteDatabase() {
entriesDatabase.close();
EntriesDatabase.deleteDatabase(getContext());
entriesDatabase = new EntriesDatabase(getContext());
}
}
I am also thinking that this is not the most efficient method of refreshing a list fragment.
I took a look at other similar issues and they said to use entriesListView.notifyDataSetChanged() but I don't know where to put it and how to use it because if I replace this line with getLoaderManager().restartLoader then it gives me a null pointer error.
To summarize, my question is how would I dynamically refresh a listFragment without restarting the loader which I think is less efficient and causes a stutter in the UI.
Thank you to everyone in advance for helping me out!