How do I implement autocomplete with cursoradapter - java

I have an SQLite database containing 2 tables 4000+ rows each used for autocomplete. I saw very simple examples that use an array of strings to provide autocomplete or they use the list of contacts to do the same. Obviously none of these work in my case. How do I use my own SQLite database with my own autocomplete data, for the autocomplete. Do I have to create content providers? How? Please give me some examples because I couldn't find any. I have managed to override SQLiteOpenHelper to copy the database from the assets folder to the /data/data/MY_PACKAGE/databases/ folder on the android. I have created a custom CursorAdapter that uses my custom SQLiteOpenHelper and returns a cursor from runQueryOnBackgroundThread. I get strange errors about some _id column missing. I have added the _id column to my tables. I also don't understand what is the Filterable interface doing and when does my data get filtered. What methods/classes do I need to override? Thanks.

It works.
You need the SQLiteOpenHelper from here. You basically have to copy your database into a specific folder from your assets folder. Then you need a custom CursorAdapter that uses your custom SQLiteOpenHelper.
Here is the onCreate method for my activity.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.search);
KeywordsCursorAdapter kwadapter = new KeywordsCursorAdapter(this, null);
txtKeyword = (AutoCompleteTextView)this.findViewById(R.id.txtKeyword);
txtKeyword.setAdapter(kwadapter);
txtCity = (AutoCompleteTextView)this.findViewById(R.id.txtCity);
btnSearch = (Button)this.findViewById(R.id.btnSearch);
btnSearch.setOnClickListener(this);
}
Here is the cursoradapter. You can pass null for cursor when constructing.
public class KeywordsCursorAdapter extends CursorAdapter {
private Context context;
public KeywordsCursorAdapter(Context context, Cursor c) {
super(context, c);
this.context = context;
}
//I store the autocomplete text view in a layout xml.
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflater.inflate(R.layout.keyword_autocomplete, null);
return v;
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
String keyword = cursor.getString(cursor.getColumnIndex("keyword"));
TextView tv = (TextView)view.findViewById(R.id.txtAutocomplete);
tv.setText(keyword);
}
//you need to override this to return the string value when
//selecting an item from the autocomplete suggestions
//just do cursor.getstring(whatevercolumn);
#Override
public CharSequence convertToString(Cursor cursor) {
//return super.convertToString(cursor);
String value = "";
switch (type) {
case Keywords:
value = cursor.getString(DatabaseHelper.KEYWORD_COLUMN);
break;
case Cities:
value = cursor.getString(DatabaseHelper.CITY_COLUMN);
break;
}
return value;
}
#Override
public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
//return super.runQueryOnBackgroundThread(constraint);
String filter = "";
if (constraint == null) filter = "";
else
filter = constraint.toString();
//I have 2 DB-s and the one I use depends on user preference
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
//String selectedCountryCode = prefs.getString("selectedCountry", "GB");
String selectedCountryCode = prefs.getString(context.getString(R.string.settings_selected_country), "GB");
selectedCountryCode += "";
//Here i have a static SQLiteOpenHelper instance that returns a cursor.
Cursor cursor = MyApplication.getDbHelpers().get(selectedCountryCode.toLowerCase()).getKeywordsCursor(filter);
return cursor;
}
}
Here is the part that returns the cursor: it's just a select with a like condition.
public class DatabaseHelper extends SQLiteOpenHelper {
...
public synchronized Cursor getKeywordsCursor (String prefix) {
if (database == null) database = this.getReadableDatabase();
String[] columns = {"_id", "keyword"};
String[] args = {prefix};
Cursor cursor;
cursor = database.query("keywords", columns, "keyword like '' || ? || '%'", args, null, null, "keyword", "40");
int idcol = cursor.getColumnIndexOrThrow("_id");
int kwcol = cursor.getColumnIndexOrThrow("keyword");
while(cursor.moveToNext()) {
int id = cursor.getInt(idcol);
String kw = cursor.getString(kwcol);
Log.i("keyword", kw);
}
cursor.moveToPosition(-1);
return cursor;
}
...
}
You can also create a custom content provider but in this case it would be just another useless class you need to override.

