this is my first question on this site and I am also new to Android. I am creating an application using an online API. I am working with this API in XML and parsing the responses into ListViews. I have reached a point where I would like to select an item from a ListView in one activity and send that information to the next activity along with another ListView containing more information for the selected item. As an example, one activity has a list of bands. Clicking on the band name will bring up the band name and a list of tour dates on the next activity. According to my API, the band's ID number is needed to access the bands tour information I am trying to pass the ID number as a search parameter but cannot get this to work. I did manage to find a decent tutorial on androidhive.info but cannot seem to be able to apply these techniques. The doInBackground() method is where my app is hanging up.
ListView lv = getListView();
lv.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
// getting values from selected ListItem
String displayName = ((TextView) view.findViewById(R.id.tvDisplayName)).getText().toString();
String onTourUntil = ((TextView) view.findViewById(R.id.tvOnTourUntil)).getText().toString();
String identification = ((TextView) view.findViewById(R.id.tvId)).getText().toString();
// Starting new intent
Intent in = new Intent(getApplicationContext(), SingleArtistActivity.class);
in.putExtra(KEY_DISPLAY_NAME, displayName);
in.putExtra(KEY_ID, identification);
in.putExtra(KEY_ON_TOUR_UNTIL, onTourUntil);
new AsyncDownload().execute(identification);
startActivity(in);
}
});
}
private class AsyncDownload extends AsyncTask<String, String, String> {
ProgressDialog pDialog;
#Override
protected void onPreExecute() {
super.onPreExecute();
pDialog = new ProgressDialog(ArtistsSearchActivity.this);
pDialog.setMessage("Please Wait...");
pDialog.setCancelable(false);
pDialog.show();
}
#Override
protected String doInBackground(String... params) {
Log.v(TAG, "query is" + params[0]);
String result = new ArtistCalendarHelper().getXml(params[0]);
return result;
}
My AsyncDownload class is called in my onClickListener. The class calls a helper that contains the URL and API key.
public class ArtistCalendarHelper {
private static final String TAG = "ArtistCalendarHelper";
private static final String SONGKICK_URL = "http://api.songkick.com/api/3.0/artists/";
private static final String API_KEY = "yIekMi1hQzcFheKc";
public String getXml(String identification) {
HttpClient httpclient = new DefaultHttpClient();
String getParameters = "";
try {
getParameters = URLEncoder.encode(identification, "UTF-8")
+ "/calendar.xml?apikey=" + URLEncoder.encode(API_KEY, "UTF-8");
} catch (UnsupportedEncodingException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
String url = SONGKICK_URL + getParameters;
// Prepare a request object
HttpGet httpget = new HttpGet(url);
// Execute the request
HttpResponse response;
These methods worked for obtaining the bands name in an initial search. How could this be changed to perform a search for tour information with an argument retrieved from a ListView?
Is this different from getting a search query from an EditText field? I didn't think there would be much of a difference. I have tried to include the affected code. I am not sure how much code I should provide.
you can try this:
step 1. Intent in = new Intent(getApplicationContext(), SingleArtistActivity.class);
in.putExtra(KEY_DISPLAY_NAME, displayName);
in.putExtra(KEY_ID, identification);
in.putExtra(KEY_ON_TOUR_UNTIL, onTourUntil);
// new AsyncDownload().execute(identification); dont start the net request here
startActivity(in);
step 2.
put on request url method in second Activity's on create() method
public class SingleArtistActivity{
onCrete(){
...
new AsyncDownload().execute(identification);
}
in SingleArtistActivity, instance a empty Adapter, call method : listView.setAdapter(adapter), then the view will show but the listview is empty ,no data will display, but the url request is working in background.
}
step 3:
when the AsyncTask return the data , put the data into a List data;
then call the adapter.notifyDataSetChanged()
the listview will reload the apapter again , the data will be display in the listview
I think there is a mistake here:
// Starting new intent
Intent in = new Intent(getApplicationContext(), SingleArtistActivity.class);
in.putExtra(KEY_DISPLAY_NAME, displayName);
in.putExtra(KEY_ID, identification);
in.putExtra(KEY_ON_TOUR_UNTIL, onTourUntil);
new AsyncDownload().execute(identification);
startActivity(in);
I guess you want to load the data with AsyncDownload then show them in SingleArtistActivity.class.
So you should put these code in SingleArtistActivity.java.
You should call new AsyncDownload().execute(identification);in onCreate() or onResume() of SingleArtistActivity.java ;
and setAdapter in onPostExecute of your AsyncDownload class.
like these:
#Override
protected void onPostExecute() {
if(pDialog != null && pDialog.showing()) {
pDialog.dismiss();
}
YourAdapter adapter = new Adapter(yourdata);
setListAdapter(adapter);
}
Related
I have an activity that in onCreate() does the following:
Creates an empty ArrayList
Creates a new ArrayAdapter associated with the above ArrayList
Sets ListView to use the above ArrayAdapter
Uses Volley to send a GET request to my API to fetch some JSON data to load into the ListView
Once the data is fetched I add it to my ArrayList and the ListView is populated as expected
My problem is that when the activity is restarted (i.e. the screen is rotated via the emulator or the activity is restarted through Android Studio) the ListView no longer populates.
I am not saving any state. I expect the activity to return to its initial default state so I don't think onSaveInstanceState() is the answer.
I've verified that the data is returned successfully from the API and that the adapter's hashcode is the same before and after the volley request and that it equals the ListView's set adapter. I've also verified that onDestroy() and then onCreate() are called when the activity is restarted so I know it is going through a full life cycle.
If I rotate the screen programmatically with setRequestedOrientation() I don't experience this issue. If I add items to my ArrayList outside of the GET request callback, I don't experience this issue.
Here is my activity onCreate()
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
//The data to be displayed
descriptions = new ArrayList<>();
listView = (ListView)this.findViewById(R.id.myListView);
//Link 'descriptions' to the adapter
adapter = new ArrayAdapter<>(this, R.layout.list_json_text_view, descriptions);
listView.setAdapter(adapter);
this.addTextFilter();
this.addListViewClickListener();
//See my ApiGetRequest class below
request = new ApiGetRequest();
request.send(this.getContext(), getDataUrl(), this, "", REQUEST_TYPES.TEXT);
}
And my activity GET request callback
public void onSuccess(DescriptiveJSONArray items, REQUEST_TYPES type) {
descriptions.clear();
try {
for (int i = 0; i < items.length(); ++i) {
JSONObject obj = items.getJSONObject(i);
String desc = obj.optString("name", "") + " " + obj.optString("description", "");
//TODO: Remove debug code
System.out.println("Adding: "+desc);
descriptions.add(desc);
}
}
catch(JSONException e) {
e.printStackTrace();
//getJSONObject failed
}
}
And my ApiGetRequest methods
//My activity implements ApiGetCallback
public void send(Context context, String url, ApiGetCallback callback, String tag, REQUEST_TYPES type) {
StringRequest stringRequest = getStringRequest(url, callback, tag, type);
//Singleton wrapper for RequestQueue
AppRequestQueue queue = AppRequestQueue.getInstance(context);
queue.add(stringRequest);
}
//Inner class inside ApiGetCallback
class SuccessListener implements Response.Listener<String> {
ApiGetCallback callback;
REQUEST_TYPES type;
public SuccessListener(ApiGetCallback callback, REQUEST_TYPES type) {
this.callback = callback;
this.type = type;
}
#Override
public void onResponse(String response) {
try {
DescriptiveJSONArray jsonResp = new DescriptiveJSONArray(response);
callback.onSuccess(jsonResp, type);
}
catch(JSONException e) {
callback.onJsonException(e);
}
}
}
Any ideas what is happening?. I'm testing on Marshmallow and Nougat
You are missing a call to notifyDataSetChanged, after the onSuccess function is done.
you may need to override onStart and do update anything in it
adapter = new ArrayAdapter<>(this, R.layout.list_json_text_view, descriptions);
listView.setAdapter(adapter);
//See my ApiGetRequest class below
request = new ApiGetRequest();
request.send(this.getContext(), getDataUrl(), this, "", REQUEST_TYPES.TEXT);
use this part of code in onResume method.
I'm doing a lost property office application. It consists of four tabs, each tab must display announcement feed uploaded by users. By clicking on a particular item opens a new activity to derive more information.(Well, sort of news feed). I take the data from the API site in the form of JSON.
I did four tabs using ViewPager, Fragment. image my fragments
I have tried to place every Fragment listview and display their data with JSON with AsynTask. And caused AsyncTask for each fragment also called the same JSON in the first fragment. May be it is not right.
So, please tell me the correct move.
What used to for display data into listview from json
How to display information on a new activity that opens when you press the listview item?
Send to fragment over the intent or download a JSON again?
How to realize adding ads by user?
If possible, write the steps. Thank you very much, I will be glad of any help.
P.S Sorry for bad English
First of all you can use a simple list adapter to be able to put JSON data into a listview, you can see a tutorial of this here.
Second to be able to see details about a item in a list view you can use a onListItemClick, with extending ListActivity. An example of doing this would be:
MyListActivity.java
public class MyListActivity extends ListActivity {
// . . .
protected void onListItemClick(ListView l, View v, int position, long id) {
//handle the list click here
intent = new Intent(this, ListDetails.class);
//convert the position int into a string to pass to the intent
value = String.valueOf(position);
//put the value into the intent
intent.putExtra("key", value);
//start the activity here
MyListActivity.this.startActivity(intent);
}
}
ListDetails.java
public class ListDetails extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//gets the value sent with the intent
Intent intent = getIntent();
final String helpView = intent.getStringExtra("key");
// . . .
}
This is assuming that you are using a ListActivity to make the list and it really depends on what sort of information you want to display, if you want to display all the data, you could pass the JSON data as a string through the intent, then get it like in the example above.
Thirdly if you want to keep the JSON data you can just pass it along with the intent and then when the user presses back, just pass it back to the original activity once again through the intent. If you want to re-download the JSON data, you can, but to save with users mobile internet connection going over (especially for large JSON files repeatedly), in would not recommend using this option. If you want to download the JSON data again, you will need to use a AsyncTask to download the data, so not to disrupt the user interface and that it will raise a error when you try to do this (believe me, I have pulled my hair out over this). A example of a AsyncTask is (from the android project I am working on, that uses google maps to calculate a route):
private class ReadTask extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String... url) {
String data = "";
try {
HttpConnection http = new HttpConnection();
data = http.readUrl(url[0]);
} catch (Exception e) {
Log.d("Background Task", e.toString());
}
return data;
}
#Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
Log.i("Log","Parser Task Started");
Log.i("Log",result);
new ParserTask().execute(result);
}
}
private class ParserTask extends
AsyncTask<String, Integer, List<List<HashMap<String, String>>>> {
#Override
protected List<List<HashMap<String, String>>> doInBackground(
String... jsonData) {
JSONObject jObject;
List<List<HashMap<String, String>>> routes = null;
try {
jObject = new JSONObject(jsonData[0]);
PathJSONParser parser = new PathJSONParser();
routes = parser.parse(jObject);
} catch (Exception e) {
e.printStackTrace();
}
return routes;
}
#Override
protected void onPostExecute(List<List<HashMap<String, String>>> routes) {
//process the JSON data here
}
}
You would place this inside a activity, adding in the necessary processing for the JSON data inside the onPostExecute in ParserTask. To download the JSON data, you would call (replacing the url with your url for getting the JSON data):
String url = "http://www.example.com"
ReadTask downloadTask = new ReadTask();
downloadTask.execute(url);
EDIT:
Lastly to show the latest or most important/relevant lost property adverts, you can once again use JSON to get this from your backend website. In PHP you can customise server responses, sending back JSON responses depending on values in the GET (?thisIsTheGet=blahblahblah). To add advertisements you can create a seperate activity, with EditTexts etc, to create a form, then send the data through a GET request to your backend server (where the JSON is held), so the server can update the advertisement list. Once again you can use a AsyncTask to send the GET request to the server, perhaps using methods above to send back from the server the success of the adding of the advertisement. A example of what your form could look like (XML) would be:
activity_add_advertisement.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
tools:context="your.activity.class.root.activityAddAdvertisement"
tools:showIn="#layout/activity_add_advertisement"
android:id="#+id/LLMainContent"
android:orientation="vertical"
>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/edittext1"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/edittext2"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/submitButton"/>
</LinearLayout>
Then set a on click listener for the button being pressed inside the activity:
activityAddAdvertisement.java
public class activityAddAdvertisement {
// . . .
Button submitButton;
#Override
protected void onCreate(Bundle savedInstanceState) {
// . . .
submitButton = (Button) findViewById(R.id.submitButton);
submitButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//call the methods from above to send a request to a url, with the contents of the edit texts in the GET request
}
}
}
You can add more EditTexts and other elements and inside the contents inside the GET request. The server will then edit the JSON to include the new advertisements. The PHP won't be too hard, if you need some tutorials on PHP, W3Schools has good tutorials and examples for you to use.
Further Reading:
Android Intents
I have a list that gets loaded from the server. Below is the task that does this:
class LoadActivities extends AsyncTask <String, String, String> {
protected String doInBackground(String ... args) {
final RestAdapter restAdapter = new RestAdapter.Builder().setServer("http://10.0.2.2:8080").build();
final MyService apiManager = restAdapter.create(MyService.class);
final Activity activity = apiManager.getActivity("some user", act_id);
//tasks in activity
for (Tasks t : activity.getTasks()) {
String r_id = t.getId()+"";
String name = t.getName();
HashMap<String, String> map = new HashMap<String, String>();
map.put("activity_id", act_id);
map.put("t_id", t_id);
map.put("t_name", name);
tasksList.add(map);
}
return null;
}
protected void onPostExecute(String file_url) {
runOnUiThread(new Runnable() {
public void run() {
ListAdapter adapter = new SimpleAdapter(
TaskActivity.this, tasksList,
R.layout.list_item_rec, new String[] { "act_id", "t_id", "t_name"}, new int[] {
R.id.act_id, R.id.task_id,R.id.task_name });
setListAdapter(adapter);
}
});
}
}
All of this is working fine. However, on another screen I am adding an item on the server and after that I come back to this screen to show the list again. At the time of coming back I want to refresh the list so that it reflects the newly added item.
Questions
Should I refresh the entire list? I have tried doing this by calling the above class again. like so:
public boolean onOptionsItemSelected(MenuItem menuItem) {
if (menuItem.getTitle().toString().equalsIgnoreCase("save")) {
new CreateTask(this,activityName.getText().toString(), actId).execute();
Intent returnIntent = new Intent();
setResult(RESULT_OK, returnIntent);
finish();
return true;
}
return true;
}
...back on this screen
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 1) {
if (resultCode == RESULT_OK) {
Log.d("This is result", result);
new LoadActivities().execute();
}
}
}
problem with this is that It is repopulating the list. Meaning I have duplicates of every activity. How can I resolve this?
OR Is there a way so that I won't have to reload the entire list but rather just add an item(s) to the existing list?
First,in the method "onPostExecute", you don't need to call "runOnUiThread", because the "onPostExecute" was run in UI thread.
Second, if you want to refresh the ListView in front of the page, you can use "onActivityResult" in the front page, but if your server data was updated, just get data from server again and update your data set(list), then call adapter.notifyDataSetChanged().
Wish to help you!
You should us and ArrayAdapter and let it handle the list.
Create and set the ArrayAdapter right away, then add items to it as necessary. You'll have to override getView in the adapter, but for a simple view that won't be complex code.
The general structure will look like:
onCreate(...) {
// It's okay if the adapter is empty when you attach it to the ListView
setListAdapter(new ArrayAdapter<ListItemType>(...));
}
onPostExecute(...) {
// Once you've retrieved the list of items from the server, add them to
// the adapter
ArrayAdapter adapter = (ArrayAdapter) getListAdapter();
adapter.add([items retrieved from server]);
}
onActivityResult(..., Intent data) {
// Add the newly added item, either pass it back directly, or get the new
// list from the server and compare to see which item needs adding.
// For simplicity, we'll assume it was passed back by the activity
ListItemType newlyAddedItem = (ListItemType) data.getParcelableExtra("key");
ArrayAdapter adapter = (ArrayAdapter) getListAdapter();
adapter.add(newlyAddedItem);
}
I have a database in a server and from a Tablet I take some values from one table in the database. I load this information correctly into a list but I would like to know why when there is a change, nothing happens even if I use notifyDataSetChanged();. I must say that for loading the loading data y use the AsyncTaskClass
So, my problem is that I don't know if use the notifyDataSetChanged(); method correctly ,because if there's is a change I would like to refresh the image. Here is some part of the code of the class:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.all_candidatos);
candidatosList = new ArrayList<HashMap<String, String>>();
new CargarCandidatos().execute();
}
// public void timer(){
// new CountDownTimer(tiempo, 100) {
//
// public void onTick(long millisUntilFinished) {
//
// }
//
// public void onFinish() {
// // new CargarCandidatos().execute();
//
// }
// }.start();}
/**
* Background Async Task to Load all product by making HTTP Request
* */
class CargarCandidatos extends AsyncTask<String, String, String> {
/**
* Before starting background thread Show Progress Dialog
* */
#Override
protected void onPreExecute() {
super.onPreExecute();
pDialog = new ProgressDialog(Monitorizacion.this);
pDialog.setMessage("Loading ...");
pDialog.setIndeterminate(false);
pDialog.setCancelable(false);
pDialog.show();
}
/**
* getting All products from url
* */
protected String doInBackground(String... args) {
List<NameValuePair> params = new ArrayList<NameValuePair>();
JSONObject json = jParser.makeHttpRequest(url_candidatos, "GET", params);
Log.d("Candidatos: ", json.toString());
try {
int success = json.getInt(TAG_SUCCESS);
if (success == 1) {
candidatos = json.getJSONArray(TAG_CANDIDATOS);
for (int i = 0; i < candidatos.length(); i++) {
JSONObject c = candidatos.getJSONObject(i);
// Storing each json item in variable
String nserie = c.getString(TAG_NSERIE);
String dni = c.getString(TAG_DNI);
String nombre = c.getString(TAG_NOMBRE);
String test = c.getString(TAG_TEST);
String pregunta = c.getString(TAG_PREGUNTA);
String bateria = c.getString(TAG_BATERIA);
// creating new HashMap
HashMap<String, String> map = new HashMap<String, String>();
// adding each child node to HashMap key => value
map.put(TAG_NSERIE, nserie);
map.put(TAG_DNI, dni);
map.put(TAG_NOMBRE, nombre);
map.put(TAG_TEST, test);
map.put(TAG_PREGUNTA, pregunta);
map.put(TAG_BATERIA, bateria);
// adding HashList to ArrayList
candidatosList.add(map);
}
}
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
/**
* After completing background task Dismiss the progress dialog
* **/
protected void onPostExecute(String file_url) {
pDialog.dismiss();
runOnUiThread(new Runnable() {
public void run() {
/**
* Updating parsed JSON data into ListView
* */
adapter = new SimpleAdapter(
Monitorizacion.this, candidatosList,
R.layout.list_item, new String[] { TAG_NSERIE,
TAG_DNI, TAG_NOMBRE, TAG_TEST, TAG_PREGUNTA, TAG_BATERIA},
new int[] { R.id.id, R.id.dni, R.id.nombre, R.id.test, R.id.pregunta, R.id.bateria});
setListAdapter(adapter);
adapter.notifyDataSetChanged();
// timer();
}
});
}
}
}
One of the main reasons notifyDataSetChanged() won't work for you - is,
Your adapter loses reference to your list.
When you first initialize the Adapter it takes a reference of your arrayList and passes it to its superclass. But if you reinitialize your existing arrayList it loses the reference, and hence, the communication channel with Adapter.
When creating and adding a new list to the Adapter. Always follow these guidelines:
Initialise the arrayList while declaring it globally.
Add the List to the adapter directly without checking for null and empty values. Set the adapter to the list directly (don't check for any condition). Adapter guarantees you that wherever you make changes to the data of the arrayList it will take care of it, but never
lose the reference.
Always modify the data in the arrayList itself (if your data is completely new then you can call adapter.clear() and arrayList.clear() before actually adding data to the list) but don't set the adapter i.e If the new data is populated in the arrayList than just adapter.notifyDataSetChanged()
Stay true to the Documentation.
The thing you need to edit is put your
runOnUiThread(new Runnable() {
public void run() {
/**
* Updating parsed JSON data into ListView
* */
adapter = new SimpleAdapter(
Monitorizacion.this, candidatosList,
R.layout.list_item, new String[] { TAG_NSERIE,
TAG_DNI, TAG_NOMBRE, TAG_TEST, TAG_PREGUNTA, TAG_BATERIA},
new int[] { R.id.id, R.id.dni, R.id.nombre, R.id.test, R.id.pregunta, R.id.bateria});
setListAdapter(adapter);
adapter.notifyDataSetChanged();
// timer();
}
});
into the OnCreate(). and return the list candidatosList from Asynctask. than set timer for updating candidatosList list.
It might be worth checking if you have an empty override for registerDataSetObserver(). Android Studio added one for me without implementing the call to super. Adding it in as follows was enough to get my listView working again:
#Override
public void registerDataSetObserver(DataSetObserver observer) {
super.registerDataSetObserver(observer);
}
An adapter define the comportement of the layout !
-> setListAdapter() : Define the adapter for a ListView/GridView/Gallery...
but you need to specify the data !
I recommend to you, to initialize 'setListAdapter' in the 'onCreate' or in the constructor.
After you set the data into the adapter (exemple : adapter.setItem(yourData))
And NOW ! You should to call notifyDataSetChanged !
Because you have changed the data but the view isn't refresh and notifydatasetchanged() reload the content of the view (ListView/GridView/Gallery...)
For a good practice and understand correctly I recommend to you to use a 'custom adapter' using 'baseAdapter'
Read and do this tutorial (I haver learn with this): http://www.androidhive.info/2012/02/android-custom-listview-with-image-and-text/
Read the documentation : http://developer.android.com/reference/android/widget/BaseAdapter.html
The update function should be called from UI thread.
My answer is actually similar to #user1621629's answer with that difference that I am using rxJava, so here's working code that solve this problem for me:
this.subscriber = myAdapter.getSubscriber(); // keep for unsubscribe in destroy
dataSource.subscribeOn(Schedulers.computation()).observeOn(AndroidSchedulers.mainThread()).subscribe(this.subscriber);
So I set all execution in order to get data for the list to computation thread, but showing result in UI thread.
Here's how I create subscriber for this:
public class MyListAdapter extends RecyclerView.Adapter<LocationListAdapter.ViewHolder> {
private List<ListItem> mDataset = new ArrayList<>();
public Subscriber<ListItem[]> getSubscriber() {
return Subscribers.create(new Action1<ListItem[]>() {
#Override
public void call(ListItem[] listItems) {
mDataset.clear();
mDataset.addAll(Arrays.asList(listItems));
notifyDataSetChanged();
}
});
}
......
As Hissain describes above,
you need to maintain a reference to the list
Here's how I got it to work:
Let the list being sent to the adapter be set as an instance member in the activity
In the logic that performs a change to the data, make sure it updates the same list instance that the activity passed to the adapter
Then calling .notifyDataSetChanged(); worked
Remember that listView position starts at 1, so you will have to do (listViewPosition - 1) for your your java.util.List
I dont have much reputation to comment on Mr. Hissain answer.It is correct but I want to mention one more thing that reference to the list should not change. If data source underlying is changing, dont change the reference to new list. Actions only need to be done on the same list object. To do the same,clear the list using clear() and then add data to the same list using add() or addALL() and then call notifyDataSetChanged(). eg.
On first initialization of the list
list = dataSource.getList();
then one can add and remove the content from the list and call notifyDataSetChanged() it works fine but if in the code, one tries to change the reference to the other object. Like
list = dataSource.getList();
where getList() returns the new list everytime, hence the reference changes to some other list object and calling notifyDataSetChnaged does not have impact on the list.But if getList() returns the same list object, it works fine.
If everything you set fine and still not working then your list...
Is it Mutablekind of the List or not...!
private val demoList: MutableList<AnyClass> = mutableListOf()
once you define your list like above mutable manner then you can get the method
.add
.addAll
.remove
etc...
else if you have created normal list then that will not work as notifyDataSetChanged
I'm fetching all songs on the device in an AsyncTask. When I Log all songs in the songs List variable in the AsyncTask they all show up. But when I Log them in the constructor of the ArrayAdapter, it's always empty. How is this even possible?
The AsyncTask:
private class SongFinder extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... params) {
ContentResolver resolver = activity.getContentResolver();
Uri uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
Cursor cursor = resolver.query(uri, projection, selection, null, null);
Log.i(TAG, "Fetching next batch of songs");
if (cursor == null) {
Toast.makeText(activity, "Failed to discover songs",
Toast.LENGTH_LONG).show();
} else if (!cursor.moveToFirst()) {
Toast.makeText(activity, "No media on device",
Toast.LENGTH_LONG).show();
} else {
cursor.moveToFirst();
while (cursor.moveToNext()) {
String id = cursor.getString(0);
String title = cursor.getString(2);
String artist = cursor.getString(1);
String data = cursor.getString(3);
String albumName = cursor.getString(8);
Long albumId = cursor.getLong(6);
String albumKey = cursor.getString(7);
songs.add(new Song(id, title, artist, data, albumName,
albumKey, albumId));
}
}
cursor.close();
// HERE IT'S FULL WITH THE SONGS I WANT
Log.d(TAG, songs.toString());
return null;
}
#Override
protected void onPostExecute(Void result) {
if (songs.size() > 0) {
//HERE IT IS ALWAYS EMPTY
Log.d(TAG, songs.toString());
SongArrayAdapter adapter = new SongArrayAdapter(activity, songs);
EndlessSongAdapter songAdapter = new EndlessSongAdapter(activity,
adapter, R.layout.arrayadapter_songs);
songsList.setAdapter(songAdapter);
songsList.setTextFilterEnabled(true);
songsList.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> root, View view,
int position, long when) {
Intent newSong = new Intent(MusicService.ACTION_NEW);
newSong.putExtra("song", songs.get(position));
Log.i(TAG, "sending broadcast");
bManager.sendBroadcast(newSong);
// Reset the play/pause button.
activity.resetButton();
Intent addSong = new Intent(
"com.xx.xx.ADDSONG");
addSong.putExtra("song", songs.get(position));
bManager.sendBroadcast(addSong);
activity.fragmentChangeNewSong();
}
});
}
}
}
EDIT
Calling the AsyncTask
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
activity = (MusicActivity) getActivity();
bManager = LocalBroadcastManager.getInstance(activity);
ListView songsList = getListViewWithSongs(activity);
return songsList;
}
public ListView getListViewWithSongs(final MusicActivity activity) {
songsList = new ListView(activity);
new SongFinder().execute();
return songsList;
}
So the private List<Song> songs variable is filled in the doInBackground() method of the AsyncTask, but it's empty in the onPostExecute().
So, the critical thing I'm seeing you're missing (without having seen the rest of the application), is a call to:
songAdapter.notifyDataSetChanged();
This assumes, of course that your adapter extends BaseAdapter. Without that call, the adapter has no way to know that the list has been updated. So, it's a good idea to always call 'notifyDataSetChanged()' whenever you update the list.
You can also use a CursorAdapter if you like, and the view should call the cursor to get data automatically. Your mileage will vary with that, however, because if the queries to the cursor are too slow, the user interface will slow down.
Here are a couple of other thoughts:
in onPostExecute you are creating a new adapter every time. This isn't necessary. This works, but is not necessary. You can simply clear out the private list songs and then call notifyDataSetChanged on the adapter.
You're setting the on click listener every time when you call songsList.setOnItemClickListener(new OnItemClickListener(). This also is un-necessary. You can set the click listener in onCreate or onResume.
These are small details, but they add up to important things. Every time you create a new object and assign it to a variable/member, the object that it replaces is marked for garbage collection unless it's being referenced somewhere else in your program. If you do this too much, the garbage collector will start to work overtime and slow down your application - especially if you're running your SongFinder task a lot.
Hope that helps.
Well I actually figured it out. Another Fragment was being loaded simultaneously that also uses the EndlessSongAdapter and that was crashing it. When I commented it all, all the other code were able to run and finish, without crashing.... Really stupid mistake. The LogCat was actually not showing in the Stack trace from which fragment class it was being called, so I never figured it out
So if you run into the same problem, check if your ViewPager is loading multiple Fragments and if they can possible create a "collision"