I have modified the code in this question ,according to the answers there, in order to load contact's picture on a ListView. I am getting the Photo_id, and use it to get the Bitmap of the contact, using loadContactPhoto(ContentResolver cr, long id) . The problem is that no ImageView is getting a new image, although the photo id is always different. I tried using the Contact._ID, but still only two contacts' ImageView got a contact picture, and they were both wrong. I have commented the new lines I have added below.
Here is the code after the edit:
ContactStock:
public class ContactStock {
private String name;
private String number;
private Bitmap picture;
public ContactStock(String name, String number) {
this.name = name;
this.number = number;
}
public ContactStock(String name, String number, Bitmap photo) {
this.name = name;
this.number = number;
this.picture = photo;
}
public void setName(String name) {
this.name = name;
}
public void setNumber(String number) {
this.number = number;
}
public String getName() {
return this.name;
}
public String getNumber() {
return this.number;
}
public void setPicture(Bitmap picture) { // NEW METHOD
this.picture = picture;
}
public Bitmap getPicture() { // NEW METHOD
return picture;
}
}
addlistfromcontact:
public class addlistfromcontact extends Activity {
private ListView lst;
private List<ContactStock> contactstock;
private Cursor mCursor;
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.tab_contact_list);
lst = (ListView) findViewById(R.id.tab_contact_list);
contactstock = new ArrayList<ContactStock>();
mCursor = managedQuery(ContactsContract.Data.CONTENT_URI, null,
Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'", null,
ContactsContract.Data.DISPLAY_NAME + " ASC");
int number = mCursor.getColumnIndex(Phone.NUMBER);
int name = mCursor.getColumnIndex(ContactsContract.Data.DISPLAY_NAME);
int id = mCursor.getColumnIndex(Contacts.PHOTO_ID); // NEW LINE
while (mCursor.moveToNext()) {
String phName = mCursor.getString(name);
String phNumber = mCursor.getString(number);
long phId = mCursor.getLong(id); // NEW LINE
Bitmap phPhoto = loadContactPhoto(getContentResolver(), phId); // NEW LINE
Log.d("phId=", phId + "");
contactstock.add(new ContactStock(phName, phNumber, phPhoto)); // NEW LINE EDIT
}
lst.setAdapter(new ContactListAdapter(addlistfromcontact.this,
contactstock));
}
public static Bitmap loadContactPhoto(ContentResolver cr, long id) { // NEW METHOD
Uri uri = ContentUris.withAppendedId(
ContactsContract.Contacts.CONTENT_URI, id);
InputStream input = ContactsContract.Contacts
.openContactPhotoInputStream(cr, uri);
if (input == null) {
return null;
}
return BitmapFactory.decodeStream(input);
}
}
ContactListAdapter:
public class ContactListAdapter extends ArrayAdapter {
private final Activity activity;
private final List stocks;
public ContactListAdapter(Activity activity, List objects) {
super(activity, R.layout.listview_detail_tab_contact_list, objects);
this.activity = activity;
this.stocks = objects;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View rowView = convertView;
ContactStockView sv = null;
if (rowView == null) {
// Get a new instance of the row layout view
LayoutInflater inflater = activity.getLayoutInflater();
rowView = inflater.inflate(
R.layout.listview_detail_tab_contact_list, null);
// Hold the view objects in an object,
// so they don't need to be re-fetched
sv = new ContactStockView();
sv.name = (TextView) rowView.findViewById(R.id.contact_name);
sv.number = (TextView) rowView.findViewById(R.id.contact_number);
sv.photo = (ImageView) rowView.findViewById(R.id.contact_photo);
// Cache the view objects in the tag,
// so they can be re-accessed later
rowView.setTag(sv);
} else {
sv = (ContactStockView) rowView.getTag();
}
// Transfer the stock data from the data object
// to the view objects
ContactStock currentStock = (ContactStock) stocks.get(position);
sv.name.setText(currentStock.getName());
sv.number.setText(currentStock.getNumber());
sv.photo.setImageBitmap(currentStock.getPicture()); // NEW LINE
// TODO Auto-generated method stub
return rowView;
}
protected static class ContactStockView {
protected TextView name;
protected TextView number;
protected ImageView photo; // NEW LINE
}
}
There are two problems in your code. The first one is easy to overcome, the others need more work.
Lets start with the easy one:
The method ContactsContract.Contacts.openContactPhotoInputStream(cr, uri) takes a contact uri and not a photo uri. That is the id in your call to ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, id) has to be a contact id.
Also you create a lot of Bitmap objects when iterating over the result set. Don't do this. Though this might work at first, it probably crashes with OutOfMemory errors when the list get's to long. Try to create as few Bitmapobjects as necessary. That is: Only for those rows visible. When scrolling the list view you have to recycle existing Bitmaps.
Since I had lots of trouble figuring out how to load all contacts' photos efficiently without any UI freezing, errors on loading the images when I first started working on Android, I highly recommend anyone looking at my question to take a good look here.
It's a sample app that teaches you how to handle all proccessing data and loading images seamingly, efficiently without any 'hiccups' while scrolling your list. It is also a great project to get you started working on Android!
Specifically, for the contact image part of my questions code, the API provides two simple methods that can be found here that returns the InputStream of the contact's high res image or thumbnail. All you have to do is decode it using BitmapFactory.decodeByteArray(). Remember to load the size of the image you need to use in your app (something described in the link above), so that you won't get an OutOfMemoryError!
In order to improve the code further, you can also replace the ArrayAdapter with a custom Cursor adapter, and only load the lines of the listView you need on the spot.
Related
I'm very new to how cursors work in Android. Currently, I am able to retrieve an album's name and artist, but I am not able to retrieve it's genre. I've looked around and cannot find a way to do this. Should I just check over all the songs in the album, find the most common genre and assign the album genre based on that, or is there a more elegant way to do this? Typically all songs in an album will have the same genre, but this is not always the case.
Thanks.
You can use MetaDataRetriever if you want...It's (in my experience) a more complete way to retrieve metadata from a song (you can see more in the link https://developer.android.com/reference/android/media/MediaMetadataRetriever.html )...I wrote something like this in a project of mine, hope this can help you:
public void MetSongRetriever() {
//MediaPlayerPlay is the context, I shared a function i wrote myself
//Just like myUri is the path of the song (external storage in my case)
mMediaData.setDataSource(MediaPlayerPlay.this, myUri);
try {
mNameSong.setText(mMediaData.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE));
mBandName.setText(mMediaData.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST));
art = metaRetriver.getEmbeddedPicture();
Bitmap songImage = BitmapFactory.decodeByteArray(art, 0, art.length);
mCoverImage.setImageBitmap(songImage);
mAlbumName.setText(metaRetriver.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM));
mGenre.setText(metaRetriver.extractMetadata(MediaMetadataRetriever.METADATA_KEY_GENRE));
}
catch (Exception e) {
//set all the text Uknown, like "Unknown title" and so on
//And the background for the image grey
}
}
UPDATE:
Well, if I understood correctly, you want to show info like title, album, band, genre and so on of each songs in a list right?
I usually use a ListView for that...you can use ListView + creating a custom class to settle the info + an ArrayAdapter to inherit the class structure and show it in a ListView...maybe something like (just using title, band, album and the path where the song is stored, external storage in my case, you can add what you want following these lines)
Songs.java
public class Songs {
public String mSongname, mBandName, mAlbumName, mPathSong;
public Songs(String songName, String bandName, String albumName, String pathSong) {
mSongname = songName;
mBandName = bandName;
mAlbumName = albumName;
mPathSong = pathSong;
}
public void setSongname(String songname) {
mSongname = songname;
}
public void setBandName(String bandName) {
mBandName = bandName;
}
public void setAlbumName(String albumName) {
mAlbumName = albumName;
}
public void setPathSong(String pathSong) {
mPathSong = pathSong;
}
public String getSongname() {
return mSongname;
}
public String getBandName() {
return mBandName;
}
public String getAlbumName() {
return mAlbumName;
}
public String getPathSong() {
return mPathSong;
}
SongsAdapter.java
public class SongsAdapter extends ArrayAdapter<Songs> {
public SongsAdapter(Context context, ArrayList<Songs> mySongs) {
super(context, 0, mySongs);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
//Storing the data from the current position
Songs mySongs = getItem(position);
//Check if the convertview is reused, otherwise i load it
if(convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_songs, parent, false);
}
//Filling the structure with data
TextView SongTitle = (TextView) convertView.findViewById(R.id.ID_item_songs_title);
TextView BandName = (TextView) convertView.findViewById(R.id.ID_item_songs_band);
TextView AlbumName = (TextView) convertView.findViewById(R.id.ID_item_songs_album);
TextView PathName = (TextView) convertView.findViewById(R.id.ID_item_songs_path);
//Inserting the data in the template view
SongTitle.setText(mySongs.mSongname);
BandName.setText(mySongs.mBandName);
AlbumName.setText(mySongs.mAlbumName);
PathName.setText(mySongs.mPathSong);
//Return of the view for visualisation purpose
return convertView;
}
}
The following one is the function I used to retrieve the data (i used the index i just for storing all the songs' paths in a string array, to use them in another activity, not usefull for you)
public void getMusicList() {
int i = 0;
//I used the content resolver to get access to another part of the device, in my case the external storage
ContentResolver mContentResolver = getContentResolver();
//Taking the external storage general path
Uri mSongUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
//The cursor is used to point to a certain row in the temp database, so you can store just parts of it and not the whole database (efficiency maxed)
Cursor mSongCursor = getContentResolver().query(mSongUri, null, null, null, null, null);
if ((mSongCursor != null) && mSongCursor.moveToFirst()) {
//Gathering the index of each column for each info i want
int mTempSongTitle = mSongCursor.getColumnIndex(MediaStore.Audio.Media.TITLE);
int mTempBand = mSongCursor.getColumnIndex(MediaStore.Audio.Media.ARTIST);
int mTempAlbum = mSongCursor.getColumnIndex(MediaStore.Audio.Media.ALBUM);
int mTempLocationPath = mSongCursor.getColumnIndex(MediaStore.Audio.Media.DATA);
do {
//Gathering the real info based on the index
String mCurrentTitle = mSongCursor.getString(mTempSongTitle);
String mCurrentBand = mSongCursor.getString(mTempBand);
String mCurrentAlbum = mSongCursor.getString(mTempAlbum);
String mCurrentPath = mSongCursor.getString(mTempLocationPath);
mListOfPaths[i] = mCurrentPath;
i++;
Songs newSong = new Songs(mCurrentTitle, mCurrentBand, mCurrentAlbum, mCurrentPath);
adapter.add(newSong);
} while (mSongCursor.moveToNext());
}
}
And this last function is used to show finally all the data gathered in previous function
public void doStuff() {
mSongNames = (ListView) findViewById(R.id.ID_MPM_listView);
mSongNames.setAdapter(adapter);
getMusicList();
//The event it will occur when you click on an item of the listView
mSongNames.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
Intent mMediaPlayerPlayIntent = new Intent(MediaPlayerMain.this, MediaPlayerPlay.class);
mMediaPlayerPlayIntent.putExtra("ExtraPosition", position);
mMediaPlayerPlayIntent.putExtra("StringArray", mListOfPaths);
startActivity(mMediaPlayerPlayIntent);
}
});
}
How can I make the phone number 3456781276 which is in my phone contacts appear at the very top of my listview, and then all other contacts below that as normal? I believe I pass that value into my custom adapter and into my getView() but not at all sure how to proceed. Can you help?
In my ListView I show all my phone contacts with the following code:
class LoadContact extends AsyncTask<Void, Void, Void> {
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected Void doInBackground(Void... voids) {
// we want to delete the old selectContacts from the listview when the Activity loads
// because it may need to be updated and we want the user to see the updated listview,
// like if the user adds new names and numbers to their phone contacts.
selectPhoneContacts.clear();
// we have this here to avoid cursor errors
if (cursor != null) {
cursor.moveToFirst();
}
try {
// get a handle on the Content Resolver, so we can query the provider,
cursor = getApplicationContext().getContentResolver()
// the table to query
.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null,
null,
null,
// display in ascending order
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
// get the column number of the Contact_ID column, make it an integer.
// I think having it stored as a number makes for faster operations later on.
// get the column number of the DISPLAY_NAME column
int nameIdx = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME);
// get the column number of the NUMBER column
int phoneNumberofContactIdx = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
cursor.moveToFirst();
// We make a new Hashset to hold all our contact_ids, including duplicates, if they come up
Set<String> ids = new HashSet<>();
do {
System.out.println("=====>in while");
// get a handle on the display name, which is a string
name = cursor.getString(nameIdx);
// get a handle on the phone number, which is a string
phoneNumberofContact = cursor.getString(phoneNumberofContactIdx);
//----------------------------------------------------------
// get a handle on the phone number of contact, which is a string. Loop through all the phone numbers
// if our Hashset doesn't already contain the phone number string,
// then add it to the hashset
if (!ids.contains(phoneNumberofContact)) {
ids.add(phoneNumberofContact);
System.out.println(" Name--->" + name);
System.out.println(" Phone number of contact--->" + phoneNumberofContact);
SelectPhoneContact selectContact = new SelectPhoneContact();
selectContact.setName(name);
selectContact.setPhone(phoneNumberofContact);
selectPhoneContacts.add(selectContact);
}
} while (cursor.moveToNext());
} catch (Exception e) {
Toast.makeText(NewContact.this, "what the...", Toast.LENGTH_LONG).show();
e.printStackTrace();
// cursor.close();
} finally {
}
if (cursor != null) {
cursor.close();
}
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
adapter = new SelectPhoneContactAdapter(selectPhoneContacts, NewContact.this);
// we need to notify the listview that changes may have been made on
// the background thread, doInBackground, like adding or deleting contacts,
// and these changes need to be reflected visibly in the listview. It works
// in conjunction with selectContacts.clear()
adapter.notifyDataSetChanged();
listView.setAdapter(adapter);
//this function measures the height of the listview, with all the contacts, and loads it to be that
//size. We need to do this because there's a problem with a listview in a scrollview.
justifyListViewHeightBasedOnChildren(listView);
}
}
My model, getters and setters, is like:
public class SelectPhoneContact {
String phone;
public String getPhone() {return phone;}
public void setPhone(String phone) {
this.phone = phone;
}
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
And my custom adapter:
public class SelectPhoneContactAdapter extends BaseAdapter {
//define a list made out of SelectContacts and call it theContactsList
public List<SelectPhoneContact> theContactsList;
//define an array list made out of SelectContacts and call it arraylist
private ArrayList<SelectPhoneContact> arraylist;
Context _c;
//define a ViewHolder to hold our name and number info, instead of constantly querying
// findviewbyid. Makes the ListView run smoother
ViewHolder v;
public SelectPhoneContactAdapter(List<SelectPhoneContact> selectPhoneContacts, Context context) {
theContactsList = selectPhoneContacts;
_c = context;
this.arraylist = new ArrayList<SelectPhoneContact>();
this.arraylist.addAll(theContactsList);
Collections.sort(this.arraylist, new Comparator<SelectPhoneContact>() {
#Override
public int compare(SelectPhoneContact t1, SelectPhoneContact t2) {
if(t2.getPhone().equals ("3456781276")) { // put the phone number you want on top here
return 1;
} else {
return t1.getName().compareTo(t2.getName());
}
}
});
}
#Override
public int getCount() {
return arraylist.size();
}
#Override
public Object getItem(int i) {
return arraylist.get(i);
}
#Override
public long getItemId(int i) {
return i;
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
static class ViewHolder {
// In each cell in the listview show the items you want to have
// Having a ViewHolder caches our ids, instead of having to call and load each one again and again
CheckBox checkbox;
TextView title, phone, lookup;
// CheckBox check;
}
#Override
public View getView(int i, View convertView, ViewGroup viewGroup) {
//we're naming our convertView as view
View view = convertView;
if (view == null) {
//if there is nothing there (if it's null) inflate the layout for each row
LayoutInflater li = (LayoutInflater) _c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = li.inflate(R.layout.phone_inflate_listview, null);
//or else use the view (what we can see in each row) that is already there
} else {
view = convertView;
}
v = new ViewHolder();
// So, for example, title is cast to the name id, in phone_inflate_listview,
// phone is cast to the id called no etc
v.title = (TextView) view.findViewById(R.id.name);
// v.check = (CheckBox) view.findViewById(R.id.check);
v.phone = (TextView) view.findViewById(R.id.no);
// store the holder with the view
final SelectPhoneContact data = (SelectPhoneContact) arraylist.get(i);
v.title.setText(data.getName());
v.phone.setText(data.getPhone());
view.setTag(data);
return view;
}
}
What about using add(int index, E element)?
if (/* check your condition here: is it the number you are looking for? */) {
// insert the contact at the beginning
selectPhoneContacts.add(0, selectContact);
} else {
// insert it at the end (default)
selectPhoneContacts.add(selectContact);
}
Try to modify your adapter's constructor like this:
public SelectPhoneContactAdapter(List<SelectPhoneContact> selectPhoneContacts, Context context) {
theContactsList = selectPhoneContacts;
_c = context;
this.arraylist = new ArrayList<SelectPhoneContact>();
this.arraylist.addAll(theContactsList);
Collections.sort(this.arraylist, new Comparator<SelectPhoneContact>() {
#Override
public int compare(SelectPhoneContact t1, SelectPhoneContact t2) {
if(t2.getPhone().equals("3456781276")) { // put the phone number you want on top here
return 1;
} else {
return t1.getName().compareTo(t2.getName());
}
}
});
}
So we are basically sorting the ArrayList before the adapter starts using it.
So in this example, I am putting the phone number "3456781276" on top of everything else. If the phone number is NOT "3456781276", it will sort all the items by the name. (If you don't want to sort it by name, just remove the else statement.
Hope this helps.
EDIT:
in getView(), change:
final SelectPhoneContact data = (SelectPhoneContact) theContactsList.get(i);
to:
final SelectPhoneContact data = (SelectPhoneContact) arraylist.get(i);
Change getCount() method like this:
#Override
public int getCount() {
return arraylist.size();
}
Change getItem() method like this:
#Override
public Object getItem(int i) {
return arraylist.get(i);
}
You must use arraylist everywhere since that is the list we are sorting.
An easy way to achieve this, just use a view in XML which contains your phone number, and set it to invisible in default, if the list shows, set the view to be visible. I hope this post help you!!!
You can easily manipulate with items positions in ArrayList with Collections.swap(); by looping through your contacts and by simply checking is number matching your number if does put it on the top for example:
Collections.swap(myArrayList, i, 0);
Refering to: http://www.java2s.com/Code/Java/Collections-Data-Structure/SwapelementsofJavaArrayList.htm
I have created SQL database in my Android project and managed to populate ListView with data that I inserted. Next part of the project is to enable CheckBoxes for every item (from SQL database) in my ListView. I have found a way how to do it with String values, but I am not sure how to do it with values from SQL database.
Is it somehow possible to put SQL values into String ? Or I need to use different data values to populate my ListView ?
I am still nooby with SQL in Android, so every advice would be helpfull.
Here is code:
public class ModelBreakfast {
public String name; //This String need to be filled with SQL datas. If it's possible.
public boolean checked;
public ModelBreakfast(String name, boolean checked){
this.name = name;
this.checked = checked;
}
}
Just need to say that I tried to replace public String name; with my ContractClass
public FoodContract.FoodEntry entry; where I defined all String values for my database rows.
(_ID, NAME, etc). (I only saw that way to solve my problem). So, code is now looking like this:
public ModelBreakfast(FoodContract.FoodEntry entry, boolean checked){
this.entry = entry;
this.checked = checked;
}
Next class is CustomAdapter
public class CustomAdapterBreakfast extends ArrayAdapter<ModelBreakfast> {
private ArrayList<ModelBreakfast> dataSet;
Context mContext;
private static class ViewHolder {
TextView txtName;
CheckBox checkBox;
}
public CustomAdapterBreakfast(ArrayList<ModelBreakfast> data, Context context){
super(context, R.layout.activity_breakfast_checkbox, data);
this.dataSet = data;
this.mContext = context;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
final View result;
if (convertView == null) {
viewHolder = new ViewHolder();
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.activity_breakfast_checkbox, parent, false);
viewHolder.txtName = (TextView) convertView.findViewById(R.id.txtName);
viewHolder.checkBox = (CheckBox) convertView.findViewById(R.id.checkBox);
result=convertView;
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
result=convertView;
}
ModelBreakfast item = getItem(position);
viewHolder.txtName.setText(item.name); //Need to replace or modify this part
viewHolder.checkBox.setChecked(item.checked);
return result;
}}
Last part is the MainActivity
public class BreakfastActivity extends AppCompatActivity {
ArrayList<ModelBreakfast> modelBreakfastArrayList;
private CustomAdapterBreakfast customAdapterBreakfast;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_breakfast);
ListView listView = (ListView) findViewById(R.id.listBreakfast);
modelBreakfastArrayList = new ArrayList<>();
modelBreakfastArrayList.add(new ModelBreakfast("This string will show in ListView. So I need to somehow replace that String with SQL datas.", false));
customAdapterBreakfast = new CustomAdapterBreakfast(modelBreakfastArrayList, getApplicationContext());
listView.setAdapter(customAdapterBreakfast);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
ModelBreakfast modelBreakfast= modelBreakfastArrayList.get(position);
modelBreakfast.checked = !modelBreakfast.checked;
customAdapterBreakfast.notifyDataSetChanged();
}
});
}}
After I replaced public String name; with my ContractClass public FoodContract.FoodEntry entry; I understand that I can't use
modelBreakfastArrayList.add(new ModelBreakfast("This string will show in ListView", false));. But than what do I need to set, so my ListView with CheckBoxes will displaying my SQL database values ?
Should I use ArrayList instead String? And how?
Again as I said before in the last question. Look at the for loops. So within your SQLDB Activity and in the function that is taking the values out of the database, you need to populate an array list that you will call in the MainActivity.
public ArrayList<String> getAirportRegion(String code)
Cursor cursor = db.rawQuery("SELECT "+ AIRPORT_NAME +
" FROM " + AIRPORT_TABLE + " WHERE " + AIRPORT_CODE + " = " + code, null);
if (cursor.moveToFirst()) {
while (!cursor.isAfterLast()) {
arrayList.add(cursor.getString(cursor.getColumnIndex(AIRPORT_NAME)));
cursor.moveToNext();
}
}
cursor.close();
return arrayList;
}
Now in the Main Activity get a reference to the database and set it to modelBreakfastArrayList like so
airportArrayList = mdb.getAirportRegion();
Voila it is done
Do you see how I am extracting the data? For the most part, this is the best way to extract lists from the local database. Keep these Activities separate, also I hope you have the Database activity as a singleton, otherwise, you will have multiple databases and that will guzzle up resources. Look below for how I start these database activities.
private DBHelper(Context context) {
super(context, "db", null, DATABASE_VERSION);
}
private static DBHelper INSTANCE;
public static DBHelper getInstance(Context context) {
if (INSTANCE == null) {
INSTANCE = new DBHelper(context);
}
return INSTANCE;
}
How can I get the name of a specific cell in my listview to load into a new activity ? At present, when I click any of the arrows it loads the last person in my contacts (Yvonne) in the next activity that loads when the arrow is clicked. I want the name in the corresponding cell to load in the next activity. How can I do this?
For example, in the image above, I want Alexi to load into the next Activity. But instead I keep getting Yvonne.
At present my code looks like this:
public class MainActivity extends Activity {
// ArrayList called selectContacts that will contain SelectContact info
ArrayList<SelectContact> selectContacts;
ListView listView;
SearchView search;
SelectContactAdapter adapter;
String name;
String phoneNumber;
String lookupkey;
CharSequence nameofcontact;
// *****18-04-2016***
Cursor cursor;
// ListView mainListView;
// ArrayList hashMapsArrayList;
public String cleartext;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//selectContacts is an empty array list that will hold our SelectContct info
selectContacts = new ArrayList<SelectContact>();
listView = (ListView) findViewById(R.id.contacts_list);
search = (SearchView) findViewById(R.id.searchView);
//*** setOnQueryTextListener ***
search.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
#Override
public boolean onQueryTextSubmit(String query) {
// TODO Auto-generated method stub
return false;
}
#Override
public boolean onQueryTextChange(String newText) {
// TODO Auto-generated method stub
adapter.filter(newText);
return false;
}
});
}
// Load data on background
class LoadContact extends AsyncTask<Void, Void, Void> {
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected Void doInBackground(Void... voids) {
// Perhaps running this thread on the UI thread has solved the issue of the app
// crashing? ListView had not been updating properly, I think.
runOnUiThread(new Runnable() {
public void run() {
// we want to delete the old selectContacts from the listview when the Activity loads
// because it may need to be updated and we want the user to see the updated listview,
// like if the user adds new names and numbers to their phone contacts.
selectContacts.clear();
// we have this here to avoid cursor errors
if (cursor != null) {
cursor.moveToFirst();
}
try {
// get a handle on the Content Resolver, so we can query the provider,
cursor = getApplicationContext().getContentResolver()
// the table to query
.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
// Null. This means that we are not making any conditional query into the contacts table.
// Hence, all data is returned into the cursor.
// Projection - the columns you want to query
null,
// Selection - with this you are extracting records with assigned (by you) conditions and rules
null,
// SelectionArgs - This replaces any question marks (?) in the selection string
// if you have something like String[] args = { "first string", "second#string.com" };
null,
// display in ascending order
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
// get the column number of the Contact_ID column, make it an integer.
// I think having it stored as a number makes for faster operations later on.
int Idx = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.CONTACT_ID);
// get the column number of the DISPLAY_NAME column
int nameIdx = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME);
// get the column number of the NUMBER column
int phoneNumberIdx = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
// ****
int contactlookupkey = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY);
// ****
cursor.moveToFirst();
// We make a new Hashset to hold all our contact_ids, including duplicates, if they come up
Set<String> ids = new HashSet<>();
do {
System.out.println("=====>in while");
// get a handle on the contactid, which is a string. Loop through all the contact_ids
String contactid = cursor.getString(Idx);
// if our Hashset doesn't already contain the contactid string,
// then add it to the hashset
if (!ids.contains(contactid)) {
ids.add(contactid);
HashMap<String, String> hashMap = new HashMap<String, String>();
// get a handle on the display name, which is a string
name = cursor.getString(nameIdx);
// get a handle on the phone number, which is a string
phoneNumber = cursor.getString(phoneNumberIdx);
// String image = cursor.getString(photoIdIdx);
lookupkey = cursor.getString(contactlookupkey);
System.out.println("Id--->" + contactid + " Name--->" + name);
System.out.println("Id--->" + contactid + " Number--->" + phoneNumber);
System.out.println("Id--->" + contactid + " lookupkey--->" + lookupkey);
SelectContact selectContact = new SelectContact();
selectContact.setName(name);
selectContact.setPhone(phoneNumber);
selectContacts.add(selectContact);
}
} while (cursor.moveToNext());
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}});
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
//into each inflate_listview, put a name and phone number, which are the details making
// our SelectContact, above. And SelectContacts is all these inflate_listviews together
// This is the first property of our SelectContactAdapter, a list
// The next part, MainActivity.this, is our context, which is where we want the list to appear
adapter = new SelectContactAdapter(selectContacts, MainActivity.this);
// adapter.notifyDataSetChanged();
listView.setAdapter(adapter);
// Select item on listclick
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
listView.setFastScrollEnabled(true);
// we need to notify the listview that changes may have been made on
// the background thread, doInBackground, like adding or deleting contacts,
// and these changes need to be reflected visibly in the listview. It works
// in conjunction with selectContacts.clear()
// adapter.notifyDataSetChanged();
}
});
}}
//the is the arrow image, it opens the activity for show and edit
public void DisplayorEditContact(View v) {
System.out.println("works so far");
System.out.println(name);
Intent intent = new Intent(getApplicationContext(), EditorNewContact.class).putExtra("thecontactname",name);
startActivity(intent);
}
#Override
protected void onStop() {
super.onStop();
}
#Override
protected void onResume() {
//I want to clear the searchview text when my app resumes or closes, but I keep getting an error, my app shuts down
// cleartext = findViewById(R.id.searchView).toString();
// cleartext.isEmpty();
// search.setQuery("", false);
super.onResume();
// load the contacts again, refresh them, when the user resumes the activity
LoadContact loadContact = new LoadContact();
loadContact.execute();
// cursor.close();
}
}
The salient part of the code I believe is :
//the is the arrow image, it opens the activity for show and edit
public void DisplayorEditContact(View v) {
System.out.println("works so far");
System.out.println(name);
Intent intent = new Intent(getApplicationContext(), EditorNewContact.class).putExtra("thecontactname",name);
startActivity(intent);
}
And the child activity, into which I want Alexi to load (at present I keep getting Yvonne) looks like this :
public class EditorNewContact extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.edit_contact);
String s= getIntent().getStringExtra("thecontactname");
System.out.println("the name is" + s);
EditText edittext = (EditText) findViewById(R.id.editText);
edittext.setText(s);
I was asked to share my SelectContactAdapter, so here it is :
public class SelectContactAdapter extends BaseAdapter {
//define a list made out of SelectContacts and call it _data
public List<SelectContact> _data;
//define an array list made out of SelectContacts and call it arraylist
private ArrayList<SelectContact> arraylist;
Context _c;
//define a ViewHolder to hold our name and number info, instead of constantly querying
// findviewbyid. Makes the ListView run smoother
ViewHolder v;
// RoundImage roundedImage;
public SelectContactAdapter(List<SelectContact> selectContacts, Context context) {
_data = selectContacts;
_c = context;
this.arraylist = new ArrayList<SelectContact>();
this.arraylist.addAll(_data);
}
#Override
public int getCount() {
return _data.size();
}
#Override
public Object getItem(int i) {
return _data.get(i);
}
#Override
public long getItemId(int i) {
return i;
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
#Override
public View getView(int i, View convertView, ViewGroup viewGroup) {
//we're naming our convertView as view
View view = convertView;
//if there is nothing there (if it's null) inflate the layout for each row
if (view == null) {
LayoutInflater li = (LayoutInflater) _c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = li.inflate(R.layout.inflate_listview, null);
// Log.e("Inside", "here--------------------------- In view1");
//or else use the view (what we can see in each row) that is already there
} else {
view = convertView;
// Log.e("Inside", "here--------------------------- In view2");
}
v = new ViewHolder();
// So, for example, title is cast to the name id, in activity main,
// phone is cast to the id called no etc
v.title = (TextView) view.findViewById(R.id.name);
// v.check = (CheckBox) view.findViewById(R.id.check);
v.phone = (TextView) view.findViewById(R.id.no);
v.imageView = (ImageView) view.findViewById(R.id.arrowright);
// for each new cell with title, name, number etc...
//
final SelectContact data = (SelectContact) _data.get(i);
v.title.setText(data.getName());
// v.check.setChecked(data.getCheckedBox());
v.phone.setText(data.getPhone());
view.setTag(data);
return view;
}
// Filter Class
public void filter(String charText) {
charText = charText.toLowerCase(Locale.getDefault());
// _data is our list of contacts
_data.clear();
// If there is nothing in the searchview, if the charText length is 0,
// then show all the contacts
if (charText.length() == 0) {
_data.addAll(arraylist);
// or else....
} else {
for (SelectContact wp : arraylist) {
// If a contact's name matches the input thus far, which is charText,
// then include it in the listview.
if ((wp.getName().toLowerCase(Locale.getDefault())
.contains(charText)) || (wp.getPhone().toLowerCase(Locale.getDefault())
.contains(charText)))
{
_data.add(wp);
}
}
}
notifyDataSetChanged();
}
static class ViewHolder {
// In each cell in the listview show the items you want to have
ImageView imageView;
TextView title, phone;
// CheckBox check;
}
}
It is hard to predict how your code works without seeing the SelectContactAdapter source code. But I can suggest a probably easiest solution, which is using a tag
all you need to do is to set a tag to your arrow image somewhere in your adapter's getView method like this:
youArrowImage.setTag("here_is_a_name_of_a_row");
and then in your DisplayorEditContact(View v) you can access it like this:
String itemName = (String)v.getTag();
here I suppose that v is a reference to clicked arrow image
You could also just monitor the click in your ListView setOnItemClickListener.
// Click listener to bring to profile
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent viewProfileIntent = new Intent(getApplicationContext(), UserProfile.class);
viewProfileIntent.putExtra("name", selectContacts.get(position));
Log.i("User Tapped", selectContacts.get(position));
startActivity(viewProfileIntent);
}
});
Please add following line to your SelectContactsAdapter.java
final SelectContact data = (SelectContact) _data.get(i);
v.title.setText(data.getName());
v.phone.setText(data.getPhone());
// Please add this line to your existing code right after above lines
v.imageView.setTag(data.getName());
Modify your method as below
public void DisplayorEditContact(View v) {
System.out.println("works so far");
System.out.println(v.getTag().toString());
Intent intent = new Intent(getApplicationContext(), EditorNewContact.class).putExtra("thecontactname",v.getTag().toString());
startActivity(intent);
}
Hope this helps
Your this method will like this:
public void DisplayorEditContact(View v) {
TextView tvName = (TextView) v.findViewById(R.id.YOUR_TEXT_NAME);
System.out.println(tvName.getText().toString());
}
Hope this will solve your problem :)
You need to use onItemClickListener on your list view.
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
SelectContact contact = (SelectContact)parent.getItemAtPosition(position);
Intent secondActivity = new Intent(MainActivity.this, EditorNewContact.class);
secondActivity.putExtra("Key", contact);
startActivity(secondActivity);
}
});
Also, in your EditorNewContact activity, you will need to resolve this intent in the onCreate method, like:
Intent intent = getIntent();
SelectContact contact = (SelectContact) intent.get("Key");
Also, your SelectContact class can be Serializeable, If that is the can, the the intent will look like.
Intent secondActivity = new Intent(MainActivity.this, EditorNewContact.class);
secondActivity.putSerializeableExtra("Key", contact);
startActivity(secondActivity);
And, to resolve this:
Intent intent = getIntent();
SelectContact contact = (SelectContact) intent.getSerializableExtra("Key");
i hope this helps.
I am building an sms app which lists the contact pic,name,message,date of a conversation.
I'm using a custom adapter to list them.Below is the code
public class ConvRowAdapter extends ArrayAdapter<ConvItem> {
private Context my_context;
// View lookup cache
private static class ViewHolder {
TextView name,body,date;
ImageView img;
}
public ConvRowAdapter(Context context, ArrayList<ConvItem> ConvItems) {
super(context, R.layout.convitem, ConvItems);
my_context=context;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// Get the data item for this position
ConvItem ConvItem = getItem(position);
// Check if an existing view is being reused, otherwise inflate the view
ViewHolder viewHolder; // view lookup cache stored in tag
if (convertView == null) {
viewHolder = new ViewHolder();
LayoutInflater inflater = LayoutInflater.from(getContext());
convertView = inflater.inflate(R.layout.convitem, null);
viewHolder.img = (ImageView) convertView.findViewById(R.id.iv_photo);
viewHolder.name = (TextView) convertView.findViewById(R.id.tv_name);
viewHolder.date = (TextView) convertView.findViewById(R.id.tv_date);
viewHolder.body = (TextView) convertView.findViewById(R.id.tv_body);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
// Populate the data into the template view using the data object
if(ConvItem.getPhotoUri()!=null)
{
Bitmap bitmap=BitmapFactory.decodeStream(my_context.getContentResolver().openInputStream(ConvItem.getPhotoUri()));
viewHolder.img.setImageBitmap(bitmap);//Image is set
}
else
{
Bitmap bitmap=BitmapFactory.decodeResource(my_context.getResources(),R.drawable.contact_blue);
viewHolder.img.setImageBitmap(bitmap);
}
if(ConvItem.getDisplayName()==null)
viewHolder.name.setText(ConvItem.getAddress());//Phone number set
else
viewHolder.name.setText(ConvItem.getDisplayName());//If phone number exists in contacts display contact name
viewHolder.date.setText(ConvItem.getDate());//set date
viewHolder.body.setText(ConvItem.getBody());//setting the body of the message
// Return the completed view to render on screen
return convertView;
}
}
It takes 8-9 seconds to display the list.
Is there a way to improve the speed?
If so please post the correct way to do so.
Thanks in advance :)
Edited:
To check if the delay in loading is caused by the loading of images i tried deleting all photo setting statements and ran it.Still the loading takes the same time.
By using a simplecursoradapter i could display the body,date and message in no delay.But to display the photo with them i tried using the custom adapter.
This is the function which returns all sms into an arraylist.Please check the efficiency of the code:
public ArrayList<ConvItem> getSMS(){
ArrayList<ConvItem> ConvItems = new ArrayList<ConvItem>();
Uri uriSMSURI = Uri.parse("content://mms-sms/conversations?simple=true");
Cursor cur = getActivity().getContentResolver().query(uriSMSURI, null, null, null,
"date desc");
cur.moveToFirst();
while (!cur.isAfterLast()) {
ConvItem ConvItem = new ConvItem();
String address = null, body = null, dname = null,res=null,id_dummy=null;
Long date=null;
Uri uriphotoid=null;
//to obtain address from canonical-addresses
res = cur.getString(cur.getColumnIndex("recipient_ids"));
Uri ad =Uri.parse("content://mms-sms/canonical-addresses/");
Cursor curad=getActivity().getContentResolver().query(ad, null,null, null, null);
curad.moveToFirst();
while(!curad.isAfterLast())
{
id_dummy=curad.getString(curad.getColumnIndexOrThrow("_id"));
if(id_dummy.equals(res))
address=curad.getString(curad.getColumnIndexOrThrow("address"));
curad.moveToNext();
}
curad.close();
body = cur.getString(cur.getColumnIndexOrThrow("snippet"));
date = cur.getLong(cur.getColumnIndexOrThrow("date"));
Date datenew=new Date(date);
String formatted_date=new SimpleDateFormat(" "+"dd/MM/yyyy").format(datenew);
thread_id = cur.getString(cur.getColumnIndexOrThrow("_id"));
Long ContactID = fetchContactIdFromPhoneNumber(address);
//uriphotoid = getPhotoUri(ContactID);
dname = getcontactname(address);
ConvItem.setDisplayName(dname);
ConvItem.setThreadId(thread_id);
ConvItem.setAddress(address);
//ConvItem.setPhotoUri(uriphotoid);
ConvItem.setDate(formatted_date);
ConvItem.setBody(body);
ConvItems.add(ConvItem);
cur.moveToNext();
}
cur.close();
return ConvItems;
}
This function is running on my onCreate method in the main activity class.
I used loader.callbacks to correct the delay issue.
public class InboxLoaderFragment extends Fragment implements LoaderCallbacks<Cursor>{
private static final int LOADER_ID = 1;//identify which loader
LoaderManager lm;
SimpleCursorAdapter mAdapter;
ListView lv;
private LoaderManager.LoaderCallbacks<Cursor> mCallbacks;
public InboxLoaderFragment(){}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootview=inflater.inflate(R.layout.inboxloaderfragment,container,false);
lv=(ListView)rootview.findViewById(R.id.list);
// Create an empty adapter we will use to display the loaded data.
String[] uiBindFrom = {"recipient_ids","recipient_ids","snippet"};
int[] uiBindTo = {R.id.iv_photo,R.id.tv_name,R.id.tv_body};
mAdapter = new SimpleCursorAdapter(getActivity(),R.layout.convitem,null,uiBindFrom,uiBindTo, 0);
mAdapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
#Override
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
switch(view.getId())
{
case R.id.iv_photo:
String res=null,address=null;
//to obtain address from canonical-addresses
res = cursor.getString(cursor.getColumnIndex("recipient_ids"));
address=getadd(res);
Long ContactID = fetchContactIdFromPhoneNumber(address);
Uri uriphotoid = getPhotoUri(ContactID);
setcontactimage(uriphotoid,view);
//set photo using uri
return true;
case R.id.tv_name:
String res1=null,address1=null;
res1 = cursor.getString(cursor.getColumnIndex("recipient_ids"));
address1=getadd(res1);//to obtain address from canonical-addresses
String dname = getcontactname(address1);
if(dname!=null)//contact exits
((TextView)view).setText(dname);//contact exists
else
((TextView)view).setText(address1);//set display name
return true;
case R.id.tv_body:
String body = cursor.getString(cursor.getColumnIndexOrThrow("snippet"));
((TextView)view).setText(body);//set message body
return true;
default:
return false;
}
}
});
lv.setAdapter(mAdapter);
mCallbacks=this;
lm = getLoaderManager();
//Initiating the loader
lm.initLoader(LOADER_ID, null,mCallbacks);
return rootview;
}
#Override
public android.support.v4.content.Loader<Cursor> onCreateLoader(
int arg0, Bundle arg1) {
Uri baseUri = Uri.parse("content://mms-sms/conversations?simple=true");
return new CursorLoader(getActivity(), baseUri,
null, null, null,"date desc");
}
#Override
public void onLoadFinished(
android.support.v4.content.Loader<Cursor> arg0, Cursor arg1) {
switch (arg0.getId()) {
case LOADER_ID:
mAdapter.swapCursor(arg1);
break;
}
// The listview now displays the queried data
}
#Override
public void onLoaderReset(android.support.v4.content.Loader<Cursor> arg0) {
mAdapter.swapCursor(null);
}
}///main activity
App loads very fast now.But scrolling is laggy.How to implement viewholder design pattern inside a custom simplecursoradapter or a cursoradapter?
Is there a way to improve the speed?
Move your photo loading to a background thread, perhaps by using an image management library like Picasso.
Beyond that, use Traceview and other tools to determine specifically where your problems lie.