Related

Error querying sqlite database in android studio

I have a problem in my application, to see if there is someone who can help me.
It turns out that in my application I have made a database with SQLite that has two tables, one for players and one for results.
#Override
public void onCreate(SQLiteDatabase BaseDeDades) {
BaseDeDades.execSQL("create table jugadors(codi int primary key, nom text, cognoms text, data date, club text, categoria text)");
BaseDeDades.execSQL("create table resultats(codipuntuacio int primary key, codijugador int,codiexercici text, puntuacio text, temps long, data date)");
}
To consult the first of the tables (players) that shows a list of all the players entered in the database, I did it as follows.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_llistajug);
Llistajugadors();
}
public void Llistajugadors(){
AdminSQLiteOpenHelper admin = new AdminSQLiteOpenHelper(this,"administracio",null,1);
SQLiteDatabase BaseDeDades = admin.getWritableDatabase();
if(BaseDeDades!=null){
Cursor c= BaseDeDades.rawQuery("select * from jugadors",null);
int quantitat = c.getCount();
int i=0;
String[] array = new String[quantitat];
if (c.moveToFirst()){
do{
String linia = c.getInt(0)+"-"+c.getString(1);
array[i] = linia;
i++;
}while(c.moveToNext());
}
ArrayAdapter<String>adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,array);
final ListView llista = (ListView)findViewById(R.id.llista);
llista.setAdapter(adapter);
llista.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent intent = getIntent();
intent.putExtra("dato2", llista.getItemAtPosition(position).toString());
setResult(RESULT_OK,intent);
finish();
}
});
}
}
}
The problem has arisen when trying to consult the data of the other table (results) since I have tried to do it the same way
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_llistajug);
jugador = getIntent().getStringExtra("name");
exercici = getIntent().getStringExtra("exercise");
nom = jugador.split("-")[1];
codi = Integer.parseInt(jugador.split("-")[0]);
Resultats();
}
public void Resultats() {
AdminSQLiteOpenHelper admin = new AdminSQLiteOpenHelper(this, "administracio", null, 1);
SQLiteDatabase BaseDeDades = admin.getWritableDatabase();
if (BaseDeDades != null) {
Cursor c2 = BaseDeDades.rawQuery("select * from resultats",null);
int quantitat2 = c2.getCount();
int i2 = 0;
String[] array2 = new String[quantitat2];
if (c2.moveToFirst()) {
do {
String linia2 = c2.getInt(0) + "-" + c2.getString(1);
array2[i2] = linia2;
i2++;
} while (c2.moveToNext());
}
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, array2);
final ListView llista2 = (ListView) findViewById(R.id.llista2);
llista2.setAdapter(adapter);
}
}
}
But when executing this activity, in this case the application stops.
Does anyone know why if I have done it the same way? Thank you
This is the error that appears in Logcat when executing the activity:
Logcat error
Thanks, the bug was fixed. But now I have another problem with the query. How can I make the query for a string?
codijugador i codi are integers and it works correctly but adding another parameter codiexercici = exerici which are strings gives me an error, are they not done the same way?
Thanks, the bug was fixed. But now I have another problem with the query. How can I make the query for a string?
Thanks, the bug was fixed. But now I have another problem with the query. How can I make the query for a string?
co-player i codi are integers and it works correctly but adding another parameter codiexercici = exerici which are strings gives me an error, are they not done the same way?
Cursor c = BaseDeDades.rawQuery("select * from resultats where codijugador = "+codi+" and codiexercici="+exercici, null);
String must be enclosed inside single quotes, but this is something that you should not do by concatenating the parameters and the single quotes.
Use ? placeholders for the parameters and the 2nd argument of rawQuery() to pass them:
Cursor c = BaseDeDades.rawQuery(
"select * from resultats where codijugador = ? and codiexercici = ?",
new String[] {String.valueOf(codi), exercici}
);

