It's not duplicate. I saw all answers is Stack.
My problem: I have CommentActivity, where I get Cursor. In CommentCursorAdapter I get values from Database.
In adapter I have two images: Like and Dislike. When I click Like - in database rating incremented.
TextView rating should show a new rating after pressing. How to do it correctly?
CommentActivity
public class CommentActivity extends AppCompatActivity {
private String idComment;
private ListView listComments;
private CommentCursorAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_comment);
listComments = (ListView) findViewById(R.id.list_comments);
idComment = getIntent().getStringExtra(MainActivity.ID_KEY);
getCursorData();
}
private void getCursorData() {
adapter = new CommentCursorAdapter(this, new CursorLoader(App.getInstance(), idComment).loadInBackground(), 0);
listComments.setAdapter(adapter);
}
private static class CursorLoader extends SimpleCursorAdapter {
private String idComment;
public CursorLoader(Context context, String idComment) {
super(context);
this.idComment = idComment;
}
#Override
public Cursor loadInBackground() {
return App.getInstance().getDb().rawQuery(
"SELECT comment._id AS _id, comment.text AS text, user.email AS email, comment.rate FROM comment JOIN user ON comment.userId = user._id WHERE comment.postId = ? ORDER BY _id ASC", new String[]{
idComment
});
}
}
}
CommentCursorAdapter
public class CommentCursorAdapter extends CursorAdapter {
private TextView commentEmail;
private TextView commentText;
private TextView ratingText;
private ImageView imageViewLike;
private ImageView imageViewDislike;
private static final String ID = "_id";
private static final String EMAIL = "email";
private static final String TEXT = "text";
private static final String RATE = "rate";
public CommentCursorAdapter(Context context, Cursor c, int flags) {
super(context, c, 0);
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
return LayoutInflater.from(context).inflate(R.layout.adapter_comment, viewGroup, false);
}
#Override
public void bindView(View view, Context context, final Cursor cursor) {
commentEmail = (TextView) view.findViewById(R.id.comment_email);
commentText = (TextView) view.findViewById(R.id.comment_text);
ratingText = (TextView) view.findViewById(R.id.rating_text);
imageViewLike = (ImageView) view.findViewById(R.id.image_like);
imageViewDislike = (ImageView) view.findViewById(R.id.image_dislike);
imageViewLike.setTag(cursor.getString(cursor.getColumnIndexOrThrow(ID)));
imageViewDislike.setTag(cursor.getString(cursor.getColumnIndexOrThrow(ID)));
String email = cursor.getString(cursor.getColumnIndexOrThrow(EMAIL));
String text = cursor.getString(cursor.getColumnIndexOrThrow(TEXT));
String rating = cursor.getString(cursor.getColumnIndexOrThrow(RATE));
commentEmail.setText(email);
commentText.setText(text);
ratingText.setText(rating);
imageViewLike.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Log.e("like", "click");
SQLiteStatement statement = App.getInstance().getDb().compileStatement(
"UPDATE comment SET rate = rate + 1 WHERE comment._id = ?"
);
statement.bindString(1, (String) view.getTag());
try {
statement.execute();
} finally {
statement.close();
}
}
});
imageViewDislike.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Log.e("dislike", "click");
SQLiteStatement statement = App.getInstance().getDb().compileStatement(
"UPDATE comment SET rate = rate - 1 WHERE comment._id = ?"
);
statement.bindString(1, (String) view.getTag());
try {
statement.execute();
} finally {
statement.close();
}
//swapCursor(cursor);
//notifyDataSetChanged();
}
});
}
}
There are a some options you can choose from:
After updating the rating in the database, you can just reload all data using your loader.
You can migrate your database logic to using a ContentProvider. If you do it correctly, the provider can notify all loaders that their underlying data has been changed, so they are automatically reloaded.
In your CustomAdapter, you need to put this.notifyDataSetChanged(); where you are performing operation of the LIKE and COMMENT.
It's great that you have used CursorAdapter - that listens for update notifications and reloads the content automatically.
Hope it will help you.
Related
This question already has an answer here:
Android List item displaying package name and # instead of string value
(1 answer)
Closed 1 year ago.
I'm trying to get all ID and IncomeCategoryName from DB and put them into a listview.
The problem is that Package Name. ClassName#hexcode is displayed instead of ID and IncomeCategory Name.
Class File
public class IncomeCategoriesClass {
private Integer CATEGORY_ID;
private String CATEGORY_NAME;
public IncomeCategoriesClass(Integer CATEGORY_ID, String CATEGORY_NAME) {
this.CATEGORY_ID = CATEGORY_ID;
this.CATEGORY_NAME = CATEGORY_NAME;
}
public IncomeCategoriesClass() {
this.CATEGORY_NAME = CATEGORY_NAME;
}
public Integer getCATEGORY_ID() {
return CATEGORY_ID;
}
public void setCATEGORY_ID(Integer CATEGORY_ID) {
this.CATEGORY_ID = CATEGORY_ID;
}
public String getCATEGORY_NAME() {
return CATEGORY_NAME;
}
public void setCATEGORY_NAME(String CATEGORY_NAME) {
this.CATEGORY_NAME = CATEGORY_NAME;
}
}
Database helper snippet
public List get_AllIncomeCategories(){
List AllIncomeCategories = new LinkedList<>();
String queryString = "Select * FROM " + INCOME_CATEGORIES_TABLE;
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.rawQuery(queryString,null);
IncomeCategoriesClass incomeCategoriesClass;
if(cursor.moveToFirst()){
do {
incomeCategoriesClass = new IncomeCategoriesClass();
incomeCategoriesClass.setCATEGORY_ID(Integer.parseInt(cursor.getString(0)));
incomeCategoriesClass.setCATEGORY_NAME(cursor.getString(1));
AllIncomeCategories.add(incomeCategoriesClass);
}while (cursor.moveToNext());
}
cursor.close();
db.close();
return AllIncomeCategories;
}
AddEditincomeCategoriesActivity
public class AddEditIncomeCategoriesActivity extends AppCompatActivity {
//Define Screen Fields And Variables
ListView listviewAllIncomeCategories;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_edit_income_categories);
//Initialize Screen Field Variables
btn_ViewAllIncomeCategory = findViewById(R.id.btn_ViewAllIncomeCategory);
listviewAllIncomeCategories = findViewById(R.id.listviewAllIncomeCategories);
//View All Income Categories on Button Click
btn_ViewAllIncomeCategory.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
DataBaseHelper dataBaseHelper = new DataBaseHelper(AddEditIncomeCategoriesActivity.this);
List<IncomeCategoriesClass> AllIncomeCategories;
AllIncomeCategories = dataBaseHelper.get_AllIncomeCategories();
ArrayAdapter adapter = new ArrayAdapter(AddEditIncomeCategoriesActivity.this,android.R.layout.simple_list_item_1,AllIncomeCategories);
listviewAllIncomeCategories.setAdapter(adapter);
}
});
//Add Income Category on Button Click
}
}
It is outputing data but not the data I am looking for, it is Packagename.Class#HexCode instead of 1 Cash, 2 Credit Cards, etc.
What am I doing wrong and how can I do this properly?
Simple ArrayAdapter can only inflate flat values like strings or integers. For a list of objects, You need a Custom Adapter to render your views.
See ListView Android for the implementation details.
You're seeing the output of Object#toString().
You can override toString() in your IncomeCategoriesClass to produce the string output you'd like to be displayed for that item.
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.
I have a small app for keeping list of students.
When adding a new student record, a random image url (string) is chosen from my own pool of 10 addresses, fetched & being set on an image view using AsyncTask.
I'm using custom CursorAdapter for the list, and custom SQLiteOpenHelper DB to handle the DB (contains id,name, grade, image url str).
I'm using a AsyncTask in order to fetch image from the internet
My problem is that my AsyncTask keeps getting called over and over again, upon every click on the screen, fetching the same image already fetched before.
I guess i'm using my AsyncTask incorrectly (Through bindView), but not sure.
My Goal is to fetch the image for every line only once
MainActivity:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
/* Fields for adding new student to the list */
private EditText mEtName;
private EditText mEtGrade;
private ListView mLvStudents;
/* Our DB model to store student objects */
private SqlDbHelper mDB;
/* Custom SQL-Adapter to connect our SQL DB to the ListView */
private SQLAdapter mAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/* Init fields & needed views */
mEtName = findViewById(R.id.et_name);
mEtGrade = findViewById(R.id.et_grade);
mLvStudents = findViewById(R.id.lv_students);
mDB = new SqlDbHelper(getApplicationContext());
mAdapter = new SQLAdapter(this, mDB.getAllRows(), false);
/* Set click listeners and adapter to our list */
mLvStudents.setAdapter(mAdapter);
findViewById(R.id.button_add).setOnClickListener(this);
}
#Override
public void onClick(View view) {
final String name = mEtName.getText().toString();
final int gradeInt = AidUtils.getGradeInt(mEtGrade.getText().toString());
mDB.addStudent(name, gradeInt, AidUtils.randImageUrl());
mAdapter.changeCursor(mDB.getAllRows());
mEtName.setText("");
mEtGrade.setText("");
}
}
SQLAdapter:
final class SQLAdapter extends CursorAdapter {
private LayoutInflater mInflater;
public SQLAdapter(Activity context, Cursor c, boolean autoRequery) {
super(context, c, autoRequery);
mInflater = LayoutInflater.from(context);
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
return mInflater.inflate(R.layout.lv_line, viewGroup, false);
}
#Override
public void bindView(final View view, Context context, Cursor cursor) {
/* Set name */
((TextView)view.findViewById(R.id.tv_name)).setText(
cursor.getString(cursor.getColumnIndex(SqlDbHelper.KEY_NAME)));
/* Set the image URL for it and fetch the image */
final String imageUrlStr = cursor.getString(cursor.getColumnIndex(SqlDbHelper.KEY_IMG));
((TextView)view.findViewById(R.id.tv_image_url)).setText(imageUrlStr);
new AsyncImageSet(imageUrlStr, (ImageView)view.findViewById(R.id.iv_pic)).execute();
/* Set grade and color for it */
final int grade = cursor.getInt(cursor.getColumnIndex(SqlDbHelper.KEY_GRADE));
((TextView)view.findViewById(R.id.tv_grade)).setText(String.valueOf(grade));
}
}
SqlDbHelper:
final class SqlDbHelper extends SQLiteOpenHelper {
private static final String TAG = "SqlDbHelper";
/* Database version */
public static final int VERSION = 1;
/* Relevant string names, keys represent columns */
public static final String DB_NAME = "StudentsDB";
public static final String TABLE_NAME = "students";
public static final String KEY_ID = "_id";
public static final String KEY_NAME = "Name";
public static final String KEY_GRADE = "Grade";
public static final String KEY_IMG = "Image";
public SqlDbHelper(Context context) {
super(context, DB_NAME, null, VERSION);
}
#Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
StringBuilder createQuery = new StringBuilder();
createQuery.append("CREATE TABLE " + TABLE_NAME + " (")
.append(KEY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,")
.append(KEY_NAME + " TEXT,")
.append(KEY_GRADE + " INT,")
.append(KEY_IMG + " TEXT")
.append(")");
Log.d(TAG, "Create table query: " + createQuery.toString());
sqLiteDatabase.execSQL(createQuery.toString());
}
#Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {}
public void addStudent(final String name, final int grade, final String imageUrl) {
ContentValues cv = new ContentValues();
cv.put(KEY_NAME, name);
cv.put(KEY_GRADE, grade);
cv.put(KEY_IMG, imageUrl);
getWritableDatabase().insert(TABLE_NAME, null, cv);
}
public Cursor getAllRows() {
return (getReadableDatabase().
rawQuery("SELECT * FROM " + TABLE_NAME, null));
}
}
AsyncImageSet:
public class AsyncImageSet extends AsyncTask<Void, Void, Bitmap> {
private String mImageUrl;
private ImageView mImageView;
public AsyncImageSet(String imageUrl, ImageView imageView) {
mImageUrl = imageUrl;
mImageView = imageView;
}
#Override
protected Bitmap doInBackground(Void... voids) {
Log.v("AsyncImageSet", "New Async Task launched!");
Bitmap image = null;
try {
image = AidUtils.getBitmapFromUrl(AidUtils.buildUrl(mImageUrl));
} catch (IOException e) {
e.printStackTrace();
} finally {
return image;
}
}
#Override
protected void onPostExecute(Bitmap image) {
if(image != null) {
mImageView.setImageBitmap(image);
}
}
}
What Am I doing wrong here?
Thanks
What Am I doing wrong here?
You can't simply create a new AsyncTask and execute it in bindView(). That method is called every time a new row of your ListView enters the screen(and can be called in other situations too), so as the user scrolls your list up and down you'll create a lot of AsyncTask instances.
The proper way to handle this is to execute an AsyncTask to fetch an image only if there isn't an AsyncTask running already for that imageurl which you're trying to get. The simplest way to handle this is having a Map in your adapter to map a String(the imageUrl) to an AsyncTask instance(which will fetch the image pointed by that imageUrl):
final class SQLAdapter extends CursorAdapter {
private LayoutInflater mInflater;
private Map<String, AsyncImageSet> mappings = new HashMap<>();
//...
and then, in your bindView() method us the map above:
//...
final String imageUrlStr = cursor.getString(cursor.getColumnIndex(SqlDbHelper.KEY_IMG));
// at this point look in our map to see if we didn't already create an AsyncTask for this imageUrl
if (mappings.get(imageUrl) != null) {
// there's a task for this imageUrl already created so we use that
AsyncImageSet task = mappings.get(imageUrl);
task.updateView((ImageView)view.findViewById(R.id.iv_pic));
} else {
// there isn't a task for this imageUrl so create one and execute it(and save it in our mappings)
AsyncImageSet task = AsyncImageSet(imageUrlStr, (ImageView)view.findViewById(R.id.iv_pic));
mappings.put(imageUrl, task);
task.execute();
}
((TextView)view.findViewById(R.id.tv_image_url)).setText(imageUrlStr);
//...
You'll also need to change your AsyncTask to add the extra method:
public class AsyncImageSet extends AsyncTask<Void, Void, Bitmap> {
//...
private Bitmap bitmap;
public void updateView(ImageView imageView) {
mImageView = imageView;
// if the task is already finished it means the bitmap is
// already available
if (getStatus() == AsyncTask.Status.FINISHED) {
mImageView.setImageBitmap(bitmpa);
}
}
#Override
protected void onPostExecute(Bitmap image) {
if(image != null) {
bitmap = image;
mImageView.setImageBitmap(image);
}
}
This is a very simple implementation which will keep the bitmaps in memory which may not work if the images are big.
Ideally, as the other answer mentions, you should probably use a image loading library like Picasso which will help you avoid a lot of the pitfalls of implementing your own caching system.
Instead of getting bitmap from URL load that same URL into image view using Picasso.
In place of
new AsyncImageSet(imageUrlStr, (ImageView)view.findViewById(R.id.iv_pic)).execute();
Load image using Picasso library like:
Picasso.with(context).load(imageUrlStr).into(imageView);
For picasso setup see here
I want to program simple organizer with Notes.
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)
//mAdapter = new ArrayAdapter<NotesListContent.NotesItem>(getActivity(),
// android.R.layout.simple_list_item_1, android.R.id.text1, NotesListContent.ITEMS);
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);
//((AdapterView<ListAdapter>) mListView).setAdapter(mAdapter);
// 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) {
}
};
}
Android studio also generated NotesListContent.java:
public class NotesListContent {
public static List<Note> ITEMS = new ArrayList<Note>();
//public static Map<String, Note> ITEM_MAP = new HashMap<String, Note>();
private static void addItem(Note item) {
ITEMS.add(item);
//ITEM_MAP.put(item.id, item);
}
/**
* A dummy item representing a piece of content.
public static class NoteItem {
public String id;
public String content;
public NoteItem(String id, String content) {
this.id = id;
this.content = content;
}
#Override
public String toString() {
return content;
}
}*/
}
So my solution works, but I think that it is bad.
For what I need a NotesListContent.java? How can I use it?
How can I use ListView without deprecated simpleCursorAdapter?
How to delete and add items without refresh all ListView?
Especially this code seems to be very unconvenient:
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 });
I've done notes manager of my own so I'll try to answer Your questions.
For what I need a NotesListContent.java? How can I use it?
This is somewhat MVC pattern, separation of data from view. Try to think about it as an entity, or better as a single note entry description.
How can I use ListView without deprecated simpleCursorAdapter?
a) since when is simpleCursorAdapter depreciated? Only one of it's constructor is.
b) You can use second constructor, or extend some adapter class (for example ArrayAdapter) Yourself
How to delete and add items without refresh all ListView?
You add data to Your dataAdapter, then set dataAdapter as an adapter for ListView (listview.setAdapter(adapter)).
If You do not call adapter.notifyDataSetChanged() listview's view will not be updated.
Especially this code seems to be very unconvenient (...)
What's so wrong about it? But if so, feel free to use sth like this:
String[] columns = new String[] { // The desired columns to be bound
"timestamp",
"title",
"content",
};
// the XML defined views which the data will be bound to
int[] map_to = new int[] {
R.id.timestamp,
R.id.title,
R.id.content,
};
dataAdapter = new SimpleCursorAdapter(
this, R.layout.some_xml_here,
db.getAllItems(),
columns,
map_to,
0);
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);