guys, I'm trying to add a new column (KEY_EXAMPLE) in my "searchable dictionary" code example
The KEY_EXAMPLE should be the third column. But it doesn't work....
Here is my code:
DictionaryDatabase.Java
public class DictionaryDatabase {
private static final String TAG = "DictionaryDatabase";
//The columns we'll include in the dictionary table
public static final String KEY_WORD = SearchManager.SUGGEST_COLUMN_TEXT_1;
public static final String KEY_DEFINITION = SearchManager.SUGGEST_COLUMN_TEXT_2;
public static final String KEY_EXAMPLE = SearchManager.SUGGEST_COLUMN_TEXT_1;
private static final String DATABASE_NAME = "dictionary";
private static final String FTS_VIRTUAL_TABLE = "FTSdictionary";
private static final int DATABASE_VERSION = 2;
private final DictionaryOpenHelper mDatabaseOpenHelper;
private static final HashMap<String,String> mColumnMap = buildColumnMap();
/**
* Constructor
* #param context The Context within which to work, used to create the DB
*/
public DictionaryDatabase(Context context) {
mDatabaseOpenHelper = new DictionaryOpenHelper(context);
}
/**
* Builds a map for all columns that may be requested, which will be given to the
* SQLiteQueryBuilder. This is a good way to define aliases for column names, but must include
* all columns, even if the value is the key. This allows the ContentProvider to request
* columns w/o the need to know real column names and create the alias itself.
*/
private static HashMap<String,String> buildColumnMap() {
HashMap<String,String> map = new HashMap<String,String>();
map.put(KEY_WORD, KEY_WORD);
map.put(KEY_DEFINITION, KEY_DEFINITION);
map.put(KEY_EXAMPLE, KEY_EXAMPLE);
map.put(BaseColumns._ID, "rowid AS " +
BaseColumns._ID);
map.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID, "rowid AS " +
SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
map.put(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID, "rowid AS " +
SearchManager.SUGGEST_COLUMN_SHORTCUT_ID);
return map;
}
/**
* Returns a Cursor positioned at the word specified by rowId
*
* #param rowId id of word to retrieve
* #param columns The columns to include, if null then all are included
* #return Cursor positioned to matching word, or null if not found.
*/
public Cursor getWord(String rowId, String[] columns) {
String selection = "rowid = ?";
String[] selectionArgs = new String[] {rowId};
return query(selection, selectionArgs, columns);
/* This builds a query that looks like:
* SELECT <columns> FROM <table> WHERE rowid = <rowId>
*/
}
/**
* Returns a Cursor over all words that match the given query
*
* #param query The string to search for
* #param columns The columns to include, if null then all are included
* #return Cursor over all words that match, or null if none found.
*/
public Cursor getWordMatches(String query, String[] columns) {
String selection = KEY_WORD + " MATCH ?";
String[] selectionArgs = new String[] {query+"*"};
return query(selection, selectionArgs, columns);
/* This builds a query that looks like:
* SELECT <columns> FROM <table> WHERE <KEY_WORD> MATCH 'query*'
* which is an FTS3 search for the query text (plus a wildcard) inside the word column.
*
* - "rowid" is the unique id for all rows but we need this value for the "_id" column in
* order for the Adapters to work, so the columns need to make "_id" an alias for "rowid"
* - "rowid" also needs to be used by the SUGGEST_COLUMN_INTENT_DATA alias in order
* for suggestions to carry the proper intent data.
* These aliases are defined in the DictionaryProvider when queries are made.
* - This can be revised to also search the definition text with FTS3 by changing
* the selection clause to use FTS_VIRTUAL_TABLE instead of KEY_WORD (to search across
* the entire table, but sorting the relevance could be difficult.
*/
}
/**
* Performs a database query.
* #param selection The selection clause
* #param selectionArgs Selection arguments for "?" components in the selection
* #param columns The columns to return
* #return A Cursor over all rows matching the query
*/
private Cursor query(String selection, String[] selectionArgs, String[] columns) {
/* The SQLiteBuilder provides a map for all possible columns requested to
* actual columns in the database, creating a simple column alias mechanism
* by which the ContentProvider does not need to know the real column names
*/
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
builder.setTables(FTS_VIRTUAL_TABLE);
builder.setProjectionMap(mColumnMap);
Cursor cursor = builder.query(mDatabaseOpenHelper.getReadableDatabase(),
columns, selection, selectionArgs, null, null, null);
if (cursor == null) {
return null;
} else if (!cursor.moveToFirst()) {
cursor.close();
return null;
}
return cursor;
}
/**
* This creates/opens the database.
*/
private static class DictionaryOpenHelper extends SQLiteOpenHelper {
private final Context mHelperContext;
private SQLiteDatabase mDatabase;
/* Note that FTS3 does not support column constraints and thus, you cannot
* declare a primary key. However, "rowid" is automatically used as a unique
* identifier, so when making requests, we will use "_id" as an alias for "rowid"
*/
private static final String FTS_TABLE_CREATE =
"CREATE VIRTUAL TABLE " + FTS_VIRTUAL_TABLE +
" USING fts3 (" +
KEY_WORD + ", " +
KEY_DEFINITION + ", " +
KEY_EXAMPLE + " );";
DictionaryOpenHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
mHelperContext = context;
}
#Override
public void onCreate(SQLiteDatabase db) {
mDatabase = db;
mDatabase.execSQL(FTS_TABLE_CREATE);
loadDictionary();
}
/**
* Starts a thread to load the database table with words
*/
private void loadDictionary() {
new Thread(new Runnable() {
public void run() {
try {
loadWords();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).start();
}
private void loadWords() throws IOException {
Log.d(TAG, "Loading words...");
final Resources resources = mHelperContext.getResources();
InputStream inputStream = resources.openRawResource(R.raw.definitions);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
String line;
while ((line = reader.readLine()) != null) {
String[] strings = TextUtils.split(line, "//");
if (strings.length < 2) continue;
long id = addWord(strings[0].trim(), strings[1].trim(), strings[2].trim());
if (id < 0) {
Log.e(TAG, "unable to add word: " + strings[0].trim());
}
}
} finally {
reader.close();
}
Log.d(TAG, "DONE loading words.");
}
/**
* Add a word to the dictionary.
* #return rowId or -1 if failed
*/
public long addWord(String word, String definition, String example) {
ContentValues initialValues = new ContentValues();
initialValues.put(KEY_WORD, word);
initialValues.put(KEY_DEFINITION, definition);
initialValues.put(KEY_EXAMPLE, example);
return mDatabase.insert(FTS_VIRTUAL_TABLE, null, initialValues);
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+ newVersion + ", which will destroy all old data");
db.execSQL("DROP TABLE IF EXISTS " + FTS_VIRTUAL_TABLE);
onCreate(db);
}
}
}
DictionaryProvider.java
public class DictionaryProvider extends ContentProvider {
String TAG = "DictionaryProvider";
public static String AUTHORITY = "com.example.android.searchabledict.DictionaryProvider";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/dictionary");
// MIME types used for searching words or looking up a single definition
public static final String WORDS_MIME_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE +
"/vnd.example.android.searchabledict";
public static final String DEFINITION_MIME_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE +
"/vnd.example.android.searchabledict";
private DictionaryDatabase mDictionary;
// UriMatcher stuff
private static final int SEARCH_WORDS = 0;
private static final int GET_WORD = 1;
private static final int SEARCH_SUGGEST = 2;
private static final int REFRESH_SHORTCUT = 3;
private static final UriMatcher sURIMatcher = buildUriMatcher();
/**
* Builds up a UriMatcher for search suggestion and shortcut refresh queries.
*/
private static UriMatcher buildUriMatcher() {
UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
// to get definitions...
matcher.addURI(AUTHORITY, "dictionary", SEARCH_WORDS);
matcher.addURI(AUTHORITY, "dictionary/#", GET_WORD);
// to get suggestions...
matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, SEARCH_SUGGEST);
matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", SEARCH_SUGGEST);
/* The following are unused in this implementation, but if we include
* {#link SearchManager#SUGGEST_COLUMN_SHORTCUT_ID} as a column in our suggestions table, we
* could expect to receive refresh queries when a shortcutted suggestion is displayed in
* Quick Search Box, in which case, the following Uris would be provided and we
* would return a cursor with a single item representing the refreshed suggestion data.
*/
matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_SHORTCUT, REFRESH_SHORTCUT);
matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_SHORTCUT + "/*", REFRESH_SHORTCUT);
return matcher;
}
#Override
public boolean onCreate() {
mDictionary = new DictionaryDatabase(getContext());
return true;
}
/**
* Handles all the dictionary searches and suggestion queries from the Search Manager.
* When requesting a specific word, the uri alone is required.
* When searching all of the dictionary for matches, the selectionArgs argument must carry
* the search query as the first element.
* All other arguments are ignored.
*/
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
// Use the UriMatcher to see what kind of query we have and format the db query accordingly
switch (sURIMatcher.match(uri)) {
case SEARCH_SUGGEST:
if (selectionArgs == null) {
throw new IllegalArgumentException(
"selectionArgs must be provided for the Uri: " + uri);
}
return getSuggestions(selectionArgs[0]);
case SEARCH_WORDS:
if (selectionArgs == null) {
throw new IllegalArgumentException(
"selectionArgs must be provided for the Uri: " + uri);
}
return search(selectionArgs[0]);
case GET_WORD:
return getWord(uri);
case REFRESH_SHORTCUT:
return refreshShortcut(uri);
default:
throw new IllegalArgumentException("Unknown Uri: " + uri);
}
}
private Cursor getSuggestions(String query) {
query = query.toLowerCase();
String[] columns = new String[] {
BaseColumns._ID,
DictionaryDatabase.KEY_WORD,
/* SearchManager.SUGGEST_COLUMN_SHORTCUT_ID,
(only if you want to refresh shortcuts) */
SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID};
return mDictionary.getWordMatches(query, columns);
}
private Cursor search(String query) {
query = query.toLowerCase();
String[] columns = new String[] {
BaseColumns._ID,
DictionaryDatabase.KEY_WORD,
DictionaryDatabase.KEY_DEFINITION,
DictionaryDatabase.KEY_EXAMPLE};
return mDictionary.getWordMatches(query, columns);
}
private Cursor getWord(Uri uri) {
String rowId = uri.getLastPathSegment();
String[] columns = new String[] {
DictionaryDatabase.KEY_WORD,
DictionaryDatabase.KEY_DEFINITION,
DictionaryDatabase.KEY_EXAMPLE};
return mDictionary.getWord(rowId, columns);
}
private Cursor refreshShortcut(Uri uri) {
/* This won't be called with the current implementation, but if we include
* {#link SearchManager#SUGGEST_COLUMN_SHORTCUT_ID} as a column in our suggestions table, we
* could expect to receive refresh queries when a shortcutted suggestion is displayed in
* Quick Search Box. In which case, this method will query the table for the specific
* word, using the given item Uri and provide all the columns originally provided with the
* suggestion query.
*/
String rowId = uri.getLastPathSegment();
String[] columns = new String[] {
BaseColumns._ID,
DictionaryDatabase.KEY_WORD,
DictionaryDatabase.KEY_DEFINITION,
DictionaryDatabase.KEY_EXAMPLE,
SearchManager.SUGGEST_COLUMN_SHORTCUT_ID,
SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID};
return mDictionary.getWord(rowId, columns);
}
/**
* This method is required in order to query the supported types.
* It's also useful in our own query() method to determine the type of Uri received.
*/
#Override
public String getType(Uri uri) {
switch (sURIMatcher.match(uri)) {
case SEARCH_WORDS:
return WORDS_MIME_TYPE;
case GET_WORD:
return DEFINITION_MIME_TYPE;
case SEARCH_SUGGEST:
return SearchManager.SUGGEST_MIME_TYPE;
case REFRESH_SHORTCUT:
return SearchManager.SHORTCUT_MIME_TYPE;
default:
throw new IllegalArgumentException("Unknown URL " + uri);
}
}
// Other required implementations...
#Override
public Uri insert(Uri uri, ContentValues values) {
throw new UnsupportedOperationException();
}
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException();
}
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException();
}
}
SearchableDictionary.java
public class SearchableDictionary extends Activity {
private TextView mTextView;
private ListView mListView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mTextView = (TextView) findViewById(R.id.text);
mListView = (ListView) findViewById(R.id.list);
handleIntent(getIntent());
}
#Override
protected void onNewIntent(Intent intent) {
// Because this activity has set launchMode="singleTop", the system calls this method
// to deliver the intent if this activity is currently the foreground activity when
// invoked again (when the user executes a search from this activity, we don't create
// a new instance of this activity, so the system delivers the search intent here)
handleIntent(intent);
}
private void handleIntent(Intent intent) {
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
// handles a click on a search suggestion; launches activity to show word
Intent wordIntent = new Intent(this, WordActivity.class);
wordIntent.setData(intent.getData());
startActivity(wordIntent);
} else if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
// handles a search query
String query = intent.getStringExtra(SearchManager.QUERY);
showResults(query);
}
}
/**
* Searches the dictionary and displays results for the given query.
* #param query The search query
*/
private void showResults(String query) {
Cursor cursor = managedQuery(DictionaryProvider.CONTENT_URI, null, null,
new String[] {query}, null);
if (cursor == null) {
// There are no results
mTextView.setText(getString(R.string.no_results, new Object[] {query}));
} else {
// Display the number of results
int count = cursor.getCount();
String countString = getResources().getQuantityString(R.plurals.search_results,
count, new Object[] {count, query});
mTextView.setText(countString);
// Specify the columns we want to display in the result
String[] from = new String[] { DictionaryDatabase.KEY_WORD,
DictionaryDatabase.KEY_DEFINITION,
DictionaryDatabase.KEY_EXAMPLE};
// Specify the corresponding layout elements where we want the columns to go
int[] to = new int[] { R.id.word,
R.id.definition,
R.id.example};
// Create a simple cursor adapter for the definitions and apply them to the ListView
SimpleCursorAdapter words = new SimpleCursorAdapter(this,
R.layout.result, cursor, from, to);
mListView.setAdapter(words);
// Define the on-click listener for the list items
mListView.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// Build the Intent used to open WordActivity with a specific word Uri
Intent wordIntent = new Intent(getApplicationContext(), WordActivity.class);
Uri data = Uri.withAppendedPath(DictionaryProvider.CONTENT_URI,
String.valueOf(id));
wordIntent.setData(data);
startActivity(wordIntent);
}
});
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.options_menu, menu);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchView searchView = (SearchView) menu.findItem(R.id.search).getActionView();
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
searchView.setIconifiedByDefault(false);
}
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.search:
onSearchRequested();
return true;
default:
return false;
}
}
}
WordActivity.java
public class WordActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.word);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
ActionBar actionBar = getActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
}
Uri uri = getIntent().getData();
Cursor cursor = managedQuery(uri, null, null, null, null);
if (cursor == null) {
finish();
} else {
cursor.moveToFirst();
TextView word = (TextView) findViewById(R.id.word);
TextView definition = (TextView) findViewById(R.id.definition);
TextView example = (TextView) findViewById(R.id.example);
int wIndex = cursor.getColumnIndexOrThrow(DictionaryDatabase.KEY_WORD);
int dIndex = cursor.getColumnIndexOrThrow(DictionaryDatabase.KEY_DEFINITION);
int eIndex = cursor.getColumnIndexOrThrow(DictionaryDatabase.KEY_EXAMPLE);
word.setText(cursor.getString(wIndex));
definition.setText(cursor.getString(dIndex));
example.setText(cursor.getString(eIndex));
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.options_menu, menu);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchView searchView = (SearchView) menu.findItem(R.id.search).getActionView();
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
searchView.setIconifiedByDefault(false);
}
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.search:
onSearchRequested();
return true;
case android.R.id.home:
Intent intent = new Intent(this, SearchableDictionary.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
return true;
default:
return false;
}
}
}
Close inspection reveals that you have the same string value assigned to 1 and 3
public static final String KEY_WORD = SearchManager.SUGGEST_COLUMN_TEXT_1;
public static final String KEY_DEFINITION = SearchManager.SUGGEST_COLUMN_TEXT_2;
public static final String KEY_EXAMPLE = SearchManager.SUGGEST_COLUMN_TEXT_1;
it is a limitation that you can have only two items in the search suggestion provided by android
refer section
The biggest problem with search suggestions
in http://www.grokkingandroid.com/android-tutorial-adding-suggestions-to-search/
Related
I am following the data storage lessons from udacity. And in their app, you insert an animal's details such as name, breed, gender and weight. In the CatalogActivity I insert some dummy data,and read them from the provider through the ContentResolver. And in the EditorActivity I insert the data manually.
Basically the query method returns a Cursor object that holds the rows of our table. The ContentResolver passes the query method to the PetProvider. Then the PetProvider performs two operations for now. Query and Insert. This is the code of my provider.
PetProvider
/**
* {#link ContentProvider} for Pets app.
*/
public class PetProvider extends ContentProvider {
private PetHelper mHelper;
/** Tag for the log messages */
public static final String LOG_TAG = PetProvider.class.getSimpleName();
/**
* URI matcher code for the content URI for the pets table
*/
public static final int PETS = 100;
/**
* URI matcher code for the content URI for a single pet in the pets table
*/
public static final int PET_ID = 101;
/** URI matcher object to match a context URI to a corresponding code.
* The input passed into the constructor represents the code to return for the root URI.
* It's common to use NO_MATCH as the input for this case.
*/
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// Static initializer. This is run the first time anything is called from this class.
static{
// The calls to addURI() go here, for all of the content URI patterns that the provider
// should recognize. All paths added to the UriMatcher have a corresponding code to return
// when a match is found.
// The content URI of the form "content://com.example.android.pets/pets" will map to the
// integer code {#link #PETS}. This URI is used to provide access to MULTIPLE rows
// of the pets table.
sUriMatcher.addURI(PetContract.CONTENT_AUTHORITY,PetContract.PATH_PETS,PETS);
// The content URI of the form "content://com.example.android.pets/pets/#" will map to the
// integer code {#link #PETS_ID}. This URI is used to provide access to ONE single row
// of the pets table.
// In this case, the "#" wildcard is used where "#" can be substituted for an integer.
// For example, "content://com.example.android.pets/pets/3" matches, but
// "content://com.example.android.pets/pets" (without a number at the end) doesn't match.
sUriMatcher.addURI(PetContract.CONTENT_AUTHORITY, PetContract.PATH_PETS + "/#", PET_ID);
}
/**
* Initialize the provider and the database helper object.
*/
#Override
public boolean onCreate() {
mHelper = new PetHelper(getContext());
return true;
}
/**
* Perform the query for the given URI. Use the given projection, selection, selection arguments, and sort order.
*/
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
// Get readable database
SQLiteDatabase database = mHelper.getReadableDatabase();
// This cursor will hold the result of the query
Cursor cursor;
// Figure out if the URI matcher can match the URI to a specific code
int match = sUriMatcher.match(uri);
switch (match) {
case PETS:
// For the PETS code, query the pets table directly with the given
// projection, selection, selection arguments, and sort order. The cursor
// could contain multiple rows of the pets table.
cursor = database.query(PetContract.PetEntry.TABLE_NAME, projection, selection, selectionArgs,
null, null, sortOrder);
break;
case PET_ID:
// For the PET_ID code, extract out the ID from the URI.
// For an example URI such as "content://com.example.android.pets/pets/3",
// the selection will be "_id=?" and the selection argument will be a
// String array containing the actual ID of 3 in this case.
//
// For every "?" in the selection, we need to have an element in the selection
// arguments that will fill in the "?". Since we have 1 question mark in the
// selection, we have 1 String in the selection arguments' String array.
selection = PetContract.PetEntry._ID + "=?";
selectionArgs = new String[] { String.valueOf(ContentUris.parseId(uri)) };
// This will perform a query on the pets table where the _id equals 3 to return a
// Cursor containing that row of the table.
cursor = database.query(PetContract.PetEntry.TABLE_NAME, projection, selection, selectionArgs,
null, null, sortOrder);
break;
default:
throw new IllegalArgumentException("Cannot query unknown URI " + uri);
}
return cursor;
}
/**
* Insert new data into the provider with the given ContentValues.
*/
#Override
public Uri insert(Uri uri, ContentValues contentValues) {
final int match = sUriMatcher.match(uri);
switch (match) {
case PETS:
return insertPet(uri, contentValues);
default:
throw new IllegalArgumentException("Insertion is not supported for " + uri);
}
}
private Uri insertPet(Uri uri, ContentValues values) {
// Get writeable database
SQLiteDatabase database = mHelper.getWritableDatabase();
// Insert the new pet with the given values
long id = database.insert(PetContract.PetEntry.TABLE_NAME, null, values);
// If the ID is -1, then the insertion failed. Log an error and return null.
if (id == -1) {
Log.e(LOG_TAG, "Failed to insert row for " + uri);
return null;
}
// Return the new URI with the ID (of the newly inserted row) appended at the end
return ContentUris.withAppendedId(uri, id);
}
/**
* Updates the data at the given selection and selection arguments, with the new ContentValues.
*/
#Override
public int update(Uri uri, ContentValues contentValues, String selection, String[] selectionArgs) {
return 0;
}
/**
* Delete the data at the given selection and selection arguments.
*/
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
/**
* Returns the MIME type of data for the content URI.
*/
#Override
public String getType(Uri uri) {
return null;
}
}
Then in CatalogueActivity, I use the query(...) method of the ContentResolver. So
CatalogActivity
public class CatalogActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_catalog);
// Setup FAB to open EditorActivity
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent intent = new Intent(CatalogActivity.this, EditorActivity.class);
startActivity(intent);
}
});
}
#Override
protected void onStart() {
super.onStart();
displayDatabaseInfo();
}
/**
* Temporary helper method to display information in the onscreen TextView about the state of
* the pets database.
*/
private void displayDatabaseInfo() {
// Define a projection that specifies which columns from the database
// you will actually use after this query.
String[] projection = {
PetEntry._ID,
PetEntry.COLUMN_PET_NAME,
PetEntry.COLUMN_PET_BREED,
PetEntry.COLUMN_PET_GENDER,
PetEntry.COLUMN_PET_WEIGHT };
// Perform a query on the provider using the ContentResolver.
// Use the {#link PetEntry#CONTENT_URI} to access the pet data.
Cursor cursor = getContentResolver().query(
PetEntry.CONTENT_URI, // The content URI of the words table
projection, // The columns to return for each row
null, // Selection criteria
null, // Selection criteria
null); // The sort order for the returned rows
TextView displayView = (TextView) findViewById(R.id.text_view_pet);
try {
// Create a header in the Text View that looks like this:
//
// The pets table contains <number of rows in Cursor> pets.
// _id - name - breed - gender - weight
//
// In the while loop below, iterate through the rows of the cursor and display
// the information from each column in this order.
displayView.setText("The pets table contains " + cursor.getCount() + " pets.\n\n");
displayView.append(PetEntry._ID + " - " +
PetEntry.COLUMN_PET_NAME + " - " +
PetEntry.COLUMN_PET_BREED + " - " +
PetEntry.COLUMN_PET_GENDER + " - " +
PetEntry.COLUMN_PET_WEIGHT + "\n");
// Figure out the index of each column
int idColumnIndex = cursor.getColumnIndex(PetEntry._ID);
int nameColumnIndex = cursor.getColumnIndex(PetEntry.COLUMN_PET_NAME);
int breedColumnIndex = cursor.getColumnIndex(PetEntry.COLUMN_PET_BREED);
int genderColumnIndex = cursor.getColumnIndex(PetEntry.COLUMN_PET_GENDER);
int weightColumnIndex = cursor.getColumnIndex(PetEntry.COLUMN_PET_WEIGHT);
// Iterate through all the returned rows in the cursor
while (cursor.moveToNext()) {
// Use that index to extract the String or Int value of the word
// at the current row the cursor is on.
int currentID = cursor.getInt(idColumnIndex);
String currentName = cursor.getString(nameColumnIndex);
String currentBreed = cursor.getString(breedColumnIndex);
int currentGender = cursor.getInt(genderColumnIndex);
int currentWeight = cursor.getInt(weightColumnIndex);
// Display the values from each column of the current row in the cursor in the TextView
displayView.append(("\n" + currentID + " - " +
currentName + " - " +
currentBreed + " - " +
currentGender + " - " +
currentWeight));
}
} finally {
// Always close the cursor when you're done reading from it. This releases all its
// resources and makes it invalid.
cursor.close();
}
}
/**
* Helper method to insert hardcoded pet data into the database. For debugging purposes only.
*/
private void insertPet() {
// Create a ContentValues object where column names are the keys,
// and Toto's pet attributes are the values.
ContentValues values = new ContentValues();
values.put(PetEntry.COLUMN_PET_NAME, "Toto");
values.put(PetEntry.COLUMN_PET_BREED, "Terrier");
values.put(PetEntry.COLUMN_PET_GENDER, PetEntry.GENDER_MALE);
values.put(PetEntry.COLUMN_PET_WEIGHT, 7);
// Insert a new row for Toto into the provider using the ContentResolver.
// Use the {#link PetEntry#CONTENT_URI} to indicate that we want to insert
// into the pets database table.
// Receive the new content URI that will allow us to access Toto's data in the future.
Uri newUri = getContentResolver().insert(PetEntry.CONTENT_URI, values);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu options from the res/menu/menu_catalog.xml file.
// This adds menu items to the app bar.
getMenuInflater().inflate(R.menu.menu_catalog, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// User clicked on a menu option in the app bar overflow menu
switch (item.getItemId()) {
// Respond to a click on the "Insert dummy data" menu option
case R.id.action_insert_dummy_data:
insertPet();
displayDatabaseInfo();
return true;
// Respond to a click on the "Delete all entries" menu option
case R.id.action_delete_all_entries:
// Do nothing for now
return true;
}
return super.onOptionsItemSelected(item);
}
}
And the EditorActiviy
**
* Allows user to create a new pet or edit an existing one.
*/
public class EditorActivity extends AppCompatActivity {
/** EditText field to enter the pet's name */
private EditText mNameEditText;
/** EditText field to enter the pet's breed */
private EditText mBreedEditText;
/** EditText field to enter the pet's weight */
private EditText mWeightEditText;
/** EditText field to enter the pet's gender */
private Spinner mGenderSpinner;
/**
* Gender of the pet. The possible values are:
* 0 for unknown gender, 1 for male, 2 for female.
*/
private int mGender = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_editor);
// Find all relevant views that we will need to read user input from
mNameEditText = (EditText) findViewById(R.id.edit_pet_name);
mBreedEditText = (EditText) findViewById(R.id.edit_pet_breed);
mWeightEditText = (EditText) findViewById(R.id.edit_pet_weight);
mGenderSpinner = (Spinner) findViewById(R.id.spinner_gender);
setupSpinner();
}
/**
* Setup the dropdown spinner that allows the user to select the gender of the pet.
*/
private void setupSpinner() {
// Create adapter for spinner. The list options are from the String array it will use
// the spinner will use the default layout
ArrayAdapter genderSpinnerAdapter = ArrayAdapter.createFromResource(this,
R.array.array_gender_options, android.R.layout.simple_spinner_item);
// Specify dropdown layout style - simple list view with 1 item per line
genderSpinnerAdapter.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line);
// Apply the adapter to the spinner
mGenderSpinner.setAdapter(genderSpinnerAdapter);
// Set the integer mSelected to the constant values
mGenderSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String selection = (String) parent.getItemAtPosition(position);
if (!TextUtils.isEmpty(selection)) {
if (selection.equals(getString(R.string.gender_male))) {
mGender = 1; // Male
} else if (selection.equals(getString(R.string.gender_female))) {
mGender = 2; // Female
} else {
mGender = 0; // Unknown
}
}
}
// Because AdapterView is an abstract class, onNothingSelected must be defined
#Override
public void onNothingSelected(AdapterView<?> parent) {
mGender = 0; // Unknown
}
});
}
/**
* Get user input from editor and save new pet into database.
*/
private void insertPet() {
// Read from input fields
// Use trim to eliminate leading or trailing white space
String nameString = mNameEditText.getText().toString().trim();
String breedString = mBreedEditText.getText().toString().trim();
String weightString = mWeightEditText.getText().toString().trim();
int weight = Integer.parseInt(weightString);
// Create a ContentValues object where column names are the keys,
// and pet attributes from the editor are the values.
ContentValues values = new ContentValues();
values.put(PetEntry.COLUMN_PET_NAME, nameString);
values.put(PetEntry.COLUMN_PET_BREED, breedString);
values.put(PetEntry.COLUMN_PET_GENDER, mGender);
values.put(PetEntry.COLUMN_PET_WEIGHT, weight);
// Insert a new pet into the provider, returning the content URI for the new pet.
Uri newUri = getContentResolver().insert(PetEntry.CONTENT_URI, values);
// Show a toast message depending on whether or not the insertion was successful
if (newUri == null) {
// If the new content URI is null, then there was an error with insertion.
Toast.makeText(this, getString(R.string.editor_insert_pet_failed),
Toast.LENGTH_SHORT).show();
} else {
// Otherwise, the insertion was successful and we can display a toast.
Toast.makeText(this, getString(R.string.editor_insert_pet_successful),
Toast.LENGTH_SHORT).show();
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu options from the res/menu/menu_editor.xml file.
// This adds menu items to the app bar.
getMenuInflater().inflate(R.menu.menu_editor, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// User clicked on a menu option in the app bar overflow menu
switch (item.getItemId()) {
// Respond to a click on the "Save" menu option
case R.id.action_save:
insertPet();
return true;
// Respond to a click on the "Delete" menu option
case R.id.action_delete:
// Do nothing for now
return true;
// Respond to a click on the "Up" arrow button in the app bar
case android.R.id.home:
// Navigate back to parent activity (CatalogActivity)
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
}
The exception occurs in CatalogActivity.
Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'void android.database.Cursor.close()' on a null object reference
PetContract
public final class PetContract {
// To prevent someone from accidentally instantiating the contract class,
// give it an empty constructor.
private PetContract() {}
/**
* The "Content authority" is a name for the entire content provider, similar to the
* relationship between a domain name and its website. A convenient string to use for the
* content authority is the package name for the app, which is guaranteed to be unique on the
* device.
*/
public static final String CONTENT_AUTHORITY = "com.example.android.pets";
/**
* Use CONTENT_AUTHORITY to create the base of all URI's which apps will use to contact
* the content provider.
*/
public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
/**
* Possible path (appended to base content URI for possible URI's)
* For instance, content://com.example.android.pets/pets/ is a valid path for
* looking at pet data. content://com.example.android.pets/staff/ will fail,
* as the ContentProvider hasn't been given any information on what to do with "staff".
*/
public static final String PATH_PETS = "pets";
/**
* Inner class that defines constant values for the pets database table.
* Each entry in the table represents a single pet.
*/
public static final class PetEntry implements BaseColumns {
/** The content URI to access the pet data in the provider */
public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, PATH_PETS);
/** Name of database table for pets */
public final static String TABLE_NAME = "pets";
/**
* Unique ID number for the pet (only for use in the database table).
*
* Type: INTEGER
*/
public final static String _ID = BaseColumns._ID;
/**
* Name of the pet.
*
* Type: TEXT
*/
public final static String COLUMN_PET_NAME ="name";
/**
* Breed of the pet.
*
* Type: TEXT
*/
public final static String COLUMN_PET_BREED = "breed";
/**
* Gender of the pet.
*
* The only possible values are {#link #GENDER_UNKNOWN}, {#link #GENDER_MALE},
* or {#link #GENDER_FEMALE}.
*
* Type: INTEGER
*/
public final static String COLUMN_PET_GENDER = "gender";
/**
* Weight of the pet.
*
* Type: INTEGER
*/
public final static String COLUMN_PET_WEIGHT = "weight";
/**
* Possible values for the gender of the pet.
*/
public static final int GENDER_UNKNOWN = 0;
public static final int GENDER_MALE = 1;
public static final int GENDER_FEMALE = 2;
}
}
Any ideas?
Thanks,
Theo.
Your content URI is not matching while query, so it's throwing IllegalArgumentException, and your cursor is null, but you are trying to close the cursor, so it's crashing
In final check null before close,
finally {
// Always close the cursor when you're done reading from it. This releases all its
// resources and makes it invalid.
if(cursor != null)
cursor.close();
}
Check your content URI while querying in CatalogActivity,
Update your getType() too its returning null
I just did this and my app worked:
In PetContract.PetEntry add
public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, PATH_PETS);
then in catalogue activity and edit activity use CONTENT_URI instead of BASE_CONTENT_URI.
<provider android:name="com.example.android.pets.PetProvider"
android:authorities="com.example.android.pets">
</provider>
Just use this provider tag in manifest file. I have used it. and it is working fine
Use this in your suriMatcher:
sUriMatcher.addURI(PetContract.CONTENT_AUTHORITY, PetContract.PATH_PETS + "/#", PET_ID);
instead of this:
sURIMatcher.addURI(petcontract.CONTENT_AUTHORITY, pet_Path,PETS_ID);
I'm having an Activity which handles a details Fragment for my Movie data & I want to implement favorite functionality. But the issue is that I can favorite only one movie at a time. Also, each time I try to add/favorite a movie, it'll show on the database that it's being saved but never being deleted on unfavorite.
Here's the code that I've:
MovieDetailsActivity
public class MovieDetailsActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/* Set the content of the activity to use the activity_tv_show_details.xml layout file */
setContentView(R.layout.activity_movie_details);
Bundle movieDetails = new Bundle();
/**get the movie's Object from the parent activity**/
Movie movie = getIntent().getParcelableExtra("movie");
movieDetails.putParcelable("movie", movie);
Intent intent = getIntent();
Uri mCurrentMovieUri = intent.getData();
movieDetails.putString("currentMovieUri", mCurrentMovieUri.toString());
/* Check for pre-existing instances of fragments(here explicitly check for savedInstance)
and then begin fragment transaction accordingly */
if (savedInstanceState == null) {
MovieDetailsFragment defaultMovieFragment = new MovieDetailsFragment();
defaultMovieFragment.setArguments(movieDetails);
getSupportFragmentManager().beginTransaction()
.add(R.id.containerMovieDetailActivity, defaultMovieFragment)
.commit();
}
}
}
MovieDetailsFragment
public class MovieDetailsFragment extends Fragment implements LoaderManager.LoaderCallbacks<MovieDetailsBundle> {
private static final int MOVIE_DETAIL_LOADER_ID = 2;
/* Arrays for holding movie details */
Movie movie;
public MovieDetailsFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_movie_detail, container, false);
Bundle bundle = getArguments();
position = bundle.getInt("position");
currentMovieUri = Uri.parse(bundle.getString("currentMovieUri"));
favoriteButton = (ImageButton) rootView.findViewById(R.id.favorite);
if (savedInstanceState == null) {
mReview = new ArrayList<>();
mVideo = new ArrayList<>();
mCredits = new ArrayList<>();
mMovieDetailsBundle = new MovieDetailsBundle();
}
if ((bundle != null)) {
movie = getArguments().getParcelable("movie");
movieDetailTitleTextView.setText(movie.getMovieTitle());
...
String[] projection = {
MoviesEntry._ID,
MoviesEntry.COLUMN_MOVIE_TITLE,
MoviesEntry.COLUMN_MOVIE_RELEASE_DATE,
MoviesEntry.COLUMN_MOVIE_OVERVIEW,
MoviesEntry.COLUMN_MOVIE_POSTER_URL,
MoviesEntry.COLUMN_MOVIE_BACKDROP_URL,
MoviesEntry.COLUMN_MOVIE_RATING};
// Perform a query on the provider using the ContentResolver.
// Use the {#link MoviesEntry#CONTENT_URI} to access the pet data.
Cursor cursor = getActivity().getContentResolver().query(
MoviesEntry.CONTENT_URI, // The content URI of the movies table
projection, // The columns to return for each row
null, // Selection criteria
null, // Selection criteria
null);
try {
// Figure out the index of each column
int idColumnIndex = cursor.getColumnIndex(MoviesEntry._ID);
int titleColumnIndex = cursor.getColumnIndex(MoviesEntry.COLUMN_MOVIE_TITLE);
int releaseDateColumnIndex = cursor.getColumnIndex(MoviesEntry.COLUMN_MOVIE_RELEASE_DATE);
int overviewColumnIndex = cursor.getColumnIndex(MoviesEntry.COLUMN_MOVIE_OVERVIEW);
int posterUrlColumnIndex = cursor.getColumnIndex(MoviesEntry.COLUMN_MOVIE_POSTER_URL);
int backdropUrlColumnIndex = cursor.getColumnIndex(MoviesEntry.COLUMN_MOVIE_BACKDROP_URL);
int ratingColumnIndex = cursor.getColumnIndex(MoviesEntry.COLUMN_MOVIE_RATING);
// Iterate through all the returned rows in the cursor
if (cursor.moveToFirst()){
// Use that index to extract the String or Int value of the word
// at the current row the cursor is on.
currentID = cursor.getInt(idColumnIndex);
currentTitle = cursor.getString(titleColumnIndex);
currentReleaseDate = cursor.getString(releaseDateColumnIndex);
currentOverview = cursor.getString(overviewColumnIndex);
currentposterUrl = cursor.getString(posterUrlColumnIndex);
currentBackdropUrl = cursor.getString(backdropUrlColumnIndex);
currentRatings = cursor.getFloat(ratingColumnIndex);
}
} finally {
// Always close the cursor when you're done reading from it. This releases all its
// resources and makes it invalid.
cursor.close();
}
if (currentTitle!=null) {
//currentTitle = movie.getMovieTitle();
if (currentTitle.equals(movie.getMovieTitle())) {
favoriteButton.setImageResource(R.drawable.starred);
favorite = true;
}
}else{
favoriteButton.setImageResource(R.drawable.unstarred);
favorite = false;
}
/*setting the ratingbar from #link: https://github.com/FlyingPumba/SimpleRatingBar*/
SimpleRatingBar simpleRatingBar = (SimpleRatingBar) rootView.findViewById(R.id.movieRatingInsideMovieDetailsFragment);
simpleRatingBar.setRating((float) (movie.getMovieVoteAverage()) / 2);
favoriteButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (favorite) {
// Only perform the delete if this is an existing movie.
if (currentMovieUri != null) {
// Call the ContentResolver to delete the movie at the given content URI.
// Pass in null for the selection and selection args because the mCurrentPetUri
// content URI already identifies the movie that we want.
int rowsDeleted = getActivity().getContentResolver().delete(currentMovieUri, null, null);
// Show a toast message depending on whether or not the delete was successful.
if (rowsDeleted == 0) {
// If no rows were deleted, then there was an error with the delete.
Toast.makeText(getContext(), getString(R.string.delete_movie_failed),
Toast.LENGTH_SHORT).show();
} else {
// Otherwise, the delete was successful and we can display a toast.
Toast.makeText(getContext(), getString(R.string.delete_movie_successful),
Toast.LENGTH_SHORT).show();
}
}
favoriteButton.setImageResource(R.drawable.unstarred);
} else {
// Create a ContentValues object where column names are the keys,
// and movie attributes from the editor are the values.
ContentValues values = new ContentValues();
values.put(MoviesEntry.COLUMN_MOVIE_TITLE, movie.getMovieTitle());
values.put(MoviesEntry.COLUMN_MOVIE_RELEASE_DATE, movie.getMovieReleaseDate());
values.put(MoviesEntry.COLUMN_MOVIE_OVERVIEW, movie.getMovieOverview());
values.put(MoviesEntry.COLUMN_MOVIE_POSTER_URL, movie.getMoviePosterPath());
values.put(MoviesEntry.COLUMN_MOVIE_BACKDROP_URL, movie.getMovieBackdropPath());
values.put(MoviesEntry.COLUMN_MOVIE_RATING, movie.getMovieVoteAverage() / 2);
// Insert a new movie into the provider, returning the content URI for the new movie.
Uri newUri = getActivity().getContentResolver().insert(MoviesEntry.CONTENT_URI, values);
// Show a toast message depending on whether or not the insertion was successful
if (newUri == null) {
// If the new content URI is null, then there was an error with insertion.
Toast.makeText(getContext(), getString(R.string.insert_movie_failed),
Toast.LENGTH_SHORT).show();
} else {
// Otherwise, the insertion was successful and we can display a toast.
Toast.makeText(getContext(), getString(R.string.insert_movie_successful),
Toast.LENGTH_SHORT).show();
}
favoriteButton.setImageResource(R.drawable.starred);
}
favorite = !favorite;
}
});
...
#Override
public Loader<MovieDetailsBundle> onCreateLoader(int id, Bundle args) {
Uri baseUri = Uri.parse((UrlsAndConstants.MovieDetailQuery.DEFAULT_URL) + movie.getMovieId());
Uri.Builder uriBuilder = baseUri.buildUpon();
uriBuilder.appendQueryParameter(API_KEY_PARAM, API_KEY_PARAM_VALUE);
uriBuilder.appendQueryParameter(APPEND_TO_RESPONSE, VIDEOS_AND_REVIEWS_AND_CREDITS);
return new DetailsMovieLoader(getActivity().getApplicationContext(), uriBuilder.toString());
}
#Override
public void onLoadFinished(Loader<MovieDetailsBundle> loader, )
...
}
public void updateDurationTextView(MovieDetailsBundle movieDetailsBundle) {
...
movieRunTimeDuration.setText(mMovieDurationString);
}
#Override
public void onLoaderReset(Loader<MovieDetailsBundle> loader) {
}
}
ContentProvider
public class MovieProvider extends ContentProvider {
/**
* URI matcher code for the content URI for the movies table
*/
private static final int MOVIES = 100;
/**
* URI matcher code for the content URI for a single movie in the movie table
*/
private static final int MOVIE_ID = 200;
/**
* UriMatcher object to match a content URI to a corresponding code.
* The input passed into the constructor represents the code to return for the root URI.
* It's common to use NO_MATCH as the input for this case.
*/
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// Static initializer. This is run the first time anything is called from this class.
static {
// The calls to addURI() go here, for all of the content URI patterns that the provider
// should recognize. All paths added to the UriMatcher have a corresponding code to return
// when a match is found.
// The content URI of the form "content://com.example.android.movie/movie" will map to the
// integer code {#link #MOVIES}. This URI is used to provide access to MULTIPLE rows
// of the movie table.
sUriMatcher.addURI(MovieContract.CONTENT_AUTHORITY, MovieContract.PATH_MOVIES, MOVIES);
// The content URI of the form "content://com.example.android.movie/movie/#" will map to the
// integer code {#link #MOVIES}. This URI is used to provide access to ONE single row
// of the movie table.
//
// In this case, the "#" wildcard is used where "#" can be substituted for an integer.
// For example, "content://com.example.android.movie/movie/3" matches, but
// "content://com.example.android.movie/movie" (without a number at the end) doesn't match.
sUriMatcher.addURI(MovieContract.CONTENT_AUTHORITY, MovieContract.PATH_MOVIES + "/#", MOVIE_ID);
}
/**
* Database helper object
*/
private MovieDbHelper mDbHelper;
#Override
public boolean onCreate() {
mDbHelper = new MovieDbHelper(getContext());
return true;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
// Get readable database
SQLiteDatabase database = mDbHelper.getReadableDatabase();
// This cursor will hold the result of the query
Cursor cursor;
// Figure out if the URI matcher can match the URI to a specific code
int match = sUriMatcher.match(uri);
switch (match) {
case MOVIES:
// For the MOVIES code, query the movie table directly with the given
// projection, selection, selection arguments, and sort order. The cursor
// could contain multiple rows of the movie table.
cursor = database.query(MoviesEntry.TABLE_NAME, projection, selection, selectionArgs,
null, null, sortOrder);
break;
case MOVIE_ID:
// For the MOVIE_ID code, extract out the ID from the URI.
// For an example URI such as "content://com.example.android.movie/movie/3",
// the selection will be "_id=?" and the selection argument will be a
// String array containing the actual ID of 3 in this case.
//
// For every "?" in the selection, we need to have an element in the selection
// arguments that will fill in the "?". Since we have 1 question mark in the
// selection, we have 1 String in the selection arguments' String array.
selection = MoviesEntry._ID + "=?";
selectionArgs = new String[]{String.valueOf(ContentUris.parseId(uri))};
// This will perform a query on the movie table where the _id equals 3 to return a
// Cursor containing that row of the table.
cursor = database.query(MoviesEntry.TABLE_NAME, projection, selection, selectionArgs,
null, null, sortOrder);
break;
default:
throw new IllegalArgumentException("Cannot query unknown URI " + uri);
}
// Set notification URI on the Cursor,
// so we know what content URI the Cursor was created for.
// If the data at this URI changes, then we know we need to update the Cursor.
cursor.setNotificationUri(getContext().getContentResolver(), uri);
// Return the cursor
return cursor;
}
#Override
public Uri insert(Uri uri, ContentValues contentValues) {
final int match = sUriMatcher.match(uri);
switch (match) {
case MOVIES:
return insertMovie(uri, contentValues);
default:
throw new IllegalArgumentException("Insertion is not supported for " + uri);
}
}
/**
* Insert a movie into the database with the given content values. Return the new content URI
* for that specific row in the database.
*/
private Uri insertMovie(Uri uri, ContentValues values) {
// Log.v("my_tag", "Received Uri to be matched insert is :"+uri.toString());
Log.i("my_tag", "Received Uri to be matched insert is :"+uri.toString());
// Check that the product name is not null
String title = values.getAsString(MoviesEntry.COLUMN_MOVIE_TITLE);
if (title == null) {
throw new IllegalArgumentException("Product requires a name");
}
// Check that the product name is not null
String releaseDate = values.getAsString(MoviesEntry.COLUMN_MOVIE_RELEASE_DATE);
if (releaseDate == null) {
throw new IllegalArgumentException("Product requires a detail");
}
// If the price is provided, check that it's greater than or equal to 0
String overview = values.getAsString(MoviesEntry.COLUMN_MOVIE_OVERVIEW);
if (overview == null) {
throw new IllegalArgumentException("Product requires valid price");
}
String posterUrl = values.getAsString(MoviesEntry.COLUMN_MOVIE_POSTER_URL);
if (posterUrl == null) {
throw new IllegalArgumentException("Product requires valid quantity");
}
String backdropUrl = values.getAsString(MoviesEntry.COLUMN_MOVIE_BACKDROP_URL);
if (backdropUrl == null) {
throw new IllegalArgumentException("Product requires valid quantity");
}
Integer ratings = values.getAsInteger(MoviesEntry.COLUMN_MOVIE_RATING);
if (ratings != null && ratings < 0) {
throw new IllegalArgumentException("Product requires valid quantity");
}
// Get writeable database
SQLiteDatabase database = mDbHelper.getWritableDatabase();
// Insert the new product with the given values
long id = database.insert(MoviesEntry.TABLE_NAME, null, values);
Log.i("my_tag", "values is :"+values.toString());
// Log.e("my_tag", "values is :"+values.toString());
// Log.v("my_tag", "values is :"+values.toString());
if (id == -1) {
return null;
}
// Notify all listeners that the data has changed for the product content URI
getContext().getContentResolver().notifyChange(uri, null);
// Return the new URI with the ID (of the newly inserted row) appended at the end
return ContentUris.withAppendedId(uri, id);
}
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// Get writeable database
SQLiteDatabase database = mDbHelper.getWritableDatabase();
// Track the number of rows that were deleted
int rowsDeleted;
final int match = sUriMatcher.match(uri);
case MOVIES:
// Delete all rows that match the selection and selection args
rowsDeleted = database.delete(MoviesEntry.TABLE_NAME, selection, selectionArgs);
break;
case MOVIE_ID:
// Delete a single row given by the ID in the URI
selection = MoviesEntry._ID + "=?";
selectionArgs = new String[]{String.valueOf(ContentUris.parseId(uri))};
Log.i("my_tag", "selection is :"+selection);
// Log.e("my_tag", "selection is :"+selection);
// Log.v("my_tag", "selection is :"+selection);
Log.i("my_tag", "selectionArgs is :"+selectionArgs[0]);
// Log.e("my_tag", "selectionArgs is :"+selectionArgs[0]);
// Log.v("my_tag", "selectionArgs is :"+selectionArgs[0]);
rowsDeleted = database.delete(MoviesEntry.TABLE_NAME, selection, selectionArgs);
break;
default:
throw new IllegalArgumentException("Deletion is not supported for " + uri);
}
// If 1 or more rows were deleted, then notify all listeners that the data at the
// given URI has changed
if (rowsDeleted != 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
// Return the number of rows deleted
return rowsDeleted;
}
#Override
public int update(Uri uri, ContentValues contentValues, String selection,
String[] selectionArgs) {
...
}
#Override
public String getType(Uri uri) {
final int match = sUriMatcher.match(uri);
switch (match) {
case MOVIES:
return MoviesEntry.CONTENT_LIST_TYPE;
case MOVIE_ID:
return MoviesEntry.CONTENT_ITEM_TYPE;
default:
throw new IllegalStateException("Unknown URI " + uri + " with match " + match);
}
}
}
This question already has answers here:
java.lang.NullPointerException: Attempt to invoke interface method 'boolean android.database.Cursor.moveToFirst()' on a null object reference [duplicate]
(1 answer)
What is a NullPointerException, and how do I fix it?
(12 answers)
Closed 2 years ago.
I do have a problem that gives me a headache. I store some images of my city in the sqlite database via a custom content provider. However when I run my app I get a null cursor.
Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'int android.database.Cursor.getCount()' on a null object reference
at theo.testing.androidcustomloaders.fragments.MainActivityFragment.onActivityCreated(MainActivityFragment.java:74)
at android.support.v4.app.Fragment.performActivityCreated(Fragment.java:2089)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1133)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1290)
at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:801)
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1677)
at android.support.v4.app.FragmentController.execPendingActions(FragmentController.java:388)
at android.support.v4.app.FragmentActivity.onStart(FragmentActivity.java:604)
at android.support.v7.app.AppCompatActivity.onStart(AppCompatActivity.java:178)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1220)
at android.app.Activity.performStart(Activity.java:5992)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2261)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2358)
at android.app.ActivityThread.access$800(ActivityThread.java:144)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1278)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5219)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:898)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:693)
That would mean the the info are not stored correctly or the Uri of my provider is somehow faulty. So.
MyCityContract
public class MyCityContract {
public static final String CONTENT_AUTHORITY = "theo.testing.customloaders";
public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
public static final class MyCityEntry implements BaseColumns{
//table name
public static final String TABLE_MY_CITY = "my_city";
//columns
public static final String _ID = "_id";
public static final String COLUMN_NAME = "name";
public static final String COLUMN_ICON = "icon";
// create content uri
public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon()
.appendPath(TABLE_MY_CITY).build();
// create cursor of base type directory for multiple entries
public static final String CONTENT_DIR_TYPE =
ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + TABLE_MY_CITY;
// create cursor of base type item for single entry
public static final String CONTENT_ITEM_TYPE =
ContentResolver.CURSOR_ITEM_BASE_TYPE +"/" + CONTENT_AUTHORITY + "/" + TABLE_MY_CITY;
// for building URIs on insertion
public static Uri buildFlavorsUri(long id){
return ContentUris.withAppendedId(CONTENT_URI, id);
}
}
}
MyCityDbHelper
public class MyCityDbHelper extends SQLiteOpenHelper{
public static final String LOG_TAG = MyCityDbHelper.class.getSimpleName();
//name & version
public static final String DATABASE_NAME = "city.db";
public static final int DATABASE_VERSION = 4;
// Create the database
public MyCityDbHelper(Context context) {
super(context, DATABASE_NAME,null,DATABASE_VERSION);
}
#Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
final String SQL_CREATE_MY_CITY_TABLE = "CREATE TABLE " +
MyCityContract.MyCityEntry.TABLE_MY_CITY + "(" + MyCityContract.MyCityEntry._ID +
" INTEGER PRIMARY KEY AUTOINCREMENT, " +
MyCityContract.MyCityEntry.COLUMN_NAME + " TEXT NOT NULL, " +
MyCityContract.MyCityEntry.COLUMN_ICON + " INTEGER NOT NULL);";
sqLiteDatabase.execSQL(SQL_CREATE_MY_CITY_TABLE);
}
#Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
Log.w(LOG_TAG, "Upgrading database from version " + oldVersion + " to " +
newVersion + ". OLD DATA WILL BE DESTROYED");
// Drop the table
sqLiteDatabase.execSQL("DROP TABLE IF EXISTS " + MyCityContract.MyCityEntry.TABLE_MY_CITY);
sqLiteDatabase.execSQL("DELETE FROM SQLITE_SEQUENCE WHERE NAME = '" +
MyCityContract.MyCityEntry.TABLE_MY_CITY + "'");
// re-create database
onCreate(sqLiteDatabase);
}
}
MyCityProvider
public class MyCityProvider extends ContentProvider {
private static final String LOG_TAG = MyCityProvider.class.getSimpleName();
private static final UriMatcher sUriMatcher = buildUriMatcher();
private MyCityDbHelper myCityDbHelper;
//Codes for UriMatcher
private static final int MY_CITY = 100;
private static final int MY_CITY_WITH_ID = 200;
private static UriMatcher buildUriMatcher(){
// Build a UriMatcher by adding a specific code to return based on a match
// It's common to use NO_MATCH as the code for this case.
final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
final String authority = MyCityContract.CONTENT_AUTHORITY;
//add code for each URI
matcher.addURI(authority,MyCityContract.MyCityEntry.TABLE_MY_CITY,MY_CITY);
matcher.addURI(authority,MyCityContract.MyCityEntry.TABLE_MY_CITY + "/#",MY_CITY_WITH_ID);
return matcher;
}
#Override
public boolean onCreate() {
myCityDbHelper = new MyCityDbHelper(getContext());
return true;
}
#Override
public String getType(Uri uri) {
final int match = sUriMatcher.match(uri);
switch (match){
case MY_CITY: {
return MyCityContract.MyCityEntry.CONTENT_DIR_TYPE;
}
case MY_CITY_WITH_ID:{
return MyCityContract.MyCityEntry.CONTENT_ITEM_TYPE;
}
default:{
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder){
Cursor retCursor;
switch(sUriMatcher.match(uri)){
// All Flavors selected
case MY_CITY:{
retCursor = myCityDbHelper.getReadableDatabase().query(
MyCityContract.MyCityEntry.TABLE_MY_CITY,
projection,
selection,
selectionArgs,
null,
null,
sortOrder);
return retCursor;
}
// Individual flavor based on Id selected
case MY_CITY_WITH_ID:{
retCursor = myCityDbHelper.getReadableDatabase().query(
MyCityContract.MyCityEntry.TABLE_MY_CITY,
projection,
MyCityContract.MyCityEntry._ID + " = ?",
new String[] {String.valueOf(ContentUris.parseId(uri))},
null,
null,
sortOrder);
return retCursor;
}
default:{
// By default, we assume a bad URI
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
}
#Override
public Uri insert(Uri uri, ContentValues contentValues) {
final SQLiteDatabase db = myCityDbHelper.getWritableDatabase();
Uri returnUri;
switch (sUriMatcher.match(uri)){
case MY_CITY:
long _id = db.insert(MyCityContract.MyCityEntry.TABLE_MY_CITY,null,contentValues);
Log.d("id",String.valueOf(_id));
// insert unless it is already contained in the database
if(_id>0){
returnUri = MyCityContract.MyCityEntry.buildFlavorsUri(_id);
}else {
throw new android.database.SQLException("Failed to insert row into: " + uri);
}
break;
default: {
throw new UnsupportedOperationException("Unknown uri: " + uri );
}
}
getContext().getContentResolver().notifyChange(uri,null);
return returnUri;
}
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
final SQLiteDatabase db = myCityDbHelper.getWritableDatabase();
final int match = sUriMatcher.match(uri);
int numDeleted;
switch(match){
case MY_CITY:
numDeleted = db.delete(
MyCityContract.MyCityEntry.TABLE_MY_CITY, selection, selectionArgs);
// reset _ID
db.execSQL("DELETE FROM SQLITE_SEQUENCE WHERE NAME = '" +
MyCityContract.MyCityEntry.TABLE_MY_CITY + "'");
break;
case MY_CITY_WITH_ID:
numDeleted = db.delete(MyCityContract.MyCityEntry.TABLE_MY_CITY,
MyCityContract.MyCityEntry._ID + " = ?",
new String[]{String.valueOf(ContentUris.parseId(uri))});
// reset _ID
db.execSQL("DELETE FROM SQLITE_SEQUENCE WHERE NAME = '" +
MyCityContract.MyCityEntry.TABLE_MY_CITY + "'");
break;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
return numDeleted;
}
#Override
public int bulkInsert(Uri uri, ContentValues[] values){
final SQLiteDatabase db = myCityDbHelper.getWritableDatabase();
final int match = sUriMatcher.match(uri);
switch(match){
case MY_CITY:
// allows for multiple transactions
db.beginTransaction();
// keep track of successful inserts
int numInserted = 0;
try{
for(ContentValues value : values){
if (value == null){
throw new IllegalArgumentException("Cannot have null content values");
}
long _id = -1;
try{
_id = db.insertOrThrow(MyCityContract.MyCityEntry.TABLE_MY_CITY,
null, value);
}catch(SQLiteConstraintException e) {
Log.w(LOG_TAG, "Attempting to insert " +
value.getAsString(
MyCityContract.MyCityEntry.COLUMN_NAME)
+ " but value is already in database.");
}
if (_id != -1){
numInserted++;
}
}
if(numInserted > 0){
// If no errors, declare a successful transaction.
// database will not populate if this is not called
db.setTransactionSuccessful();
}
} finally {
// all transactions occur at once
db.endTransaction();
}
if (numInserted > 0){
// if there was successful insertion, notify the content resolver that there
// was a change
getContext().getContentResolver().notifyChange(uri, null);
}
return numInserted;
default:
return super.bulkInsert(uri, values);
}
}
#Override
public int update(Uri uri, ContentValues contentValues, String selection, String[] selectionArgs){
final SQLiteDatabase db = myCityDbHelper.getWritableDatabase();
int numUpdated = 0;
if (contentValues == null){
throw new IllegalArgumentException("Cannot have null content values");
}
switch(sUriMatcher.match(uri)){
case MY_CITY:{
numUpdated = db.update(MyCityContract.MyCityEntry.TABLE_MY_CITY,
contentValues,
selection,
selectionArgs);
break;
}
case MY_CITY_WITH_ID: {
numUpdated = db.update(MyCityContract.MyCityEntry.TABLE_MY_CITY,
contentValues,
MyCityContract.MyCityEntry._ID + " = ?",
new String[] {String.valueOf(ContentUris.parseId(uri))});
break;
}
default:{
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
if (numUpdated > 0){
getContext().getContentResolver().notifyChange(uri, null);
}
return numUpdated;
}
}
MainActivityFragment
public class MainActivityFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor>{
private static final String LOG_TAG = MainActivityFragment.class.getSimpleName();
private MyCityAdpapter myCityAdpapter;
private static final int CURSOR_LOADER_ID = 0;
private GridView mGridView;
MyCity[] mMyCity = {
new MyCity("Ancient Theatre - Larisa", R.drawable.larissa1),
new MyCity("Ancient Theatre - Larisa", R.drawable.larissa2),
new MyCity("Municipality park", R.drawable.larissa3),
new MyCity("Municipality park", R.drawable.larissa4),
new MyCity("Old trains",R.drawable.larissa5),
new MyCity("Old trains",R.drawable.larissa6),
new MyCity("Church",
R.drawable.larissa7),
new MyCity("Church",
R.drawable.larissa8),
new MyCity("Alcazar park",
R.drawable.larissa9),
new MyCity("Alcazar park",
R.drawable.larissa10),
new MyCity("AEL FC Arena",
R.drawable.larissa11),
new MyCity("AEL FC Arena",
R.drawable.larissa12),
new MyCity("Larissa Fair",
R.drawable.larissa13),
new MyCity("Larissa Fair",
R.drawable.larissa14),
new MyCity("Larissa Fair",
R.drawable.larissa15),
new MyCity("Larissa Fair",
R.drawable.larissa16)
};
public MainActivityFragment() {
// Required empty public constructor
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
Cursor c =
getActivity().getContentResolver().query(MyCityContract.MyCityEntry.CONTENT_URI,
new String[]{MyCityContract.MyCityEntry._ID},
null,
null,
null);
if (c.getCount() == 0){
insertData();
}
// initialize loader
getLoaderManager().initLoader(CURSOR_LOADER_ID, null, this);
super.onActivityCreated(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
// inflate fragment_main layout
final View rootView = inflater.inflate(R.layout.fragment_main_activity, container, false);
// initialize our FlavorAdapter
myCityAdpapter = new MyCityAdpapter(getActivity(), null, 0, CURSOR_LOADER_ID);
// initialize mGridView to the GridView in fragment_main.xml
mGridView = (GridView) rootView.findViewById(R.id.flavors_grid);
// set mGridView adapter to our CursorAdapter
mGridView.setAdapter(myCityAdpapter);
return rootView;
}
// insert data into database
public void insertData(){
ContentValues[] cityValuesArr = new ContentValues[mMyCity.length];
// Loop through static array of MyCity, add each to an instance of ContentValues
// in the array of ContentValues
for(int i = 0; i < mMyCity.length; i++){
cityValuesArr[i] = new ContentValues();
cityValuesArr[i].put(MyCityContract.MyCityEntry.COLUMN_ICON, mMyCity[i].image);
cityValuesArr[i].put(MyCityContract.MyCityEntry.COLUMN_NAME,
mMyCity[i].name);
}
// bulkInsert our ContentValues array
getActivity().getContentResolver().bulkInsert(MyCityContract.MyCityEntry.CONTENT_URI,
cityValuesArr);
}
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args){
return new CursorLoader(getActivity(),
MyCityContract.MyCityEntry.CONTENT_URI,
null,
null,
null,
null);
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
myCityAdpapter.swapCursor(data);
}
#Override
public void onLoaderReset(Loader<Cursor> loader){
myCityAdpapter.swapCursor(null);
}
}
I read the Cursor inside the onCreateView(...) method of my Fragment
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
Cursor c =
getActivity().getContentResolver().query(MyCityContract.MyCityEntry.CONTENT_URI,
new String[]{MyCityContract.MyCityEntry._ID},
null,
null,
null);
if (c.getCount() == 0){
insertData();
}
// initialize loader
getLoaderManager().initLoader(CURSOR_LOADER_ID, null, this);
super.onActivityCreated(savedInstanceState);
}
and this is where I am thrown the null Cursor exception.
Any ideas?
Thanks,
Theo.
EDIT
I changed this if condition from
if (c.getCount() == 0){
insertData();
}
to
if (c == null){
insertData();
}
and I am getting this exception!
Caused by: java.lang.IllegalArgumentException: Unknown URL content://theo.testing.customloaders/my_city
So there is be an error with provider. hmm...
I think you has a problem at using getActivity in onActivityCreated in your MainActivity Fragment
getActivity "might" return null if called from within onActivityCreated...especially during a configuration change like orientation change because the activity gets destroyed...
move that initialization to onAttach...
following link helped me to find out
getActivity return null in fragment onActivityCreated in some rooted device
Ok. I fixed it. All I did was changing my authority name from
theo.testing.customloaders
to
theo.testing.customloaders.app
This is my project for Android. I've tried to create a new table column: KEY_EXAMPLE. But it doesn't work. I'll put my code below and I would like to fix this. Can someone help me, please? It's very important. I'm a newbie. The KEY_EXAMPLE doesn't work. This is my code and what I'm trying to do this workout.
DictionaryDatabase.Java
public class DictionaryDatabase {
private static final String TAG = "DictionaryDatabase";
public static final String KEY_WORD = SearchManager.SUGGEST_COLUMN_TEXT_1;
public static final String KEY_DEFINITION = SearchManager.SUGGEST_COLUMN_TEXT_2;
public static final String KEY_EXAMPLE = SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA;
private static final String DATABASE_NAME = "dictionary";
private static final String FTS_VIRTUAL_TABLE = "FTSdictionary";
private static final int DATABASE_VERSION = 3;
private final DictionaryOpenHelper mDatabaseOpenHelper;
private static final HashMap<String,String> mColumnMap = buildColumnMap();
public DictionaryDatabase(Context context) {
mDatabaseOpenHelper = new DictionaryOpenHelper(context);
}
private static HashMap<String,String> buildColumnMap() {
HashMap<String,String> map = new HashMap<String,String>();
map.put(KEY_WORD, KEY_WORD);
map.put(KEY_DEFINITION, KEY_DEFINITION);
map.put(KEY_EXAMPLE, KEY_EXAMPLE);
map.put(BaseColumns._ID, "rowid AS " +
BaseColumns._ID);
map.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID, "rowid AS " +
SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
map.put(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID, "rowid AS " +
SearchManager.SUGGEST_COLUMN_SHORTCUT_ID);
return map;
}
public Cursor getWord(String rowId, String[] columns) {
String selection = "rowid = ?";
String[] selectionArgs = new String[] {rowId};
return query(selection, selectionArgs, columns);
}
public Cursor getWordMatches(String query, String[] columns) {
String selection = KEY_WORD + " MATCH ?";
String[] selectionArgs = new String[] {query+"*"};
return query(selection, selectionArgs, columns);
}
private Cursor query(String selection, String[] selectionArgs, String[] columns) {
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
builder.setTables(FTS_VIRTUAL_TABLE);
builder.setProjectionMap(mColumnMap);
Cursor cursor = builder.query(mDatabaseOpenHelper.getReadableDatabase(),
columns, selection, selectionArgs, null, null, null);
if (cursor == null) {
return null;
} else if (!cursor.moveToFirst()) {
cursor.close();
return null;
}
return cursor;
}
private static class DictionaryOpenHelper extends SQLiteOpenHelper {
private final Context mHelperContext;
private SQLiteDatabase mDatabase;
private static final String FTS_TABLE_CREATE =
"CREATE VIRTUAL TABLE " + FTS_VIRTUAL_TABLE +
" USING fts3 (" +
KEY_WORD + ", " +
KEY_DEFINITION +
KEY_EXAMPLE + ");";
DictionaryOpenHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
mHelperContext = context;
}
#Override
public void onCreate(SQLiteDatabase db) {
mDatabase = db;
mDatabase.execSQL(FTS_TABLE_CREATE);
loadDictionary();
}
private void loadDictionary() {
new Thread(new Runnable() {
public void run() {
try {
loadWords();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).start();
}
private void loadWords() throws IOException {
Log.d(TAG, "Loading words...");
final Resources resources = mHelperContext.getResources();
InputStream inputStream = resources.openRawResource(R.raw.definitions);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
String line;
while ((line = reader.readLine()) != null) {
String[] strings = TextUtils.split(line, "-");
if (strings.length < 2) continue;
long id = addWord(strings[0].trim(), strings[1].trim(), strings[2].trim());
if (id < 0) {
Log.e(TAG, "unable to add word: " + strings[0].trim());
}
}
} finally {
reader.close();
}
Log.d(TAG, "DONE loading words.");
}
public long addWord(String word, String definition, String example) {
ContentValues initialValues = new ContentValues();
initialValues.put(KEY_WORD, word);
initialValues.put(KEY_DEFINITION, definition);
initialValues.put(KEY_EXAMPLE, example);
return mDatabase.insert(FTS_VIRTUAL_TABLE, null, initialValues);
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+ newVersion + ", which will destroy all old data");
db.execSQL("DROP TABLE IF EXISTS " + FTS_VIRTUAL_TABLE);
onCreate(db);
}
}
}
DictionaryProvider.Java
public class DictionaryProvider extends ContentProvider {
String TAG = "DictionaryProvider";
public static String AUTHORITY = "com.pesquisa.dicionario.searchabledict.DictionaryProvider";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/dictionary");
public static final String WORDS_MIME_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE +
"/vnd.pesquisa.dicionario.searchabledict";
public static final String DEFINITION_MIME_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE +
"/vnd.pesquisa.dicionario.searchabledict";
private DictionaryDatabase mDictionary;
private static final int SEARCH_WORDS = 0;
private static final int GET_WORD = 1;
private static final int SEARCH_SUGGEST = 2;
private static final int REFRESH_SHORTCUT = 3;
private static final UriMatcher sURIMatcher = buildUriMatcher();
private static UriMatcher buildUriMatcher() {
UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
matcher.addURI(AUTHORITY, "dictionary", SEARCH_WORDS);
matcher.addURI(AUTHORITY, "dictionary/#", GET_WORD);
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, REFRESH_SHORTCUT);
matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_SHORTCUT + "/*", REFRESH_SHORTCUT);
return matcher;
}
#Override
public boolean onCreate() {
mDictionary = new DictionaryDatabase(getContext());
return true;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
switch (sURIMatcher.match(uri)) {
case SEARCH_SUGGEST:
if (selectionArgs == null) {
throw new IllegalArgumentException(
"selectionArgs must be provided for the Uri: " + uri);
}
return getSuggestions(selectionArgs[0]);
case SEARCH_WORDS:
if (selectionArgs == null) {
throw new IllegalArgumentException(
"selectionArgs must be provided for the Uri: " + uri);
}
return search(selectionArgs[0]);
case GET_WORD:
return getWord(uri);
case REFRESH_SHORTCUT:
return refreshShortcut(uri);
default:
throw new IllegalArgumentException("Unknown Uri: " + uri);
}
}
private Cursor getSuggestions(String query) {
query = query.toLowerCase();
String[] columns = new String[] {
BaseColumns._ID,
DictionaryDatabase.KEY_WORD,
SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID};
return mDictionary.getWordMatches(query, columns);
}
private Cursor search(String query) {
query = query.toLowerCase();
String[] columns = new String[] {
BaseColumns._ID,
DictionaryDatabase.KEY_WORD,
DictionaryDatabase.KEY_DEFINITION,
DictionaryDatabase.KEY_EXAMPLE};
return mDictionary.getWordMatches(query, columns);
}
private Cursor getWord(Uri uri) {
String rowId = uri.getLastPathSegment();
String[] columns = new String[] {
DictionaryDatabase.KEY_WORD,
DictionaryDatabase.KEY_DEFINITION,
DictionaryDatabase.KEY_EXAMPLE};
return mDictionary.getWord(rowId, columns);
}
private Cursor refreshShortcut(Uri uri) {
String rowId = uri.getLastPathSegment();
String[] columns = new String[] {
BaseColumns._ID,
DictionaryDatabase.KEY_WORD,
DictionaryDatabase.KEY_DEFINITION,
DictionaryDatabase.KEY_EXAMPLE,
SearchManager.SUGGEST_COLUMN_SHORTCUT_ID,
SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID};
return mDictionary.getWord(rowId, columns);
}
#Override
public String getType(Uri uri) {
switch (sURIMatcher.match(uri)) {
case SEARCH_WORDS:
return WORDS_MIME_TYPE;
case GET_WORD:
return DEFINITION_MIME_TYPE;
case SEARCH_SUGGEST:
return SearchManager.SUGGEST_MIME_TYPE;
case REFRESH_SHORTCUT:
return SearchManager.SHORTCUT_MIME_TYPE;
default:
throw new IllegalArgumentException("Unknown URL " + uri);
}
}
#Override
public Uri insert(Uri uri, ContentValues values) {
throw new UnsupportedOperationException();
}
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException();
}
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException();
}
}
SenseDictionary.Java (the Main Activity)
public class SenseDictionary extends Activity {
private TextView mTextView;
private ListView mListView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.main);
mTextView = (TextView) findViewById(R.id.text);
mListView = (ListView) findViewById(R.id.list);
handleIntent(getIntent());
}
#Override
protected void onNewIntent(Intent intent) {
handleIntent(intent);
}
private void handleIntent(Intent intent) {
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
Intent wordIntent = new Intent(this, WordActivity.class);
wordIntent.setData(intent.getData());
startActivity(wordIntent);
} else if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
String query = intent.getStringExtra(SearchManager.QUERY);
showResults(query);
}
}
private void showResults(String query) {
Cursor cursor = managedQuery(DictionaryProvider.CONTENT_URI, null, null,
new String[] {query}, null);
if (cursor == null) {
mTextView.setText(getString(R.string.no_results, new Object[] {query}));
} else {
int count = cursor.getCount();
String countString = getResources().getQuantityString(R.plurals.search_results,
count, new Object[] {count, query});
mTextView.setText(countString);
String[] from = new String[] { DictionaryDatabase.KEY_WORD,
DictionaryDatabase.KEY_DEFINITION,
DictionaryDatabase.KEY_EXAMPLE};
int[] to = new int[] { R.id.word,
R.id.definition,
R.id.example};
SimpleCursorAdapter words = new SimpleCursorAdapter(this,
R.layout.result, cursor, from, to);
mListView.setAdapter(words);
mListView.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent wordIntent = new Intent(getApplicationContext(), WordActivity.class);
Uri data = Uri.withAppendedPath(DictionaryProvider.CONTENT_URI,
String.valueOf(id));
wordIntent.setData(data);
startActivity(wordIntent);
}
});
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main, menu);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchView searchView = (SearchView) menu.findItem(R.id.search).getActionView();
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
searchView.setIconifiedByDefault(false);
}
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.search:
onSearchRequested();
return true;
default:
return false;
}
}
}
WordActivity
public class WordActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.word);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
ActionBar actionBar = getActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
}
Uri uri = getIntent().getData();
Cursor cursor = managedQuery(uri, null, null, null, null);
if (cursor == null) {
finish();
} else {
cursor.moveToFirst();
TextView word = (TextView) findViewById(R.id.word);
TextView definition = (TextView) findViewById(R.id.definition);
TextView example = (TextView) findViewById(R.id.example);
int wIndex = cursor.getColumnIndexOrThrow(DictionaryDatabase.KEY_WORD);
int dIndex = cursor.getColumnIndexOrThrow(DictionaryDatabase.KEY_DEFINITION);
int eIndex = cursor.getColumnIndexOrThrow(DictionaryDatabase.KEY_EXAMPLE);
word.setText(cursor.getString(wIndex));
definition.setText(cursor.getString(dIndex));
example.setText(cursor.getString(eIndex));
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main, menu);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchView searchView = (SearchView) menu.findItem(R.id.search).getActionView();
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
searchView.setIconifiedByDefault(false);
}
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.search:
onSearchRequested();
return true;
case android.R.id.home:
Intent intent = new Intent(this, SenseDictionary.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
return true;
default:
return false;
}
}
}
I would like to help you, but there are some issues.
First: You only posted the question an hour ago. In general, the stackoverflow community is not eagerly waiting for the next question to come in. You'll have to be patient.
Second: You've only posted your program. There may be someone out there who's willing to go through it step by step to figure out the problem, and I wish him or her luck, but I'd hesitate to do it. stackoverflow isn't my full-time job, so I can't spend 15-20 minutes just figuring out what (if anything) your program does.
It's not a simple program. You have a database, a content provider, and two activities.
Third: Your program doesn't have a single comment. Makes it even harder to debug.
Fourth: You said "I've tried to create a new table column: KEY_EXAMPLE. But it doesn't work." What happens? Why do you think it's not working? What do you expect it to do?
stackoverflow "works" when you have a specific question or problem that has you stumped. It works the worst when you ask, in essence, "fix my program for me".
Having said all this, I'll venture an off-the-wall guess: Did you run this app before and create a previous version of the database? If so, I don't see any place that you're deleting the previous version. You won't be able to create a new version, including the new column, until you delete the old version. You can do this in Settings > Apps > appname > Clear Data, or you can put it into the program itself.
Good luck!
I am implementing DictionaryDatabase in andriod. At that time i have an EditText View in which i type a word and matching word comes from the database. Now problem is that my database is too large.Like for Word "A" i have from 200 to 300 words. At first time on installation of app the database have to be loaded which takes 1 to 3 minutes and its horrible. Now i want that on after app installation or on click on EditTex means on focus i should call an asyntask with ProgressDialogue box. Do loadWords in database and on completion dismiss the dialogue. I know about async task but i don't know how to use async for my that code
here is the code for Database Loading Class:
/**
* Contains logic to return specific words from the dictionary, and
* load the dictionary table when it needs to be created.
*/
public class DictionaryDatabase extends SQLiteOpenHelper {
private static final String TAG = "DictionaryDatabase";
final static String DB_DESTINATION = "/data/data/YOUR_PACKAGE_NAME/databases/dictionary.db";
private DictionaryDatabase mDictionary;
//****FOr Dictionary****//
static int [] raw_textFiles={R.raw.a,R.raw.b,R.raw.c,
R.raw.d,R.raw.e,R.raw.f,
R.raw.g,R.raw.h,R.raw.i,
R.raw.j,R.raw.k,R.raw.l,
R.raw.m,R.raw.n,R.raw.o,
R.raw.p,R.raw.q,R.raw.r,
R.raw.s,R.raw.t,R.raw.u,
R.raw.v,R.raw.w,R.raw.x,
R.raw.y,R.raw.z};
//Array for Parsing Use
ArrayList<String> Words = new ArrayList<String>();
private static String word;
private final Context mHelperContext;
private SQLiteDatabase mDatabase;
private ProgressDialog dialog;
//The columns we'll include in the dictionary table
public static final String KEY_WORD = SearchManager.SUGGEST_COLUMN_TEXT_1;
public static final String KEY_DEFINITION = SearchManager.SUGGEST_COLUMN_TEXT_2;
private static final String DATABASE_NAME = "dictionary";
private static final String FTS_VIRTUAL_TABLE = "FTSdictionary";
private static final int DATABASE_VERSION = 2;
private static final HashMap<String,String> mColumnMap = buildColumnMap();
private static final String FTS_TABLE_CREATE =
"CREATE VIRTUAL TABLE " + FTS_VIRTUAL_TABLE +
" USING fts3 (" +
KEY_WORD + ", " +
KEY_DEFINITION + ");";
/**
* Constructor
* #param context The Context within which to work, used to create the DB
*/
public DictionaryDatabase(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
mHelperContext = context;
}
/**
* Builds a map for all columns that may be requested, which will be given to the
* SQLiteQueryBuilder. This is a good way to define aliases for column names, but must include
* all columns, even if the value is the key. This allows the ContentProvider to request
* columns w/o the need to know real column names and create the alias itself.
*/
private static HashMap<String,String> buildColumnMap() {
HashMap<String,String> map = new HashMap<String,String>();
map.put(KEY_WORD, KEY_WORD);
map.put(KEY_DEFINITION, KEY_DEFINITION);
map.put(BaseColumns._ID, "rowid AS " +
BaseColumns._ID);
map.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID, "rowid AS " +
SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
map.put(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID, "rowid AS " +
SearchManager.SUGGEST_COLUMN_SHORTCUT_ID);
return map;
}
/**
* Returns a Cursor positioned at the word specified by rowId
*
* #param rowId id of word to retrieve
* #param columns The columns to include, if null then all are included
* #return Cursor positioned to matching word, or null if not found.
*/
public Cursor getWord(String rowId, String[] columns) {
String selection = "rowid = ?";
String[] selectionArgs = new String[] {rowId};
return query(selection, selectionArgs, columns);
/* This builds a query that looks like:
* SELECT <columns> FROM <table> WHERE rowid = <rowId>
*/
}
/**
* Returns a Cursor over all words that match the given query
*
* #param query The string to search for
* #param columns The columns to include, if null then all are included
* #return Cursor over all words that match, or null if none found.
*/
public Cursor getWordMatches(String query, String[] columns) {
String selection = KEY_WORD + " MATCH ?";
String[] selectionArgs = new String[] {query+"*"};
return query(selection, selectionArgs, columns);
/* This builds a query that looks like:
* SELECT <columns> FROM <table> WHERE <KEY_WORD> MATCH 'query*'
* which is an FTS3 search for the query text (plus a wildcard) inside the word column.
*
* - "rowid" is the unique id for all rows but we need this value for the "_id" column in
* order for the Adapters to work, so the columns need to make "_id" an alias for "rowid"
* - "rowid" also needs to be used by the SUGGEST_COLUMN_INTENT_DATA alias in order
* for suggestions to carry the proper intent data.
* These aliases are defined in the DictionaryProvider when queries are made.
* - This can be revised to also search the definition text with FTS3 by changing
* the selection clause to use FTS_VIRTUAL_TABLE instead of KEY_WORD (to search across
* the entire table, but sorting the relevance could be difficult.
*/
}
/**
* Performs a database query.
* #param selection The selection clause
* #param selectionArgs Selection arguments for "?" components in the selection
* #param columns The columns to return
* #return A Cursor over all rows matching the query
*/
private Cursor query(String selection, String[] selectionArgs, String[] columns) {
/* The SQLiteBuilder provides a map for all possible columns requested to
* actual columns in the database, creating a simple column alias mechanism
* by which the ContentProvider does not need to know the real column names
*/
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
builder.setTables(FTS_VIRTUAL_TABLE);
builder.setProjectionMap(mColumnMap);
Cursor cursor = builder.query(getReadableDatabase(),
columns, selection, selectionArgs, null, null, null);
if (cursor == null) {
return null;
} else if (!cursor.moveToFirst()) {
cursor.close();
return null;
}
return cursor;
}
/**
* This creates/opens the database.
*/
#Override
public void onCreate(SQLiteDatabase db) {
mDatabase = db;
Log.i("PATH",""+mDatabase.getPath());
mDatabase.execSQL(FTS_TABLE_CREATE);
// loadDictionary();
}
/**
* Starts a thread to load the database table with words
*/
private void loadDictionary() {
new Thread(new Runnable() {
public void run() {
try {
loadWords();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).start();
}
public void loadWords() throws IOException {
Log.d(TAG, "Loading words...");
for(int i=0;i<=25;i++)
{ //***//
final Resources resources = mHelperContext.getResources();
InputStream inputStream = resources.openRawResource(raw_textFiles[i]);
//InputStream inputStream = resources.openRawResource(R.raw.definitions);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
StringBuilder sb = new StringBuilder();
while ((word = reader.readLine()) != null)
{
sb.append(word);
// Log.i("WORD in Parser", ""+word);
}
String contents = sb.toString();
StringTokenizer st = new StringTokenizer(contents, "||");
while (st.hasMoreElements()) {
String row = st.nextElement().toString();
String title = row.substring(0, row.indexOf("$$$"));
String desc = row.substring(row.indexOf("$$$") + 3);
// Log.i("Strings in Database",""+title+""+desc);
long id = addWord(title,desc);
if (id < 0) {
Log.e(TAG, "unable to add word: " + title);
}
}
} finally {
reader.close();
}
} //***//
Log.d(TAG, "DONE loading words.");
}
/**
* Add a word to the dictionary.
* #return rowId or -1 if failed
*/
public long addWord(String word, String definition) {
ContentValues initialValues = new ContentValues();
initialValues.put(KEY_WORD, word);
initialValues.put(KEY_DEFINITION, definition);
return mDatabase.insert(FTS_VIRTUAL_TABLE, null, initialValues);
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+ newVersion + ", which will destroy all old data");
db.execSQL("DROP TABLE IF EXISTS " + FTS_VIRTUAL_TABLE);
onCreate(db);
}
}
UPDATED:
EditTEx search;
//::::::::::::::::::::::TextWatcher For EditTextView :::::::::::::::::::::://
TextWatcher myTextWatcher = new TextWatcher() {
#SuppressLint("NewApi")
#Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
query= (search.getText().toString());
char character = 0;
if(query.isEmpty()==false)
{
character=query.toLowerCase().charAt(0);
}
if (start==0) {
//Log.i("query",""+query);
Cursor cursor = managedQuery(DictionaryProvider.CONTENT_URI, null, null,new String[] {query}, null);
viewFlipper.showNext();
}
listAdapter.notifyDataSetChanged();
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
#SuppressLint("NewApi")
#Override
public void afterTextChanged(Editable s) {
query= s.toString();
char character = 0;
if(query.isEmpty()==false)
{
character=query.toLowerCase().charAt(0);
}
Cursor cursor = managedQuery(DictionaryProvider.CONTENT_URI, null, null,new String[] {query}, null);
listAdapter.getFilter().filter(s);
}
};
Here is Provider Class:
*/
private static UriMatcher buildUriMatcher() {
UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
// to get definitions...
matcher.addURI(AUTHORITY, "dictionary", SEARCH_WORDS);
matcher.addURI(AUTHORITY, "dictionary/#", GET_WORD);
// to get suggestions...
matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, SEARCH_SUGGEST);
matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", SEARCH_SUGGEST);
/* The following are unused in this implementation, but if we include
* {#link SearchManager#SUGGEST_COLUMN_SHORTCUT_ID} as a column in our suggestions table, we
* could expect to receive refresh queries when a shortcutted suggestion is displayed in
* Quick Search Box, in which case, the following Uris would be provided and we
* would return a cursor with a single item representing the refreshed suggestion data.
*/
matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_SHORTCUT, REFRESH_SHORTCUT);
matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_SHORTCUT + "/*", REFRESH_SHORTCUT);
return matcher;
}
#Override
public boolean onCreate() {
mDictionary = new DictionaryDatabase(getContext());
return true;
}
/**
* Handles all the dictionary searches and suggestion queries from the Search Manager.
* When requesting a specific word, the uri alone is required.
* When searching all of the dictionary for matches, the selectionArgs argument must carry
* the search query as the first element.
* All other arguments are ignored.
*/
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
// Use the UriMatcher to see what kind of query we have and format the db query accordingly
switch (sURIMatcher.match(uri)) {
case SEARCH_SUGGEST:
if (selectionArgs == null) {
throw new IllegalArgumentException(
"selectionArgs must be provided for the Uri: " + uri);
}
return getSuggestions(selectionArgs[0]);
case SEARCH_WORDS:
if (selectionArgs == null) {
throw new IllegalArgumentException(
"selectionArgs must be provided for the Uri: " + uri);
}
return search(selectionArgs[0]);
case GET_WORD:
return getWord(uri);
case REFRESH_SHORTCUT:
return refreshShortcut(uri);
default:
throw new IllegalArgumentException("Unknown Uri: " + uri);
}
}
private Cursor getSuggestions(String query) {
query = query.toLowerCase();
String[] columns = new String[] {
BaseColumns._ID,
DictionaryDatabase.KEY_WORD,
DictionaryDatabase.KEY_DEFINITION,
/* SearchManager.SUGGEST_COLUMN_SHORTCUT_ID,
(only if you want to refresh shortcuts) */
SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID};
return mDictionary.getWordMatches(query, columns);
}
private Cursor search(String query) {
query = query.toLowerCase();
String[] columns = new String[] {
BaseColumns._ID,
DictionaryDatabase.KEY_WORD,
DictionaryDatabase.KEY_DEFINITION};
return mDictionary.getWordMatches(query, columns);
}
private Cursor getWord(Uri uri) {
String rowId = uri.getLastPathSegment();
String[] columns = new String[] {
DictionaryDatabase.KEY_WORD,
DictionaryDatabase.KEY_DEFINITION};
return mDictionary.getWord(rowId, columns);
}
private Cursor refreshShortcut(Uri uri) {
/* This won't be called with the current implementation, but if we include
* {#link SearchManager#SUGGEST_COLUMN_SHORTCUT_ID} as a column in our suggestions table, we
* could expect to receive refresh queries when a shortcutted suggestion is displayed in
* Quick Search Box. In which case, this method will query the table for the specific
* word, using the given item Uri and provide all the columns originally provided with the
* suggestion query.
*/
String rowId = uri.getLastPathSegment();
String[] columns = new String[] {
BaseColumns._ID,
DictionaryDatabase.KEY_WORD,
DictionaryDatabase.KEY_DEFINITION,
SearchManager.SUGGEST_COLUMN_SHORTCUT_ID,
SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID};
return mDictionary.getWord(rowId, columns);
}
/**
* This method is required in order to query the supported types.
* It's also useful in our own query() method to determine the type of Uri received.
*/
#Override
public String getType(Uri uri) {
switch (sURIMatcher.match(uri)) {
case SEARCH_WORDS:
return WORDS_MIME_TYPE;
case GET_WORD:
return DEFINITION_MIME_TYPE;
case SEARCH_SUGGEST:
return SearchManager.SUGGEST_MIME_TYPE;
case REFRESH_SHORTCUT:
return SearchManager.SHORTCUT_MIME_TYPE;
default:
throw new IllegalArgumentException("Unknown URL " + uri);
}
}
There is a number of tutorials like
http://www.javasrilankansupport.com/2012/11/asynctask-android-example-asynctask-in.html
The problem is that the life cycle of an AsyncTask differs from the Activity one: by the moment that AsyncTask completes, the Activity may be already removed from the screen and replaced by another Activity. An activity is re-created e.g. when the screen orientation changes. But it is the old, not visible anymore, Activity that gets notified by the AsyncTask about operation completion.
See the discussion at:
Is AsyncTask really conceptually flawed or am I just missing something?
(Note the incompatibility between versions in the default AsyncTask behaviour: prior to API 11, different AsyncTasks were executed in parallel; since API 11, they are executed serially on a single worker thread.)
The solution is to invoke long operations from Model (in MVC sense) rather than from Controller (=Activity), and this most likely will not be an AsyncTask.
That being said, some code:
public class AsyncTaskTestActivity extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
Log.d("~~~~","~~~onCreate ~~~ "+this);
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
public void onStartButton(View view) {
Log.d("~~~~","~~~onStartButton {");
class MyTask extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... params) {
// TODO Auto-generated method stub
Log.d("~~~~","~~~doInBackground started");
try {
for (int i=0; i<10; i++) {
Log.d("~~~~","~~~sleep#"+i);
Thread.sleep(200);
}
Log.d("~~~~","~~~sleeping over");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Log.d("~~~~","~~~doInBackground ended");
return null;
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
taskDone();
}
}
MyTask task = new MyTask();
task.execute(null);
Log.d("~~~~","~~~onStartButton }");
}
private void taskDone() {
Log.d("~~~~","\n\n~~~taskDone ~~~ "+this+"\n\n");
}
public void onStopButton(View view) {
Log.d("~~~~","~~~onStopButton {");
MagicAppRestart.doRestart(this);
Log.d("~~~~","~~~onStopButton }");
}
public void onPause() { Log.d("~~~~","~~~onPause ~~~ "+this); super.onPause(); }
public void onStop() { Log.d("~~~~","~~~onStop ~~~ "+this); super.onPause(); }
public void onDestroy() { Log.d("~~~~","~~~onDestroy ~~~ "+this); super.onDestroy(); }
}
Basically, you have to refactor your app so that arrival of the AsyncTask data is an asynchronous event. And if an operation is unacceptably long, it will not become faster in an AsyncTask.