SQLiteAssetHelper - 'No such table' error

I have prepopulated a database with DB Browser for SQLite and tried to retrieve data from it using SQLiteAssetHelper (installed and used it using the guide at https://github.com/jgilfelt/android-sqlite-asset-helper ) and got a "No such table" error. I am sure that there is a table named that way in the database. I've tried to debug it and saw that I leave the DATABASE_VERSION as equal to one than it detects that there is already a database and doesn't copy it, though if I change the database version and setForceUpdate to (true) then it copies it, but I get the same error anyway. What should I do for SQLiteAssetHelper to copy my database properly and get rid of that annoying error?
Code:
DataBaseHelper class:
public class DataBaseHelper extends SQLiteAssetHelper {
private static final String DATABASE_NAME = "DonRoll";
private static final int DATABASE_VERSION = 2;
private static DataBaseHelper instance;
public static DataBaseHelper getInstance(Context context){
if (null == instance){
instance = new DataBaseHelper(context);
}
return instance;
}
private DataBaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
public Cursor getAllCategoryNames() {
setForcedUpgrade(2);
SQLiteDatabase db = getReadableDatabase();
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables("MenuCategories");
Cursor c = qb.query(db, null, null, null, null, null, null);
c.moveToFirst();
return c;
}
}
Activity class:
public class CategoryListActivity extends ListActivity {
DataBaseHelper db;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_category_list);
db = DataBaseHelper.getInstance(this);
getData();
}
#SuppressWarnings("deprecation")
private void getData() {
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
android.R.layout.simple_list_item_1,
db.getAllCategoryNames(),
new String[] { "value" },
new int[] { android.R.id.text1 });
ListView listView = (ListView) findViewById(android.R.id.list);
listView.setAdapter(adapter);
}
}
Problem solved: I've used adb pull to get the database file from the assets folder and it somehow got corrupted - it had only the "android_metadata" table. I don't know why this happened (maybe because I renamed it after I have put it into Android studio?), but once I have deleted this file and replaced it with a proper one everything worked just fine.

Android - Wanting to alternate icon images in a listview using cursorLoader

