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);
}
});
}
Related
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'm a total beginner in programming and I need help in my second project which is a simple dictionary.
Shortly, at first I wanted to create an Array of Strings and display it by using a ListView. I had (and now I still have) list_item layout which contains two TextViews (one holds a word and second one its translation) and ListView in another XML. As I was learning from very clear tutorial, everything was working perfectly, nevertheless, I've decided to enlarge my dictionary. For this reason, instead of declaring all the words within the method, I wanted to load them from a txt file. I followed many tutorials on how to achieve this goal, however, I wasn't able to transform my code correctly. This is my code:
public class MainActivity extends AppCompatActivity {
public static void main(String[] args) throws FileNotFoundException {
ArrayList<AndroidFlavor> androidFlavors = new ArrayList<AndroidFlavor>();
BufferedReader file = new BufferedReader(new FileReader("words.txt"));
try {
StringBuilder builder = new StringBuilder();
String line;
while ((line = file.readLine()) != null) {
String token[] = line.split("\\|");
String mPolish = token[0];
String mEnglish = token[1];
AndroidFlavor androidFlavor = new AndroidFlavor(mPolish, mEnglish);
androidFlavors.add(androidFlavor);
}
for (int i = 0; i < androidFlavors.size(); i++) {
System.out.println(androidFlavors.get(i).getPolish() + " "
+ androidFlavors.get(i).getEnglish() + " ");
}
file.close();
} catch (Exception e) {
}}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
public void button1(View view) {
setContentView(R.layout.free);
AndroidFlavorAdapter flavorAdapter = new AndroidFlavorAdapter(this, androidFlavors);
ListView listView = (ListView) findViewById(R.id.lista);
listView.setAdapter(flavorAdapter);
}
I also have this:
public class AndroidFlavor {
// String for polish words
private String mPolish;
// String for English translations
private String mEnglish;
// New AndroidFlavor object.
public AndroidFlavor(String vPolish, String vEnglish) {
mPolish = vPolish;
mEnglish = vEnglish;
}
//Get Polish word
public String getPolish() {
return mPolish;
}
//Get English word
public String getEnglish() {
return mEnglish;
}
}
And finally this:
public class AndroidFlavorAdapter extends ArrayAdapter<AndroidFlavor> {
private static final String LOG_TAG = AndroidFlavorAdapter.class.getSimpleName();
super(context, 0, androidFlavors);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View listItemView = convertView;
if(listItemView == null) {
listItemView = LayoutInflater.from(getContext()).inflate(
R.layout.list_item, parent, false);
}
AndroidFlavor currentAndroidFlavor = getItem(position);
TextView nameTextView = (TextView) listItemView.findViewById(R.id.polish);
nameTextView.setText(currentAndroidFlavor.getPolish());
TextView numberTextView = (TextView) listItemView.findViewById(R.id.english);
numberTextView.setText(currentAndroidFlavor.getEnglish());
return listItemView;
}
}
I have the following question: after clicking the button1, how can I open my ListView based on array of Strings from txt file? I'd like to obtain the same effect (i.e. display ListView from 2 TextViews) just like before I added txt file to my project.
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.
I have a peculiar problem. I am parsing a restaurant's menu card. They have it in english and in german. I have a class FoodItem as :
public class FoodItem {
private int foodClass;
private String foodType;
private String foodName;
private String foodCost;
private String hauptBeilage;
private String salat;
}
Now, I have an arraylist of fooditems downloaded using Jsoup. I separate the german and english menu using the String foodType.
I want to list german menu at the start. But, I get the english menu appended to the list as well. How should I tackle this?
My downloadThread (Jsoup) is :
public void run()
{
Log.i("downloadThread", "Inside run() - Starting getFoodItems");
getDailyGerman();
getDailyEnglish();
//Sending a message through handler here
}
In my activity, I have:
handler = new android.os.Handler() {
#Override
public void handleMessage(Message msg) {
foodItemAdapter.notifyDataSetChanged();
}
};
If I send a message through handler after getDailyGerman(); then i get a illegalstateexception saying the content of the adapter has changed, but the listview is not updated.
My Adapter code :
public FoodItemAdapter(Context context, int textViewResourceId, ArrayList<FoodItem> FoodItemArg) {
super(context, textViewResourceId, FoodItemArg);
FoodItemAdapter.foodItems = FoodItemArg;
this.setNotifyOnChange(false);
// if(FoodItemAdapter.foodItems == null)
// Log.i("Adapter", "Problem Inside Adapter Constructor");
}
//=========================public methods============================
public static ArrayList<FoodItem> getDailyEnglishFoodItems()
{
ArrayList<FoodItem> returnList = new ArrayList<FoodItem>();
for(FoodItem eachItem : FoodItemAdapter.foodItems)
{
if(eachItem.getFoodClass() == 1)
{
Log.i("Adapter" , "Adding English Daily Food : " + eachItem.getFoodName());
returnList.add(eachItem);
}
}
return returnList;
}
public static ArrayList<FoodItem> getDailyGermanFoodItems()
{
ArrayList<FoodItem> returnList = new ArrayList<FoodItem>();
for(FoodItem eachItem : FoodItemAdapter.foodItems)
{
if(eachItem.getFoodClass() == 2)
{
Log.i("Adapter" , "Adding German Daily Food : " + eachItem.getFoodName());
returnList.add(eachItem);
}
}
return returnList;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
/*
* Describes each view in the list view.
* Get the question and find the question text, timestamp and the votes.
* Show them in the textview which is a part of the listview.
*/
View v = convertView;
FoodItem foodItem =(FoodItem) FoodItemAdapter.foodItems.get(position);
if(foodItem == null)
{
Log.i("Adapter", "Null Food Item");
}
int colorPos = 0;
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater)this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = inflater.inflate(R.layout.fooditem_row, null);
colorPos = position % colors.length;
}
Please help as I am stuck at this point for 3 days. Thanks.
I had the same issue once I added the items and called
notifyDataSetChanged() in the UI
thread issue solved
From What I understand of your question, you want to have the English items at the top of the list then the German Items. you can do that using Collection.sort method and Using a specific comparator for the task in hand.
For example:
final List<FoodItem> combinedList = getDailyGermanFoodItems();
combinedList.addAll(getDailyEnglishFoodItems());
Collections.sort(compinedList, new FoodItemComparator());
//then you call the handler to update the adapter and the listView
handler.post(new Runnable(){
public void run(){
FoodItemAdapter adapter = new FoodItemAdapter(activity.this, layout, combinedList);
listView.setAdapter(adapter);
}});
where FoodItemComparator:
public class FoodItemComparatorimplements Comparator<FoodItem>{
public int compare(FoodItem item1, item2) {
String foodType1 = item1.getFoodType();
String foodType2 = item2.getFoodType();
if (foodType1.equals(foodType2))
return 0;
if (foodType1.equals("English"))
return 1;
if (foodType2.equals("English))
return -1;
return foodType1.compareTo(foodType2);
}
}
Assuming foodType Value is guaranteed to be German/English only.
Also you will have to have a getter funcion inside your FoodItem Class so the comparator can access it:
Class FoodItem
.......
public String getFoodType(){
return foodType;
}
EDIT
If you want to display each one alone , then store the two lists inside your activity object, then when user select a language (english / german):
FoodItemAdapter adapter = new FoodItemAdapter(activity.this, layout, germanList);
listView.setAdapter(adapter);
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.