I have a working recycle view:
public class MtbListAdapter extends RecyclerView.Adapter<MtbListAdapter.RecyclerViewHolder> implements Filterable {
private ArrayList<ApiObject> arrayList;
private ArrayList<ApiObject> arrayListFiltered;
private DatabaseHelper databaseHelper;
public MtbListAdapter(ArrayList<ApiObject> arrayList) {
this.arrayList =arrayList;
this.arrayListFiltered =arrayList;
}
#Override
public RecyclerViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.card_mtb, viewGroup, false);
return new RecyclerViewHolder(view);
}
#Override
public void onBindViewHolder(RecyclerViewHolder recyclerViewHolder, int position){
//load texts to card
recyclerViewHolder.tv_name.setText(arrayListFiltered.get(position).getName());
recyclerViewHolder.tv_length.setText(arrayListFiltered.get(position).getLength());
//is card already saved?
String s_identifier = arrayListFiltered.get(position).getId();
databaseHelper = new DatabaseHelper(recyclerViewHolder.tv_name.getContext());
boolean isSaved = databaseHelper.isSaved(s_identifier);
if (isSaved) recyclerViewHolder.iv_isSaved.setVisibility(View.VISIBLE);
else recyclerViewHolder.iv_isSaved.setVisibility(View.INVISIBLE);
}
In the onBindViewHolder I check if the item is already saved and if so place a "saved" image on the card. I do this by cheking an ID value against already saved items in my SQLite db. One thing I'm concerned about is that databaseHelper = new DatabaseHelper(recyclerViewHolder.tv_name.getContext()); gets called a lot. Question 1. Is this a concern?
To alleviate my concerns, I tried passing context through the constructor and making databaseHelper a class Object, but whatever context I passed caused my SQLite database method to crash in the databaseHelper Class. I tried passing Activity.this, application context, base context, etc. I've only been successful passing the viewHolder item context as shown. Like I said the code as written works, but I fear it is costly.
Here is the code I tried:
public class MtbListAdapter extends RecyclerView.Adapter<MtbListAdapter.RecyclerViewHolder> implements Filterable {
private Context context;
private ArrayList<ApiObject> arrayList;
private ArrayList<ApiObject> arrayListFiltered;
private DatabaseHelper databaseHelper = new DatabaseHelper(context);
public MtbListAdapter(ArrayList<ApiObject> arrayList, Context context) {
this.arrayList =arrayList;
this.arrayListFiltered =arrayList;
this.context =context;
}
#Override
public RecyclerViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.card_mtb, viewGroup, false);
return new RecyclerViewHolder(view);
}
#Override
public void onBindViewHolder(RecyclerViewHolder recyclerViewHolder, int position){
//load texts to card
recyclerViewHolder.tv_name.setText(arrayListFiltered.get(position).getName());
recyclerViewHolder.tv_length.setText(arrayListFiltered.get(position).getLength());
//is card already saved?
String s_identifier = arrayListFiltered.get(position).getId();
boolean isSaved = databaseHelper.isSaved(s_identifier);
if (isSaved) recyclerViewHolder.iv_isSaved.setVisibility(View.VISIBLE);
else recyclerViewHolder.iv_isSaved.setVisibility(View.INVISIBLE);
}
Here is my SQLite method:
//returns true if is already saved
public boolean isSaved(String s) {
SQLiteDatabase db = getReadableDatabase();
String[] columns = new String[] {KEY_IDENTIFIER};
String where = KEY_IDENTIFIER + " = ?";
String[] whereArgs = new String[] {s};
// select column_word from table where column_word = 's' limit 1;
try (Cursor c = db.query(TABLE_NAME, columns, where, whereArgs, null, null, null, "1")){
return c.moveToFirst();
}
}
Here is my error (that I only get with the code posted that passes context through the contructor). Otherwise it all works.
java.lang.NullPointerException: Attempt to invoke virtual method 'android.database.sqlite.SQLiteDatabase android.content.Context.openOrCreateDatabase(java.lang.String, int, android.database.sqlite.SQLiteDatabase$CursorFactory, android.database.DatabaseErrorHandler)' on a null object reference
You could include the context via the constrcutor and then create an instance of the helper in the constructor.
e.g. Instead of :-
public MtbListAdapter(ArrayList<ApiObject> arrayList) {
this.arrayList =arrayList;
this.arrayListFiltered =arrayList;
}
use :-
public MtbListAdapter(Context context, ArrayList<ApiObject> arrayList) {
this.arrayList =arrayList;
this.arrayListFiltered =arrayList;
databaseHelper = new DatabaseHelper(context);
}
and remove the line :-
databaseHelper = new DatabaseHelper(recyclerViewHolder.tv_name.getContext());
One thing I'm concerned about is that databaseHelper = new
DatabaseHelper(recyclerViewHolder.tv_name.getContext()); gets called a
lot. Question 1. Is this a concern?
It's not the best way simply because you are re-constructing the instance. With regard to opening the database, that is actually done not at construction but when either getReableDatabase or getWriteableDatabase is called, which will be the same as that's done when the isSaved method is invoked. The get????ableDatabase are designed to cope with multiple invocations. So, I believe that there is little to be concerned about.
However, you don't close the Cursor in the isSaved method. This should be of concern as if you have too many Cursors open then an exception will occur.
You may want to change :-
//returns true if is already saved
public boolean isSaved(String s) {
SQLiteDatabase db = getReadableDatabase();
String[] columns = new String[] {KEY_IDENTIFIER};
String where = KEY_IDENTIFIER + " = ?";
String[] whereArgs = new String[] {s};
// select column_word from table where column_word = 's' limit 1;
try (Cursor c = db.query(TABLE_NAME, columns, where, whereArgs, null, null, null, "1")){
return c.moveToFirst();
}
}
to something like :-
//returns true if is already saved
public boolean isSaved(String s) {
boolean rv = false;
SQLiteDatabase db = getReadableDatabase();
String[] columns = new String[] {KEY_IDENTIFIER};
String where = KEY_IDENTIFIER + " = ?";
String[] whereArgs = new String[] {s};
// select column_word from table where column_word = 's' limit 1;
(Cursor c = db.query(TABLE_NAME, columns, where, whereArgs, null, null, null, "1"));
if(c.moveToFirst) {
rv = true;
}
c.close()
return rv;
}
Related
I have coded a RecyclerView where I search for data from my SQLiteDatabase with the SearchAdapter. The MaterialSearchBar (PlaceHolder) only shows the names of the data from the Database. Now I want to select one item, which i choose in the searchbar and get all the other columns of that one row and store the data in a listview in another activity.
So my first question is, how can i get all data from one row, if i only have the name? Should I do it with a cursor?
And my second question is, how should i store all the data from that one row in a listview in another activity?
Thank you for your help!
enter cclass SearchViewHolder extends RecyclerView.ViewHolder{
public TextView medid,name,menge,art,nummer;
public SearchViewHolder(View itemView) {
super(itemView);
medid = (TextView) itemView.findViewById(R.id.medid);
name = (TextView) itemView.findViewById(R.id.name);
menge = (TextView) itemView.findViewById(R.id.menge);
art = (TextView) itemView.findViewById(R.id.art);
nummer = (TextView) itemView.findViewById(R.id.nummer);
}
}
public class SearchAdapter extends RecyclerView.Adapter<SearchViewHolder> {
private Context context;
private List<Drugs> drugs;
public SearchAdapter(Context context, List<Drugs> drugs) {
this.context = context;
this.drugs = drugs;
}
public SearchAdapter() {
}
#Override
public SearchViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater =LayoutInflater.from(parent.getContext());
View itemView = inflater.inflate(R.layout.medikamentensuche,parent,false);
return new SearchViewHolder(itemView);
}
#Override
public void onBindViewHolder(SearchViewHolder holder, int position) {
//String pharmaId,name,menge,art,preis,code,bezeichnung;
holder.medid.setText(toString().valueOf(drugs.get(position).getMedID()));
holder.name.setText(drugs.get(position).getName());
holder.menge.setText(drugs.get(position).getMenge());
holder.art.setText(drugs.get(position).getArt());
holder.nummer.setText(drugs.get(position).getNummer());
}
#Override
public int getItemCount() {
return drugs.size();
}
}
DataBaseOpenhelper class
public class DatabaseOpenHelper extends SQLiteAssetHelper {
private static final String DB_NAME = "medikamente.db";
private static final String TABLE = "Medikamente";
private static final int DB_VER = 1;
public static final String ID = "MedID";
public static final String NAME = "Handelsname";
public static final String MENGE = "Mengenangabe";
public static final String ART = "Mengenart";
public static final String NUMMER = "Pharmanummer";
public DatabaseOpenHelper(Context context) {
super(context, DB_NAME, null, DB_VER);
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE);
onCreate(db);
}
//neues Medikament hinzufügen
public boolean insertNewEntry (String name, String mengenangabe, String mengenart, String pharmanummer) {
SQLiteDatabase db = getWritableDatabase();
ContentValues values = new ContentValues();
values.put(NAME,name);
values.put(MENGE,mengenangabe);
values.put(ART,mengenart);
values.put(NUMMER,pharmanummer);
long result = db.insert("Medikamente",null,values);
if (result == -1)
return false;
else
return true;
}
public List<Drugs> getDrug() {
SQLiteDatabase db = getReadableDatabase();
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
Log.d("in der DrugDatabases", "SQLiteQueryBuilder successful");
//Kathi habe hier: "ATCCode", "BezeichnungATCCode" gelöscht
String [] sqlSelect = {"MedID", "Handelsname", "Mengenangabe", "Mengenart", "Pharmanummer"};
String tableName = "Medikamente";
Log.d("in der DrugDatabases", " successful" + sqlSelect);
qb.setTables(tableName);
Cursor cursor = qb.query(db, sqlSelect, null, null, null, null, null);
List<Drugs> result = new ArrayList<>();
if(cursor.moveToFirst()) {
do{
Drugs drug = new Drugs();
drug.setMedID(cursor.getInt(cursor.getColumnIndex("MedID")));
drug.setName(cursor.getString(cursor.getColumnIndex("Handelsname")));
drug.setMenge(cursor.getString(cursor.getColumnIndex("Mengenangabe")));
drug.setArt(cursor.getString(cursor.getColumnIndex("Mengenart")));
drug.setNummer(cursor.getString(cursor.getColumnIndex("Pharmanummer")));
result.add(drug);
}while (cursor.moveToNext());
}return result;
}
public List<String> getNames() {
SQLiteDatabase db = getReadableDatabase();
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
String [] sqlSelect = {"Handelsname" };
String tableName = "Medikamente";
qb.setTables(tableName);
Cursor cursor = qb.query(db, sqlSelect, null, null, null, null, null);
List<String> result = new ArrayList<>();
if(cursor.moveToFirst()) {
do{
result.add(cursor.getString(cursor.getColumnIndex("Handelsname" )));
}while (cursor.moveToNext());
}return result;
}
public List<Drugs> getDrugsByName(String name) {
SQLiteDatabase db = getReadableDatabase();
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
String [] sqlSelect = {"MedID" ,"Handelsname" ,"Mengenangabe" ,"Mengenart" ,"Pharmanummer"};
String tableName = "Medikamente";
qb.setTables(tableName);
Cursor cursor = qb.query(db, sqlSelect, "Handelsname LIKE ?",new String[]{"%"+name+"%"}, null, null, null);
List<Drugs> result = new ArrayList<>();
if(cursor.moveToFirst()) {
do{
Drugs drug = new Drugs();
drug.setMedID(cursor.getInt(cursor.getColumnIndex("MedID")));
drug.setName(cursor.getString(cursor.getColumnIndex("Handelsname")));
drug.setMenge(cursor.getString(cursor.getColumnIndex("Mengenangabe")));
drug.setArt(cursor.getString(cursor.getColumnIndex("Mengenart")));
drug.setNummer(cursor.getString(cursor.getColumnIndex("Pharmanummer")));
result.add(drug);
}while (cursor.moveToNext());
}return result;
}
}
MedSucheActivity
public class MedSucheActivity extends AppCompatActivity {
RecyclerView recyclerView;
RecyclerView.LayoutManager layoutManager;
SearchAdapter adapter;
TextView textView;
MaterialSearchBar materialSearchBar;
List<String> suggestList = new ArrayList<>();
DatabaseOpenHelper database;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_medsuchen);
Button button = (Button) findViewById(R.id.hinzufügen);
button.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(getApplicationContext(),MedTableActivity.class);
TextView suche = (TextView) findViewById(R.id.SuchMedikament);
intent.putExtra("weitergabe",suche.getText().toString());
startActivityForResult(intent,1);
//wichtig wenn man Daten zurück geben will von der 2.Activity
}
});
textView = (TextView) findViewById(R.id.SuchMedikament);
recyclerView = (RecyclerView) findViewById(R.id.recycler_search);
layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setHasFixedSize(true);
materialSearchBar = (MaterialSearchBar) findViewById(R.id.search_bar);
//textView = (TextView) findViewById(R.id.versuch);
//Datenbank
database = new DatabaseOpenHelper(this);
//Searchbar
materialSearchBar.setHint("Search");
materialSearchBar.setCardViewElevation(10);
loadSuggestList();
materialSearchBar.addTextChangeListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
List<String> suggest = new ArrayList<>();
for(String search:suggestList) {
if (search.toLowerCase().contains(materialSearchBar.getText().toLowerCase()))
suggest.add(search);
}
materialSearchBar.setLastSuggestions(suggest);
}
#Override
public void afterTextChanged(Editable s) {
}
});
materialSearchBar.setOnSearchActionListener(new MaterialSearchBar.OnSearchActionListener() {
#Override
public void onSearchStateChanged(boolean enabled) {
if(!enabled)
recyclerView.setAdapter(adapter);
}
#Override
public void onSearchConfirmed(CharSequence text) {
startSearch(text.toString());
}
#Override
public void onButtonClicked(int buttonCode) {
}
});
//init Adapter default set all result
adapter = new SearchAdapter(this,database.getDrug());
recyclerView.setAdapter(adapter);
}
private void startSearch(String text) {
adapter = new SearchAdapter(this,database.getDrugsByName(text));
recyclerView.setAdapter(adapter);
}
private void loadSuggestList() {
suggestList = database.getNames();
materialSearchBar.setLastSuggestions(suggestList);
}
public void onHinzuClick(View v) {
Log.d("msg","Auf Hinzufügen Button geklickt");
Intent intent = new Intent (getBaseContext(),MedikamentHinzufugenActivity.class);
startActivity(intent);
}
}
So my first question is, how can i get all data from one row, if i
only have the name?
And my second question is, how should i store all the data from that
one row in a listview in another activity?
If name is definitely going to be unique, which it appears that it may not be, then you can use that in conjunction with the getDrugsByName method to obtain a list of Drug objects (1 if the name is unique). So name is all that would be required and this can be passed to another activity via an Intent Extra and thus retrieved from that Intent Extra, you can then use the getDrugsbyName method in that activity to then get all the data for the row (for 2.). Of course you could also pass all values via Intent Extras.
If name isn't necessarily unique then you could use MedId (column ID) (assuming that it's the PRIMARY KEY of the table and thus unique) instead of the name. You would probably have a method getDrugById in the DatabaseHelper class along the lines of (for 2.) :-
public Drugs getDrugById(long id) {
SQLiteDatabase db = getReadableDatabase();
Drugs rv = new Drugs();
rv.setMedID(-1); // set so that drug not found can be determined
String whereclause = ID + "=?";
String[] whereargs = new String[]{String.valueOf(id)};
Cursor csr = db.query(TABLE,null,whereclause,whereargs,null,null,null);
if (csr.moveToFirst()) {
rv.setMedID(id);
rv.setName(csr.getString(csr.getColumnIndex(NAME)));
rv.setMenge(csr.getString(csr.getColumnIndex(MENGE)));
rv.setArt(csr.getString(csr.getColumnIndex(ART)));
rv.setNummer(csr.getString(csr.getColumnIndex(NUMMER)));
}
csr.close();
return rv;
}
Notes
The returned value should be checked for the MedID being -1, this indicating that there is no such row that matches the passed id.
Rather than risking mistyping names the CONSTANTS defined in the class have been used (you may wish to adopt this throughout).
Cursors should ALWAYS be closed when done with, otherwise an exception can occur.
The above assumes that the ID column is the PRIMARY KEY and that it is an alias of the rowid column. That is you have ID INTEGER PRIMARY KEY or ID INTEGER,..other columns.., PRIMARY KEY (ID)
The above assumes the correct usage of the ID column i.e. that it is treated as a long not an int (int is ok as long as the rows are limited, however SQLite allows rowid's as high as 9223372036854775807, which cannot be handled by an int).
This is the usual method, as using an alias of rowid will likely be the most efficient.
newbie to Android here!
I've been learning how to implement SQLite in my app, and to sum it up, I have an Accountant class which has access to the SQLite database. The class pulls up the items from the database and puts them in an ArrayList. This ArrayList is what is used for my adapter for the recyclerView.
Whenever I add a new item in the app, the the item's data is stored in the database and the Accountant class's ArrayListgets updated with this info.
Then, the adapter calls its notifyDataSetChanged() method to update the View. This is where the problem occurs; the RecyclerView DOES display all items, but only upon app startup, NOT when a new item is added.
I've done all I can, it just LOOKS like it's supposed to work, but it doesn't and it's driving me nuts.
Here's the code
ItemAdapter Class
private class ItemAdapter extends RecyclerView.Adapter<ItemHolder> {
private List<Item> mItemList;
public ItemAdapter(List<Item> itemList) {
mItemList = itemList;
}
public ItemHolder onCreateViewHolder(ViewGroup parent, int ViewType) {
View view = getLayoutInflater().inflate(R.layout.list_item_item, parent, false);
return new ItemHolder(view);
}
public void onBindViewHolder(ItemHolder holder, int position) {
Item item = mItemList.get(position);
holder.bindItem(item);
}
public int getItemCount() {
return mItemList.size();
}
}
Accountant Class
public class Accountant {
private static Accountant sAccountant;
private double mTotalMoney;
private Context mContext;
private SQLiteDatabase mDatabase;
private List<Item> mItemList;
public static Accountant get(Context context) {
sAccountant = sAccountant == null ? new Accountant(context) : sAccountant;
return sAccountant;
}
private Accountant(Context context) {
mTotalMoney = 0;
mContext = context.getApplicationContext();
mDatabase = new ItemBaseHelper(mContext).getWritableDatabase();
mItemList = getListFromSQL();
}
private static ContentValues getContentValues(Item i) {
ContentValues values = new ContentValues();
values.put(ItemTable.cols.NAME, i.getName());
values.put(ItemTable.cols.PRICE, i.getPrice());
values.put(ItemTable.cols.COUNT, i.getCount());
return values;
}
public void addItem(Item item) {
ContentValues cv = getContentValues(item);
mDatabase.insert(ItemTable.NAME, null, cv);
mItemList = getListFromSQL();
}
public void removeItem(int i) {
}
public void addMoney(double money, boolean isSet) {
mTotalMoney += isSet ? money - mTotalMoney : money;
}
public String getTotalMoney() {
return MoneyUtils.prep(mTotalMoney);
}
public String getChange() {
double cost = 0;
for (Item item : getItemList())
cost += item.getPrice() * item.getCount();
return MoneyUtils.prep(mTotalMoney - cost);
}
public List<Item> getItemList() {
return mItemList;
}
private List<Item> getListFromSQL() {
List<Item> itemList = new ArrayList<>();
ItemCursorWrapper cursor = queryItems(null, null);
try {
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
itemList.add(cursor.getItem());
cursor.moveToNext();
}
} finally {
cursor.close();
}
return itemList;
}
public ItemCursorWrapper queryItems(String whereClause, String[] whereArgs) {
Cursor cursor = mDatabase.query(ItemTable.NAME, null, whereClause, whereArgs, null, null, null);
return new ItemCursorWrapper(cursor);
}
public String individualPriceOf(Item i) {
return MoneyUtils.prep(i.getPrice());
}
public String totalPriceOf(Item i) {
return MoneyUtils.prep(i.getCount() * i.getPrice());
}
public String countOf(Item i) {
return String.valueOf(i.getCount());
}
public void clearList() {
mDatabase.delete(ItemTable.NAME, null, null);
}
}
Item adding logic
public void addItem(Item item) {
mAccountant.addItem(item);
mAdapter.notifyItemInserted(mAccountant.getListFromSQL().size() - 1);
mAdapter.notifyDataSetChanged();
mChangeButton.setText(mAccountant.getChange());
}
Well there is fundamental problem not even related to RecyclerView.
First let's see how to fix your issue then explanation of what's wrong.
change this
private List<Item> mItemList;
to this
private final List<Item> mItemList;
then instead of any assignment like mItemList = getListFromSQL(); write this
mItemList.clear();
mItemList.addAll(getListFromSQL());
Now explanation why your code is not working. The thing is that when you assign your dataSource (i.e. mItemList) to some new value you are changing reference to it (that's a java fundamental thing) so that your RecyclerView doesn't know anything about it and it's own dataSource which you assign only once in constructor remains the same old one which is not changed therefore your notifyDataSetChanged call does nothing.
General advice whenever using RecyclerView or a ListView make sure you define your dataSource as final.
This is happening because you do not add the item into your Adpater's list. Make a method inside your adapter and call this method from your Accountant class.
private class ItemAdapter extends RecyclerView.Adapter<ItemHolder> {
public void addItem(Item item) {
mItemList.add(item); ///Add the item to your arrayList and then notify
notifyItemInserted(mItemList.size());
}
When you add single item in Adapter dont call notifyDataSetChanged() method because it will notify the whole list. Instead only use notifyItemInserted() method.
Another think is make sure when you notify the adapter it must be from UI thread.
When you add your item then just call this adapter addItem() method from your Accountant class.
public void addItem(Item item) { ///This method is from Accountant Class
mAccountant.addItem(item);
mAdapter.addItem(item); // Call the addItem() from Adapter class
mChangeButton.setText(mAccountant.getChange());
}
Hope it will work...
I want to program simple organizer with Notes.
So my solution works, but very bad, because after I create a new note in NewNoteActivity I return to activity with ListView and I see how my list is building again.
I read that listview always will be rebuild, but why it takes so lot of time in my case? For example, in other applications I haven't ever seen the process of drawing list, but in my application I do.
So how to build listview so quickly as it is in good applications? What wrong with my code? I just want to add new item in NewNoteActivity, return to activity with list and see a ready list!
I have a SQLite database with some data as shown below:
_id | time | date | text
1 | 9:45 | 12.01| blabla
2 | 21:01| 13.01| albalb
...| ... | ... | ...
Also I have a class Note:
public class Note {
private int id;
private String time;
private String date;
private String text;
public Note(final int id, final String time, final String date, final String text){
setId(id);
setTime(time);
setDate(date);
setText(text);
}
public int getId(){
return id;
}
public String getTime(){
return time;
}
public String getDate(){
return date;
}
public String getText(){
return text;
}
void setId(final int id){
this.id = id;
}
void setTime(final String time){
this.time = time;
}
void setDate(final String date){
this.date = date;
}
void setText(final String text){
this.text = text;
}
}
And NotesManager:
public class NotesManager {
private static final String TABLE_NAME = "NotesListTable";
private static final String KEY_TIME = "time";
private static final String KEY_DATE = "date";
private static final String KEY_TEXT = "text";
private static final String KEY_ID = "_id";
private final SQLiteDatabase db;
public NotesManager(SQLiteDatabase db){
this.db = db;
}
public void save(final ContentValues cv){
db.insert(TABLE_NAME, null, cv);
}
public void delete(final int id){
db.delete(TABLE_NAME, KEY_ID + "=" + id, null);
}
public Note getNoteById(final int id){
Cursor mCursor = db.query(TABLE_NAME, null, KEY_ID + "=" + id, null, null, null, null, null);
if (mCursor != null) {
mCursor.moveToFirst();
}
return new Note(mCursor.getInt(mCursor.getColumnIndex(KEY_ID)),
mCursor.getString(mCursor.getColumnIndex(KEY_TIME)),
mCursor.getString(mCursor.getColumnIndex(KEY_DATE)),
mCursor.getString(mCursor.getColumnIndex(KEY_TEXT)));
}
public Cursor getAllDataFromDB(){
return db.query(TABLE_NAME, null, null, null, null, null, null);
}
public String[] getKeysArray(){
return new String[] {KEY_ID, KEY_TIME, KEY_DATE, KEY_TEXT};
}
}
I have a fragment with ListView:
It has been generated by Android Studio, nut I made some changes, added SimpleCursorAdapter
public class NotesListFragment extends Fragment implements AbsListView.OnItemClickListener {
private static final String ARG_SECTION_NUMBER = "section_number";
private int mSectionNumber = 0;
private OnFragmentInteractionListener mListener;
private AbsListView mListView;
private SimpleCursorAdapter scAdapter;
private Cursor cursor;
ImageButton deleteButton;
NotesManager notesManager = new NotesManager(OrganizerApp.db);
public static NoesListFragment newInstance(int param1) {
NoesListFragment fragment = new NotesListFragment();
Bundle args = new Bundle();
args.putInt(ARG_SECTION_NUMBER, param1);
fragment.setArguments(args);
return fragment;
}
public NotesListFragment() {
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mSectionNumber = getArguments().getInt(ARG_SECTION_NUMBER);
}
cursor = NotesManager.getAllDataFromDB();
//TODO: startManagingCursor(cursor)
scAdapter = new SimpleCursorAdapter(getActivity(),
R.layout.note_list_rowlayout,
cursor,
notesManager.getKeysArray(),
new int[]{R.id.note_list_rowlayout_item1,
R.id.note_list_rowlayout_item2,
R.id.note_list_rowlayout_item3,
R.id.note_list_rowlayout_item4 });
deleteButton = (ImageButton) getView().
findViewById(R.id.note_list_rowlayout_deleteButton);
deleteButton.setOnClickListener(onClickDeleteButton);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_note, container, false);
// Set the adapter
mListView = (AbsListView) view.findViewById(android.R.id.list);
mListView.setAdapter(scAdapter);
// Set OnItemClickListener so we can be notified on item clicks
mListView.setOnItemClickListener(this);
return view;
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mSectionNumber = getArguments().getInt(ARG_SECTION_NUMBER);
mListener = (OnFragmentInteractionListener) activity;
((MainActivity) activity).onSectionAttached(mSectionNumber);
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnFragmentInteractionListener");
}
}
#Override
public void onDetach() {
super.onDetach();
mListener = null;
}
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (null != mListener) {
// Notify the active callbacks interface (the activity, if the
// fragment is attached to one) that an item has been selected.
// mListener.onFragmentInteraction(NotesListContent.ITEMS.get(position).id);
}
}
public void setEmptyText(CharSequence emptyText) { // If list is empty.
View emptyView = mListView.getEmptyView();
if (emptyView instanceof TextView) {
((TextView) emptyView).setText(emptyText);
}
}
public interface OnFragmentInteractionListener {
// TODO: Update argument type and name
public void onFragmentInteraction(String id);
}
View.OnClickListener onClickDeleteButton = new View.OnClickListener() {
#Override
public void onClick(View v) {
}
};
}
I have an activity NewNoteActivity in which I create a new note and put it into database:
ContentValues cv = new ContentValues();
cv.put("time", entTime.getText().toString());
cv.put("date", entDate.getText().toString());
cv.put("comment", entComment.getText().toString());
NotesManager.save(cv);
I'll drop a few hints in here.
As you are using a DB to load data, my thoughts go first to the idea you have a very big number of records you are fetching when creating the list.
Multiply it by 4, the number of views in each listView's item, it may be a reason for the slowdown.
Generally, you'd want to start fetching the data from another Thread, aiming to keep the MainThread light and free(meaning your app will be responsive while the heavy loading takes place).
This can be done via AsyncThread. I'd suggest to pop up a ProgressBar view while you are loading and displaying data, to notify the user your app has work in progress.
Another GREAT way to make your listView faster (during scrolling at least, not sure if it can help you on the loading phase), is to implement the ViewHolder pattern in your Adapter.
Each time you scroll, Android calls the getView() method of the Adapter, generally creating a new View each time an item is found(remember, an item has 4 views inside!). This makes it incredibly slow when you handle many items. The idea behind ViewHolder pattern is to re-use views which are no longer visible rather than adding new ones.
You can find more about it here.
If this still doesn't help, I'd give a try to another way to load.
Having like 1000+ rows from the DB may be an overload of work for what you first need to show.
As an example, let's imagine you have 1000 records to insert in the listView. Loading them all may take some time, and when this is done and the listView is filled, you'll never be able to look at them, simply because your listView has a certain space and on the display you'd be able to see just a little portion of the data, let's say 10.
My point is: why keep the phone busy loading 1000 records on view creation, when you can load let's say 50(thinking over to an early-scroll), and when you know the listView is created and you are able to interact with the app load the rest in a background thread(still, AsyncTask). Once you are done, notify the adapter of the data change.
Hope to have been understandable ^_^"
So please bear with me on this one... I am trying to get all information from my database table and output that information into a nice custom ListView (which I have already built).
MySQLiteHelper.java (What I am using to scrape the information)
...
public List<String> getAllLogs() {
List<String> List = new ArrayList<String>();
String selectQuery = "SELECT * FROM " + TABLE_GASLOG;
SQLiteDatabase db = this.getWritableDatabase();
Cursor cursor = db.rawQuery(selectQuery, null);
if (cursor.moveToFirst()){
do {
List.add(cursor.getString(1));
} while (cursor.moveToNext());
}
return List;
}
...
gasLog.java (What I am using to get/set all of my information)
...
public class gasLog {
private int id;
private double pricePerGallon;
private double gallons;
private double odometer;
private String date;
private String filledOrNot; //This will be a 0 or 1 value.
private String comments;
public gasLog(){}
public gasLog(double pricePerGallon, double gallons, double odometer, String date, String filledOrNot, String comments){
super();
this.id = id;
this.pricePerGallon = pricePerGallon;
this.gallons = gallons;
this.odometer = odometer;
this.date = date;
this.filledOrNot = filledOrNot;
this.comments = comments;
}
//getters & setters
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public double getPricePerGallon() {
return pricePerGallon;
}
public void setPricePerGallon(double pricePerGallon) {
this.pricePerGallon = pricePerGallon;
}
public double getGallons(){
return gallons;
}
public void setGallons(double gallons){
this.gallons = gallons;
}
public double getOdometer(){
return odometer;
}
public void setOdometer(double odometer){
this.odometer = odometer;
}
public String getDate(){
return date;
}
public void setDate(String date){
this.date = date;
}
public String getFilledOrNot(){
return filledOrNot;
}
public void setFilledOrNot(String filledOrNot){
this.filledOrNot = filledOrNot;
}
public String getComments(){
return comments;
}
public void setComments(String comments){
this.comments = comments;
}
public String toString() {
return "Date: " + date + ", Price: " + pricePerGallon + ", " + gallons + " Gallons, " +
", Odometer Reading: " + odometer +
", Full fill: " + filledOrNot;
}
}
history.java (Where I am inflating the view and calling all the information to).
...
public class history extends ListActivity {
// Log table name
private static final String TABLE_GASLOG = "gasLog";
// Log table columns names
private static final String KEY_ID = "id";
private static final String KEY_PRICE_PER_GALLON = "pricePerGallon";
private static final String KEY_GALLONS = "gallons";
private static final String KEY_ODOMETER = "odometer";
private static final String KEY_DATE = "date";
private static final String KEY_FILLED_OR_NOT = "filledOrNot";
private static final String KEY_COMMENTS = "comments";
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.history);
Typeface tf = Typeface.createFromAsset(getAssets(),"Roboto-Light.ttf");
ListView listContent = (ListView) findViewById(android.R.id.list);
TextView history = (TextView) findViewById(R.id.history);
history.setTypeface(tf);
MySQLiteHelper db = new MySQLiteHelper(this);
List<gasLog> list = new ArrayList<gasLog>();
list = db.getAllLogs();
ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, list);
listContent.setAdapter(adapter);
}
}
So I know I need more in the bottom section of history.java Something similar to..
// THE DESIRED COLUMNS TO BE BOUND
String[] columns = new String[] { People.NAME, People.NUMBER };
// THE XML DEFINED VIEWS WHICH THE DATA WILL BE BOUND TO
int[] to = new int[] { R.id.name_entry, R.id.number_entry };
But I am not quite sure what everything should be. I had tried using a getContentResolver but im not sure how to set the URI (or get the URI for my database) or if that is even the proper method to go.
my history.xml contains a listview and i have a bg.xml file that has the layout for each record in the listview. Right now I can only get it to return the messy looking String toString() at the bottom of gasLog.java
Any help would be greatly appreciated, if someone could give me some direction and also maybe why? Looking forward to learning something on this that I can apply down the road. Thank you so much! Sorry for being such a newbie!
EDIT:
Just want to make sure I am clear on this part.
in GasCursorAdapter.java I will setup bindView like this:
public void bindView(View v, Context context, Cursor cursor){
TextView cardDate = (TextView) v.findViewById(R.id.cardDate);
int date = cursor.getColumnIndexOrThrow(MySQLiteHelper.KEY_DATE);
cardDate.setText(date);
}
and will do that for each view/db field (then that will assign the values to the view) (I think?!)
as far as this part goes, im not quite sure where to put that..
MySQLiteHelper db = new MySQLiteHelper(this);
Cursor cursor = db.getAllLogs();
GasCursorAdapter adapter = new GasCursorAdapter(content, cursor, 0);
setAdapter(adapter);
right now i have that in history.java but i am getting content cannot be resolved to a variable
Other than that i think i have it figured out thanks to you!!!
Thanks again for your help!
I suggest you use a CursorAdapter, which will skip a few of the steps and run much smoother. I was doing something similar to you, and ran into a lot of performance issues, but I switched to a cursorAdapter, and my code is much easier to follow and quicker as a result. There's a few parts to making this happen, which I'll show you below. The first step is to simply return the cursor, instead of trying to process it in your database call.
public Cursor getAllLogs() {
List<String> List = new ArrayList<String>();
String selectQuery = "SELECT * FROM " + TABLE_GASLOG;
SQLiteDatabase db = this.getWritableDatabase();
Cursor cursor = db.rawQuery(selectQuery, null);
return cursor;
}
This will actually pass the query in a way that you can better utilize it. The way I managed my program was to do something like this from here.
Define what a row of output of the database should look like, and place in an XML file.
Set up a CursorAdapter that looks something like the code below.
Create your query, and then pass it to a new GasCursorAdapter.
Here's GasCursorAdapter
public class GasCursorAdapter extends CursorAdapter {
private LayoutInflater mInflater=null;
public GasCursorAdapter (Context context, Cursor c,int flags) {
super(context, c, flags);
mInflater=(LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return mInflater.inflate(R.layout.gasLayout,parent,false);
}
#Override
public void bindView(View v,Context context, Cursor cursor) {
//View is the view created by newView, take it and find your views and populate it, given the Cursor
}
}
And creating it looks like:
Cursor cursor=getAllLogs();
GasCursorAdapter adapter=new GasCursorAdapter(context, cursor,0);
setAdapter(adapter);
The user is looking at a list LibraryFragment and clicks one of the options (Item1 or Item2), from there I wanted to show another list (GFragment) that is created dynamically from the items received from the database. In the logCat I get this error:
08-30 13:56:54.087: E/SqliteDatabaseCpp(22622): sqlite3_open_v2("/data/data/j.j.l.library.v11/databases/library_dev.db", &handle, 1, NULL) failed
Failed to open the database. Closing it.
Does anyone know what is wrong with the code or why it is doing this?
The code I am using for the database is:
public class DatabaseHelper {
private static String DB_PATH = "/data/data/j.j.l.library.v11/databases/";
private static String DB_NAME = "library_dev.db";
private SQLiteDatabase myDataBase;
public DatabaseHelper(){
}
//Open the database.
public void openDatabase() throws SQLException{
String myPath = DB_PATH + DB_NAME;
myDataBase = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READONLY);
}
//Return the columns we want.
public List<String> getQueryColumn(String tableName, String[] columns){
Cursor cursor;
List<String> info = new ArrayList<String>();
cursor = myDataBase.query(tableName, columns, null, null, null, null, null);
cursor.moveToFirst();
while(!cursor.isAfterLast()){
info.add(cursor.getString(0));
cursor.moveToNext();
}
cursor.close();
return info;
}
//Close the Database.
public void closeDatabase() throws SQLException{
myDataBase.close();
}
}
Another List I am trying to create dynamically from the database:
public class GFragment extends ListFragment {
private DatabaseHelper gList;
public static final String GROLE = "role";
public static final String[] ROLENAME = {"name"};
#Override
public void onActivityCreated(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onActivityCreated(savedInstanceState);
gList = new DatabaseHelper();
gList.openDatabase();
List<String> values = gList.getQueryColumn(GROLE, ROLENAME);
setListAdapter(new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, values));
gList.closeDatabase();
}
}
This is the list the user is looking at right before there is a call to retrieve the dynamic list from the database:
public class LibraryFragment extends ListFragment{
String[] libraryList = {"Item1", "Item2"};
#Override
public void onActivityCreated(Bundle savedInstanceState){
super.onActivityCreated(savedInstanceState);
setListAdapter(new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, libraryList));
}
#Override
public void onListItemClick(ListView l, View v, int position, long id){
//Get the position the user clicked.
Fragment newFragment = null;
String listPosition = libraryList[position];
getListView().setItemChecked(position, true);
if(listPosition.equals("Item1")){
newFragment = new GFragment();
}else if (listPosition.equals("Item2")){
newFragment = new ITFragment();
}
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.myFragments, newFragment);
transaction.addToBackStack(null);
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
transaction.commit();
}
}
It's because you don't create this database library_dev.db, so it's empty, resulting in a NULL reference; a closing operation is taken afterward.
You need to handle the creation/upgrade/remove of the database in a class which should extends from SQLiteOpenHelper first. Then use this class to get your database:
public class MyDatabaseHelper extends SQLiteOpenHelper { // blah...database create/upgrade handling }
MyDatabaseHelper myHelper = new MyDatabaseHelper(yourContext);
SQLiteDatabase myDatabase = myHelper.getReadableDatabase(); // now you can use `myDatabase` freely
You can refer to a proper guideline for this at: http://www.vogella.com/articles/AndroidSQLite/article.html