I have an application that displays a listView of contacts sorted by Last, then first names. Beside each contact is an image (icon). There are 3 kinds of contacts for which I'd like to display 3 different images (customers/suppliers/other) I have a default image now that is set to customer. I'm wondering if there's a way using the cusorLoader shown below to alternate images on the fly, or whether it would just be best to add a method involving a cursor in my onResume. (onResume is called each time I need to display the images). I believe simpleCursorAdapter can only take textViews as args, so if it's possible, maybe a compound textview/image would work. My icons are not stored in the database, just in the drawables.
Thanks in advance for any replies.
#Override
protected void onResume() {
super.onResume();
//Starts a new or restarts an existing Loader in this manager
getLoaderManager().restartLoader(0, null, this);
}
/*
* The fillData method binds the simpleCursorAadapter to the listView.
*/
private void fillData() {
String[] from = new String[] { ContactsDB.COLUMN_LAST_NAME, ContactsDB.COLUMN_FIRST_NAME };
//The XML views that the data will be bound to:
int[] to = new int[] {R.id.label2, R.id.label};
getLoaderManager().initLoader(0, null, this);
adapter = new SimpleCursorAdapter(this, R.layout.contact_row, null, from,
to, 0);
setListAdapter(adapter);
}
// Sort the names by last name, then by first name
String orderBy = ContactsDB.COLUMN_LAST_NAME + " COLLATE NOCASE ASC"
+ "," + ContactsDB.COLUMN_FIRST_NAME + " COLLATE NOCASE ASC" ;
// Creates a new loader after the initLoader () call
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
String[] projection = { ContactsDB.ROW_ID, ContactsDB.COLUMN_LAST_NAME, ContactsDB.COLUMN_FIRST_NAME };
CursorLoader cursorLoader = new CursorLoader(this,
SomeContentProvider.CONTENT_URI, projection, null, null, orderBy);
return cursorLoader;
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Swap the new cursor in.
// (The framework will take care of closing the old cursor once we return.)
adapter.swapCursor(data); //Call requires Min API 11
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
// This is called when the last Cursor provided to onLoadFinished()
// above is about to be closed.
// Data is no longer available, delete the reference
adapter.swapCursor(null);
}
}
Here is the code I use to dynamically show a drawable on a ListView, you have to use the function setViewBinder on your adapter:
mAdapter.setViewBinder(new ViewBinder() {
public boolean setViewValue(View aView, Cursor aCursor, int aColumnIndex) {
//Modification of the icon to display in the list
if (aColumnIndex == aCursor.getColumnIndex(DatabaseHandler.RATE_EMOTION)) {
int emotionID = aCursor.getInt(aColumnIndex);
Drawable emotionDrawable = resources.getDrawable(R.drawable.ic_unknown_rate);
//if emotion is set
if(emotionID > 0){
String emotionDrawablePath = "ic_smi" + emotionID;
int emotionDrawableID = resources.getIdentifier(emotionDrawablePath,"drawable", getPackageName());
//if a drawable is found
if(emotionDrawableID > 0){
emotionDrawable = resources.getDrawable(emotionDrawableID);
}
}
ImageView emotionImage = (ImageView) aView;
emotionImage.setImageDrawable(emotionDrawable);
return true;
}
return false;
}
});
You can see in this example that I change the drawable according to the data I get from the cursor for every row.

Android SimpleCursorAdapter.ViewBinder not updating bound TextView

I have a SimpleCursorAdapter and I'm attempting to bind a SimpleCursorAdapter.ViewBinder to it. Below is the code that I am using.
// Setup Cursor and SimpleCursorAdapter, called in Activity onResume()
Cursor userCursor = getUserProfile(userEmail);
mAdapter = new SimpleCursorAdapter(this,
R.layout.user_profile,
userCursor,
new String[] {
StackOverFlow.Users.FULL_NAME,
StackOverFlow.Users.EMAIL },
new int[] {
R.id.txtvwProfileUserFullName,
R.id.txtvwProfileOtherUserInfo } );
mAdapter.setViewBinder(new UserProfileViewBinder());
My UserProfileViewBinder class:
private class UserProfileViewBinder implements SimpleCursorAdapter.ViewBinder {
#Override
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
TextView tv;
switch (columnIndex) {
case 1: // TODO: Magic Numbers, bad!
tv = (TextView) view.findViewById(R.id.txtvwProfileUserFullName);
String userFullName = cursor.getString(columnIndex);
tv.setText(userFullName);
break;
case 2: // TODO: Magic Numbers, bad!
tv = (TextView) view.findViewById(R.id.txtvwProfileOtherUserInfo);
String userEmail = cursor.getString(columnIndex);
tv.setText(userEmail);
break;
}
return true;
}
}
Issue
When I load the activity, the TextView's do not get updated with the data from the userCursor. I can confirm that userCursor is in fact pointing to a valid row in the backing SQLite database, and has data, but the setViewValue method in UserProfileViewBinder never appears to be executing. I'm not sure what I'm doing wrong, or neglecting to include.
As an alternative, I'm doing the following to set the TextView's text:
if (mAdapter.getCursor().moveToFirst()) {
mUserFullName.setText(mAdapter.getCursor().getString(
StackOverFlow.USER_FULL_NAME));
mUserOtherInfo.setText(mAdapter.getCursor().getString(
StackOverFlow.USER_EMAIL));
}
But I don't think this will automatically update the TextView's when the cursor data changes. when the ContentProvider calls setNotificationUri after executing a query, and the REST server call returns and the thread updates the row the cursor is pointing at.
Thanks in advance.
Are you planing to do other thing besides setting the text on those two TextViews? If the answer is no then you have no reason to use the ViewBinder as the SimpleCursorAdapter, by default, will set the text for you.
Anyway, I don't understand why you used the columnIndex parameter to identify the views and not the id of the view that is passed in. See if this helps:
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
if (view.getId() == R.id.txtvwProfileUserFullName) {
String userFullName = cursor.getString(columnIndex);
((TextView) view).setText(userFullName);
return true;
} else if (view.getId() == R.id.txtvwProfileOtherUserInfo) {
String userEmail = cursor.getString(columnIndex);
((TextView) view).setText(userEmail);
return true;
} else {
return false;
}
}

