This is the tutorial that I followed to use a custom Listview Adapter. The problem I am having is that when I try to clear the adapter, the app crashes and throws java.lang.UnsupportedOperationException
if(adapter != null) {
adapter.clear();
}
UPDATED CODE:
private void setListViewAdapterToDate(int month, int year, int dv)
{
if(summaryAdapter != null) {
summaryAdapter.clear();
}
setListView(month, year, dv);
summaryList.addAll(Arrays.asList(summary_data));
summaryAdapter = new SummaryAdapter(this.getActivity().getApplicationContext(), R.layout.listview_item_row, summaryList);
summaryAdapter.notifyDataSetChanged();
calendarSummary.setAdapter(summaryAdapter);
}
Looking around a bit, it would seem that initializing the adapter with an array is the problem. See UnsupportedOperationException with ArrayAdapter.remove and Unable to modify ArrayAdapter in ListView: UnsupportedOperationException
Try using an ArrayList instead of an array like so
ArrayList<Weather> weather_data = new ArrayList<Weather>()
weather_data.add( new Weather(R.drawable.weather_cloudy, "Cloudy") );
// continue for the rest of your Weather items.
If you're feeling lazy, you can convert your array to an ArrayList this way
ArrayList<Weather> weatherList = new ArrayList<Weather>();
weatherList.addAll(Arrays.asList(weather_data));
To finish the conversion to ArrayList in your WeatherAdapter class you will want to remove the Weather data[] = null; and all of it's references (such as inside the constructor) because ArrayAdapter holds the data for you and you can access it with getItem
So inside of your getView function you would change Weather weather = data[position]; to Weather weather = getItem(position);
Update
Modify your udated code with
private void setListViewAdapterToDate(int month, int year, int dv)
{
setListView(month, year, dv);
if(summaryAdapter != null) {
summaryAdapter.clear();
summaryAdapter.addAll( summaryList );
summaryAdapter.notifyDataSetChanged();
} else {
summaryList.addAll(Arrays.asList(summary_data));
summaryAdapter = new SummaryAdapter(this.getActivity().getApplicationContext(), R.layout.listview_item_row, summaryList);
}
calendarSummary.setAdapter(summaryAdapter);
}
Related
I have multiple variables, we'll call them varA, varB, varC, and varD. When a button is pressed these 4 variables are to be stored in an ArrayList, together. This creates a single entry which can be viewed by variable varA. All the entries can be viewed in a ListView which is done through an ArrayAdapter.
onCreate
ArrayAdapter<String> profileAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, arrayList);
profileList.setAdapter(profileAdapter);
When a button is pressed
// Add data to a new profile in #+id/profileList ListView
ArrayList<String> arrayList= new ArrayList<>();
arrayList.add(varA);
System.out.println("varA's");
System.out.println(arrayList);
((ArrayAdapter)profileList.getAdapter()).notifyDataSetChanged();
Layout
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/overviewLayout">
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/profileList"/>
</RelativeLayout>
I am having issues with the ListView showing the items that were added. Also, I was trying to use TinyDB to store these items so they can be referenced later since that ArrayList used on button press is just temporary. I also I'm having trouble figuring out how to set this up so that each time the button is pressed those 4 vars will stay together in a "profile" so they don't get mixed up with some other data that might be added later.
To avoid repeated values from getting stored check if it already exists :
if(!arrayList.contains(varA)){
arrayList.add(varA);
}
For listview updation problem ,use notifyItemInserted(position) instead of notifyDataSetChanged()
yourAdapter.notifyItemInserted(position);
As I understand, you want to display a list which contains 4 datas in one entry and showing just the first of them.
If it is, you could create a custom Model, it will be easier. This will store your datas in one object:
ProfileModel
|
|-- varA
|-- varB
|-- varC
'-- varD
Your custom model could look like something like this:
public class ProfileModel {
private String varA;
private float varB;
private int varC;
private String varD;
// getters
...
// setters
...
public ProfileModel() { }
}
Initialize the list of profiles at start:
ArrayList<ProfileModel> list = new ArrayList<>();
To populate the main list do as follows:
// create a list of object to the datas
ProfileModel profile = new ProfileModel();
profile.setVarA(varA);
profile.setVarB(varB);
profile.setVarC(varC);
profile.setVarD(varD);
// then fill the first list
list.add(profile);
Since you want to fill the adapter with other datas than a simple String but a custom model, I'd prefer to use a custom adapter. It's not that hard to create one, and because sometimes, you maybe want a different behavior on an update, I'd also prefer to use a custom notifyDataSetChanged method.
So, here's a simple adapter with the same layout as the one you used:
public class ProfileAdapter extends BaseAdapter {
private Context context;
private ArrayList<ProfileModel> listProfiles;
public ProfileAdapter(Context context, ArrayList<ProfileModel> listProfiles) {
super();
this.context = context;
this.listProfiles = listProfiles;
}
private class ViewHolder {
private TextView textVarA;
}
#Override
public int getCount() {
return listProfiles != null ? listProfiles.size() : 0;
}
#Override
public Object getItem(int i) {
return listProfiles.get(i); // will return the profile model from position
}
#Override
public long getItemId(int i) {
return i;
}
#Override
public View getView(int position, View view, ViewGroup viewGroup) {
ViewHolder holder;
if (view == null) {
holder = new ViewHolder();
view = LayoutInflater.from(context)
.inflate(android.R.layout.simple_list_item_1, null);
holder.textVarA = (TextView) view.findViewById(android.R.id.text1);
view.setTag(holder);
} else {
holder = (ViewHolder) view.getTag();
}
ProfileModel profile = listProfiles.get(i);
holder.textVarA.setText(profile.getVarA()); // get varA value
return view;
}
public void addEntry(ProfileModel newEntry) {
listProfiles.add(newEntry);
this.notifyDataSetChanged();
}
}
Then, you can set the adapter as follows:
ProfileAdapter profileAdapter = new ProfileAdapter(this, list);
profileList.setAdapter(profileAdapter);
To update this adapter:
// create a list of object to the datas
ProfileModel profile = new ProfileModel();
profile.setVarA(varA);
profile.setVarB(varB);
profile.setVarC(varC);
profile.setVarD(varD);
profileAdapter.addEntry(profile); // this will call notifyDataSetChanged
Finally, I'm not very aware about TinyDB, but I saw it's using some JSON file to store data. So you could create a method to convert your datas to JSONArray which its name is varA and has values varB, varC, varD. There is many examples how to convert datas to JSON (as using Gson to convert your model to a JSON format).
Hope this will be useful.
I'm writing a simple program to request a JOSN request of a list of earthquakes to display for users. I use Asynctask to put the request in the background thread and use an ArrayList Adaptor to display the relevant information. I declare an empty ArrayList and then extract the JOSN request and put them in a temporary list and then assign the temporary list to the empty ArrayList.
I use a debugger tool to see that in the updateEarthquakeList method. I set the break point in the updateEarthquakeList method. this.earthquak and earthquakes both have 10 elements. Pics are as follow:
But when I set the break point after task.execute(USGS_REQUEST_URL) in the onCreate method, I got this:
As the pics shown after execute the AsyncTask the ArrayList is empty. But inside the AsyncTask The array was actually updated. (To do a little experiment I create an int haha as 0 and change it to 1 in the Asynctask, but it changed back to 0 afterwards)
How is this happen and how do I supposted to make it right?
public class EarthquakeActivity extends AppCompatActivity {
public static final String LOG_TAG = EarthquakeActivity.class.getName();
ArrayList<Earthquake> earthquak = new ArrayList<Earthquake>();
int haha = 0;
private static final String USGS_REQUEST_URL = "http://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&eventtype=earthquake&orderby=time&minmag=6&limit=10";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.earthquake_activity);
EarthquakeAsyncTask task = new EarthquakeAsyncTask();
task.execute(USGS_REQUEST_URL);
// Create a fake list of earthquake locations.
// Find a reference to the {#link ListView} in the layout
ListView earthquakeListView = (ListView) findViewById(R.id.list);
// Create a new {#link ArrayAdapter} of earthquakes
EarthquakeAdapter adapter = new EarthquakeAdapter(this, earthquak);
// Set the adapter on the {#link ListView}
// so the list can be populated in the user interface
earthquakeListView.setAdapter(adapter);
//OPEN a web page of a specific when textview is clicked.
earthquakeListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent intent = new Intent(Intent.ACTION_VIEW, earthquak.get(position).getUrl());
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
}
}
});
}
private void updateEarthquakeList(ArrayList<Earthquake> earthquake) {
this.earthquak = earthquake;
haha = 1;
}
private class EarthquakeAsyncTask extends AsyncTask<String, Void, ArrayList<Earthquake>> {
#Override
protected ArrayList<Earthquake> doInBackground(String... urls) {
if (urls.length < 1 || urls[0] == null) {
return null;
}
ArrayList<Earthquake> earthquakes = QueryUtils.fetchEarthquakeData(urls[0]);
return earthquakes;
}
#Override
protected void onPostExecute(ArrayList<Earthquake> earthquakes) {
updateEarthquakeList(earthquakes);
}
}
I've created a ListView which is using FastScroll. (see pic) When the user clicks any of the below Button (viz. All Tracks, Artists, Album), everytime the following custom ArrayAdater is called
ArrayAdapter<String> adapter = new ScrollIndexListAdapter(Listing.this, elements);
//Code for ScrollIndexListAdapter is below
and the same ListView is updated.
PROBLEM: According to my investigation in Android, the getSections() method is called only once (i.e. only when the first time ScrollIndexListAdapter is called).
This time the sections are populated & the fastScrolling works perfectly.
But when I update the ListView by clicking on Artists/Album, the getSections() method is not called. So the older sections are used, and the FastScrolling still shows previews of old alphabets.
So, how can I make sections get updated everytime when the ListView is updated?
There is a setSections() method, but I'm not able to find how to use it.
Code for ScrollIndexListAdapter class:
public class ScrollIndexListAdapter extends ArrayAdapter implements
SectionIndexer {
// Variables for SectionIndexer List Fast Scrolling
HashMap<String, Integer> alphaIndexer;
String[] sections;
private static ArrayList<String> list = new ArrayList<String>();
public ScrollIndexListAdapter(Context context, ArrayList<String> list) {
super(context, android.R.layout.simple_list_item_1, android.R.id.text1,
list);
this.list.clear();
this.list.addAll(list);
/*
* Setting SectionIndexer
*/
alphaIndexer = new HashMap<String, Integer>();
int size = list.size();
for (int x = 0; x < size; x++) {
String s = (String) list.get(x);
// Get the first character of the track
String ch = s.substring(0, 1);
// convert to uppercase otherwise lowercase a -z will be sorted
// after upper A-Z
ch = ch.toUpperCase();
if (!alphaIndexer.containsKey(ch)) {
alphaIndexer.put(ch, x);
}
}
Set<String> sectionLetters = alphaIndexer.keySet();
// create a list from the set to sort
ArrayList<String> sectionList = new ArrayList<String>(
sectionLetters);
Collections.sort(sectionList);
sections = new String[sectionList.size()];
sectionList.toArray(sections);
}
/*
* Methods for AphhabelIndexer for List Fast Scrolling
*/
#Override
public int getPositionForSection(int section) {
String letter = (String) sections[section];
return alphaIndexer.get(letter);
}
#Override
public int getSectionForPosition(int position) {
String letter = (String) sections[position];
return alphaIndexer.get(letter);
}
#Override
public Object[] getSections() {
return sections;
}
}
It seems that latest FastScroll version doesn't have that problem. But, in your case, there's a turn around. When setting the adapter to the ListView, disable and enable the fast scrolling. See the code below:
adapter = new ScrollIndexListAdapter(this, data);
listView.setFastScrollEnabled(false);
listView.setAdapter(adapter);
listView.setFastScrollEnabled(true);
I'm trying to download images for each artist that has music on my phone, then show these images in a GridView. I'm using the lastfm-java library that Last.fm recommends using. The method you call to fetch an artists image is getImageURL(ImageSize size), but before you do this, you need to tell it which artist you want to reference with a String parameter. So, in full it would be something like this:
#Override
protected String doInBackground(Object... arg0) {
Artist artist = Artist.getInfo(artistOrMbid, LASTFM_API_KEY);
return artist.getImageURL(ImageSize.EXTRALARGE);
}
Getting all the artists that are on my phone isn't a problem, you just reference MediaStore. You would do something like this:
private void getArtists() {
String[] projection = new String[] {
MediaStore.Audio.Artists._ID, MediaStore.Audio.Artists.ARTIST,
};
String sortOrder = MediaStore.Audio.Artists.DEFAULT_SORT_ORDER;
Cursor c = getActivity().getContentResolver().query(
MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, projection, null, null, sortOrder);
if (c != null) {
int count = c.getCount();
if (count > 0) {
final int ARTIST_IDX = c.getColumnIndex(MediaStore.Audio.Artists.ARTIST);
for (int i = 0; i < count; i++) {
c.moveToPosition(i);
}
}
c.close();
c = null;
}
}
The Adapter for my GridView isn't anything special, it simply extends BaseAdapter.
Note AQuery is a library I'm using that helps cache and load a Bitmap from a URL.
public class GridViewAdapter extends BaseAdapter {
private final String[] imageURLs;
private final LayoutInflater mInflater;
private final Activity mActivity;
public GridViewAdapter(String[] urls, Activity activity) {
imageURLs = urls;
mActivity = activity;
mInflater = (LayoutInflater)mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() {
return imageURLs.length;
}
#Override
public Object getItem(int position) {
return position;
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewholder = null;
// Inflate GridView items
if (convertView == null) {
convertView = mInflater.inflate(R.layout.gridview_items, null);
viewholder = new ViewHolder();
viewholder.mImage = (ImageView)convertView.findViewById(R.id.gridview_image);
convertView.setTag(viewholder);
} else {
viewholder = (ViewHolder)convertView.getTag();
}
AQuery aq = new AQuery(convertView);
aq.id(viewholder.mImage).image(imageURLs[position], false, false, 0, 0, null, 0, 0.75f);
return convertView;
}
}
class ViewHolder {
public ImageView mImage;
}
So in full, my AsyncTask is as follows:
public class LastfmArtistGetImageURL extends AsyncTask<Object, Integer, String[]> implements
Constants {
private static final String tag = LastfmArtistGetImageURL.class.getSimpleName();
private GridViewAdapter mGridAdapter;
// Test
private final String[] imageIds = {
"http://userserve-ak.last.fm/serve/252/71875544.png",
"http://userserve-ak.last.fm/serve/252/6258507.jpg",
"http://userserve-ak.last.fm/serve/252/51274303.png",
"http://userserve-ak.last.fm/serve/252/58672183.png",
"http://userserve-ak.last.fm/serve/252/72029714.png",
"http://userserve-ak.last.fm/serve/252/17666215.jpg",
"http://userserve-ak.last.fm/serve/252/63247381.png",
"http://userserve-ak.last.fm/serve/252/33665463.jpg"
};
private final String artistOrMbid;
private final GridView mGridView;
private final Activity mActivity;
public LastfmArtistGetImageURL(String name, GridView gv, Activity activity) {
artistOrMbid = name;
mGridView = gv;
mActivity = activity;
}
#Override
protected String[] doInBackground(Object... arg0) {
Artist artist = Artist.getInfo(artistOrMbid, LASTFM_API_KEY);
Collection<String> col = new ArrayList<String>();
col.add(artist.getImageURL(ImageSize.EXTRALARGE));
return col.toArray(new String[0]);
}
#Override
protected void onPostExecute(String[] result) {
if (result != null)
mGridAdapter = new GridViewAdapter(imageIds, mActivity);
mGridView.setAdapter(mGridAdapter);
super.onPostExecute(result);
}
}
When I call my AsyncTask, I call it in my getArtists() method like this:
new LastfmArtistGetImageURL(c.getString(ARTIST_IDX), mGridView, getActivity())
.execute();
Problem
When I call this, all of the artists images download, but they download one after the other at position 0 of my GridViewAdapter. In other words, one image loads, then next, and so on all in the first position when I need them to be placed into each available position in the GridView. When I return my test String[] in my AsyncTask everything works like it should. All of the images are placed in order in each available space in the GridView.
Question
My question is, how do I return each artist image I download into my GridView correctly and why are the images currently only being loaded at the first position in my GridViewAdapter?
Edit - Shubhayu's answer
I moved setting my GridViewAdapter into my getArtists() method like so. This results in all the images being downloaded (As says LogCat), but only the last one being set in my GridView.
String[] test = new LastfmArtistGetImageURL(c.getString(ARTIST_IDX),
mGridView, getActivity()).execute().get();
mGridAdapter = new GridViewAdapter(test, getActivity());
mGridView.setAdapter(mGridAdapter);
smoak's answer
This results in only the last artist image (by the default order) being downloaded and applied in my GridView.
String[] test = {c.getString(ARTIST_IDX)};
new LastfmArtistGetImageURL(test, mGridView, getActivity()).execute();
Your AsyncTask looks like you are executing it each time for each Artist. Thus, your AsyncTask returns only one Artist's image and your GridView gets that Artists image, then you run the AsyncTask for the next Artist, GridView gets updated with new image and so on. What you need to do is modify your AsyncTask to take a String array of Artist names and loop over them in the doInBackground to get their image's.
// ... SNIPPED
public LastfmArtistGetImageURL(String[] names, GridView gv, Activity activity) {
artistsOrMbids = names;
mGridView = gv;
mActivity = activity;
}
#Override
protected String[] doInBackground(Object... arg0) {
Collection<String> col = new ArrayList<String>();
for (String nameOrMbid : this.artistsOrMbids) {
Artist artist = Artist.getInfo(artistOrMbid, LASTFM_API_KEY);
col.add(artist.getImageURL(ImageSize.EXTRALARGE));
}
return col.toArray(new String[0]);
}
// .... SNIPPED
And pass in all the artist names:
String[] artists = { "The Black Keys", "Rush", "The Allman Brothers" };
new LastfmArtistGetImageURL(artists, mGridView, getActivity()).execute();
here's what is happening, when you pass the test string it has a list of images and hence the gridview shows them properly. but when you use it to download an image for each artist, things go wrong.
Every time you call
new LastfmArtistGetImageURL(c.getString(ARTIST_IDX), mGridView, getActivity()).execute();
it runs the doInBackground(), completes it and then immediately calls the onPostExecute() where it creates a new adapter and passes your result which basically contains a single image of the single call.
So what u need to do is in your asynctask download all the images and then create a single adapter and pass all the images to it. That is not happening currently.
EDIT
If you see the AsyncTask, you will realize that everytime you call it, the string array returns only one image. So instead of returning a string array, return a string.
Next, I would suggest you use an ArrayList in your Adapter instead of a String array.
In your getArtists(), create an ArrayList and everytime you call
new LastfmArtistGetImageURL(test, mGridView, getActivity()).execute();
add the result to your ArrayList. Once you have looped through all the artists, your ArrayList will contain all the images.
Now set it to the Adapter. (You would have t change the adapter a bit if you change it from string to arraylist.)
I am adding a map to a list and then refreshing the array adapter. This was working perfectly earlier, but now that I am using an addItem() method from two different methods, it throws a NullPointer. I hope my code will clear up what I am saying:
SimpleAdapter adapter;
List<HashMap<String, String>> painItems = new ArrayList<HashMap<String, String>>();
ListView listthings;
int[] to;
String[] from;
String painLevelString, timeOfPainString, textTreatmentString,
painLocation, row1, row2, name;
OnCreate(){
if(getIntent().getStringExtra("newPainLevel")!= null){
createNewEditedEntry();
}
adapter = new SimpleAdapter(this, painItems, R.layout.mylistlayout,
from, to);
listthings.setAdapter(adapter);
}
private void createNewEditedEntry() {
String newPainLevel = this.getIntent().getStringExtra("newPainLevel");
String newPainTime =this.getIntent().getStringExtra("newPainTime");
String newTreatment =this.getIntent().getStringExtra("newTreatment");
painLevelString = newPainLevel;
timeOfPainString = newPainTime;
textTreatmentString = newTreatment;
row1 = "sample1";
row2 = "sample2";
addItem();
//painItems.remove(getIntent().getStringExtra("position"));
//adapter.notifyDataSetChanged();
}
#Override
// on the activityresult,get the string extra, then add the item to the list
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == 1) {
row1 = data.getStringExtra("com.painLogger.row1");
row2 = data.getStringExtra("com.painLogger.row2");
painLevelString = data.getStringExtra("com.painLogger.painLevel");
painLocation = data.getStringExtra("painLocation");
timeOfPainString = data.getStringExtra("com.painLogger.painTime");
textTreatmentString = data
.getStringExtra("com.painLogger.treatment");
addItem();
}
}
// to add the item, put it in the map, and add the map into the list
private void addItem() {
HashMap<String, String> map = new HashMap<String, String>();
map.put("row_1", row1);
map.put("row_2", row2);
map.put("row_3", painLevelString);
map.put("row_4", painLocation);
map.put("row_5", timeOfPainString);
map.put("row_6",textTreatmentString);
painItems.add(map);
adapter.notifyDataSetChanged(); //Null Pointer **HERE**
}
Just to be clear, createNewEditedEntry() and onActivityResult() are never called at the same time and never clash. They are two completely different occurrences. It was working with just the OnActivityResult, but now when I use CreateNewEditedEntry(), it has stopped working. I have also checked and made sure that none of the Strings that I fetch from my intent are null.
You need to initialize adapter before you call createNewEditedEntry in onCreate:
OnCreate(){
adapter = new SimpleAdapter(this, painItems, R.layout.mylistlayout, from, to);
if(getIntent().getStringExtra("newPainLevel")!= null){
createNewEditedEntry();
}
// ...
}
Otherwise, adapter will be null in addItem called from there (last line of createNewEditedEntry).
You should also be aware that your call to addItem can fail if your activity was destroyed by Android while you were in the sub-activity. Have a read of my answer to this question to understand what can happen when resources get low.