How can I change the Data that I got from my Database?

I get my data out of my db with the following code:
private void fillData() {
cursor = mDbAdapter.fetchAllSubjects();
startManagingCursor(cursor);
String[] from = new String[] { DatabaseAdapter.KEY_TITLE, DatabaseAdapter.KEY_LECTURER, DatabaseAdapter.KEY_BEGIN };
int[] to = new int[] { R.id.title, R.id.lecturer, R.id.time };
// Now create an array adapter and set it to display using our row
SimpleCursorAdapter subjects = new SimpleCursorAdapter(this, R.layout.subject_row, cursor, from, to);
setListAdapter(subjects);
}
Now my problem is, that I want to add 3 other columns from my db and want to get the following:
"("+DatabaseAdapter.KEY_TYPE+") "+DatabaseAdapter.KEY_TITLE
DatabaseAdapter.KEY_LECTURER
new Date(DatabaseAdapter.KEY_BEGIN)
new Date(DatabaseAdapter.KEY_END)
--> these two should be in one TextView in the way dd.MM. HH:mm (this is from BEGIN) - HH:mm (this is from END)
I don't know how I'm able to do that - please help me :)
Ok I finally figured out what you really wanted.
Instead of using "SimpleCursorAdapter" directly, you can create your own Cursor adapter, inside which you can mainipulate the data as you want.
Create a new Adapter "SubjectsAdapter.java". In this Adapter you will override the "bindView" and "newView". This allows us to apply a view to the cursor. But before doing so, gives us the opportunity to change the data from the cursor.
This will give you an idea what has to be done.
private void fillData()
{
cursor = mDbAdapter.fetchAllSubjects();
startManagingCursor(cursor);
SubjectsAdapter subjectsAdapter = new SubjectsAdapter(this, cursor);
setListAdapter(subjectsAdapter);
}
//SubjectsAdapter.java - make changes to fix bugs/compilation errors. This is untested.
public class SubjectsAdapter extends ResourceCursorAdapter
{
public SubjectsAdapter(Context context, Cursor cur) {
super(context, R.layout.subject_row, cur);
}
#Override
public View newView(Context context, Cursor cur, ViewGroup parent)
{
LayoutInflater li = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
return li.inflate(R.layout.subject_row, parent, false);
}
#Override
public void bindView(View view, Context context, Cursor cursor)
{
TextView titleText = (TextView)view.findViewById(R.id.title);
titleText.setText(cursor.getString(cursor.getColumnIndex(DatabaseAdapter.KEY_TITLE)));
//You can add code to retrieve other columns here.
//This is where you retrieve the date in long format from cursor, convert it to a required format, and then using it.
TextView beginTimeText = (TextView)view.findViewById(R.id.time);
Long lBeginDate = cursor.getLong(cursor.getColumnIndex(DatabaseAdapter.KEY_BEGIN));
String sBeginDate = getFormattedDate(lBeginDate);
beginTimeText.setText(sBeginDate);
}
private String getFormattedDate(Long lDate)
{
SimpleDateFormat smdf = new SimpleDateFormat("MM/dd/yyyy h:mm:ss a");
String sDate = smdf.format( lDate ));
return sDate;
}
}

Categories