Let me start off by saying this is NOT a question about scrolling ListViews. I do not want to know how to tell when a user scrolls to the bottom of a list, so please do not give me answers for that question, or mark this as a duplicate.
I am using a class that extends AsyncTaskLoader to populate a ListView with data from a web service.
Initially, I load 50 items and everything is working great. I need to know how to tell the Loader to load the next 50 items incrementally. I understand WHERE to do this in the ListView code, but I can't figure out the best way to tell the Loader that I want to load more data without resetting it and loading everything again.
Again, to clarify, the issue I'm trying to solve here is just notifying the loader that more data needs to be loaded. It already knows how to load more data when loadInBackground() is called a second time, and the ListView already knows where/when to notify the Loader, the question is just how.
Some of the relevant code:
#Override
public void onActivityCreated(Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
m_adapter = new SearchAdapter(getActivity());
setListAdapter(m_adapter);
// if the loader doesn't already exist, one will be created
// otherwise the existing loader is reused so we don't have
// to worry about orientation and other configuration changes
getLoaderManager().initLoader(SEARCH_LOADER_ID, null, this);
}
#Override
public Loader<List<Result>> onCreateLoader(int id, Bundle args)
{
String query = args != null ? args.getString(QUERY_KEY) : "";
return new SearchLoader(getActivity(), query);
}
private class SearchAdapter extends ArrayAdapter<Result>
{
// ...
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
// ...
if (position == getCount() - 2)
// TODO: Need to notify Loader here
// ...
}
}
private static class SearchLoader extends OurAsyncTaskLoader<List<Result>>
{
public SearchLoader(Context context, String query)
{
super(context);
m_query = query;
m_data = Lists.newArrayList();
m_loadedAllResults = false;
}
#Override
public List<Result> loadInBackground()
{
if (m_loadedAllResults)
return m_data;
// the Loader implementation does a == check rather than a .equals() check
// on the data, so we need this to be a new List so that it will know we have
// new data
m_data = Lists.newArrayList(m_data);
MyWebService service = new MyWebService();
List<Result> results = service.getResults(m_query, m_data.size(), COUNT);
service.close();
if (results == null)
return null;
if (results.size() < COUNT)
m_loadedAllResults = true;
for (Result result : results)
m_data.add(result)
return m_data;
}
private static final int COUNT = 50;
private final String m_query;
private boolean m_loadedAllResults;
private List<Result> m_data;
}
I figured out a way that works. In my SearchAdapter#getView() method, I have the following code:
private class SearchAdapter extends ArrayAdapter<Result>
{
// ...
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
// ...
if (position == getCount() - 2)
getLoaderManager().getLoader(SEARCH_LOADER_ID).onContentChanged();
// ...
}
}
I still would like to know if this is the "best practice" way of doing it, but it seems to solve my problem for now.
In your scenario I will recommend you to use ForceLoadContentObserver which you can bind with a URI to the ContentResolver. Like this:
class SearchLoader ....
ForceLoadContentObserver contentObserver = new ForceLoadContentObserver();
....
#Override
public void onStartLoading() {
if (cacheResult == null || takeContentChanged()) { // This will see if there's a change notification to observer and take it.
onForceLoad();
} else {
deliverResult(cacheResult);
}
}
#Override
public Result loadInBackground() {
Result result = loadResult();
// notification uri built upon Result.BASE_URI so it receives all notifications to BASE_URI.
getContext().getContentResolver().registerContentObserver(result.getNotificationUri(), true, contentObserver);
}
#Override
public void onReset() {
// ... Do your clean stuff...
getContext().getContentResolver().unregisterContentObserver(contentObserver);
}
...
}
So you can notify your change everywhere by using:
context.getContentResolver().notifyChanged(Result.BASE_URI, null);
Even when the activity holding the loader is in the background or not possible to deliverResult. And you don't need to get loaders' instances.
All the notification is around Uri of object. Uri is a powerful representation of data in Android.
But I do have my confusion as well. In this scenario, both your approach and my approach assumes a content changes means loading more data. But what if you do need to reload all the data? What kind of notification you will use?
Related
I've searched exhaustively for the right way to do this, and despite it often being said that you should not load data on the UI thread, nobody ever posts code!
So, this is what I am doing, and it's not quite working:
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/...
linearLayoutManager = new LinearLayoutManager(getActivity());
list = new ArrayList<>();
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
new Thread(new Runnable() {
#Override
public void run() {
reloadData();
}
}).start();
adapter = new MyAdapter(args, list, etc);
return super.onCreateView(inflater, container, savedInstanceState);
}
#Override
public void reloadData() {
list.clear();
list.addAll(fetchDataFromSQLDatabase(args));
}
The RecyclerView stuff is handled in the parent class' onCreateView() (binding views, attaching adapter to the recyclerview, etc).
But is this the right way to load in the data? The list always shows up blank, until I rotate the screen or do something that interacts with the list, etc.
Is there a simple way I can modify what I have above so it works? I am not looking to overhaul the whole thing; I just want it to work. I'm using GreenDao for my database access so I can't change everything around too much. I just want to reorganize the code above so it works. I'm repeating myself a lot here because every time I ask this question people ignore it. I'm not looking to drastically overhaul this. I'm not looking to drastically overhaul this. I'm not looking to drastically overhaul this. I just want it to work.
For doing db related operation you should use load manager loader call back.
public class SampleActivity extends Activity implements LoaderManager.LoaderCallbacks<D> {
public Loader<D> onCreateLoader(int id, Bundle args) { ... }
public void onLoadFinished(Loader<D> loader, D data) { ... }
public void onLoaderReset(Loader<D> loader) { ... }
}
onCreateLoader() get called when you call getLoadmanager().initLoader().
This method used to initialize the loader(cusrsorLoader). This may be any kind of db operation(query etc).
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// Create a new CursorLoader with the following query parameters.
return new CursorLoader(SampleListActivity.this, CONTENT_URI,
PROJECTION, null, null, null);
}
onLoadFinished() get called when the load manager finishes its load.
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
// A switch-case is useful when dealing with multiple Loaders/IDs
switch (loader.getId()) {
case LOADER_ID:
// The asynchronous load is complete and the data
// is now available for use. Only now can we associate
// the queried Cursor with the SimpleCursorAdapter.
// call the recycler view adapter to show the data
// pass the loader object to adapter and the set the adapter to
// recycler view
break;
}
// Your recycler view now displays the queried data.
}
I got a response back from the API like this below:
{
"transactions": [null]
}
However, when I tried to debug, List.getTransactionItems().size() is equal 1 rather than 0. I think it considers null as an item. Also, I checked few things as below but none of them work.
if (this.transactionsViewModel.getTransactionItems().size() == 0
|| this.transactionsViewModel.getTransactionItems() == null
|| this.transactionsViewModel.getTransactionItems().isEmpty()
|| this.transactionsViewModel.getTransactionItems().equals(null))
However, when I tried to call something like that below, it actually recognized that there is an null item in the list.
this.transactionsViewModel.getTransactionItems().contains(null)
Any idea in this situation?
Thanks in advance.
As far as i know, json array not supposed to be null like that. You can make "transactions": [] or "transactions": null instead. That behaviour happens because your code recognise that the array is not empty. It has 1 item with null value.
If you can not change the server response.
You can check if the list only contains null by removeAll null object from your list then check the size
(this.transactionsViewModel.getTransactionItems().removeAll(Collections.singleton(null))).size() == 0
// size == 0 your list only contains null
// size > 0 your list is not empty
How about reading the stream into a string, and doing a search and replace on the json string, and remove the null, before you parse the json? You can also extend a stream class, and do it in the stream.
This is the problem about the unsuitable response from the server. To solve your problem, you can only to make some workaround in your code.
I suggest you should keep your application as clear and origin as possible. Don't change the condition call if it is really necessary.
The following is my suggested code ( used some code from Phan Van Linh)
This class used Retrofit library as the example to make the callback abstract
public class TestFragment extends BaseFragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
view = inflater.inflate(R.layout.testlayout, null, false);
RestA rest = UtilRetrofit.build(RestA.class);//from the restful endpoint
Call<YourObject> call = rest.getTransactions();
call.enqueue(new TransactionCallback() {
#Override
public void onResponse(YourObject bean) {
Toast.makeText(getActivity(), "Hello World", Toast.LENGTH_SHORT).show();
}
#Override
public void onFailure(Call<YourObject> call, Throwable t) {
}
});
return view;
}
public abstract class TransactionCallback implements Callback<YourObject> {
#Override
public void onResponse(Call<YourObject> call, Response<YourObject> response) {
YourObject lNewBean = response.body();
lNewBean.getTransactions().removeAll(Collections.singleton(null));// do some custom action and remove useless thing
onResponse(lNewBean);
}
public abstract void onResponse(YourObject bean);
}
/**
* Created by me on 22/8/2016.
*/
public static class YourObject {
private List<String> transactions;
public List<String> getTransactions() {
return transactions;
}
public void setTransactions(List<String> pTransactions) {
transactions = pTransactions;
}
}
}
I am using a singleton for fetching data from a web service and storing the resulting data object in an ArrayList. It looks like this:
public class DataHelper {
private static DataHelper instance = null;
private List<CustomClass> data = null;
protected DataHelper() {
data = new ArrayList<>();
}
public synchronized static DataHelper getInstance() {
if(instance == null) {
instance = new DataHelper();
}
return instance;
}
public void fetchData(){
BackendlessDataQuery query = new BackendlessDataQuery();
QueryOptions options = new QueryOptions();
options.setSortBy(Arrays.asList("street"));
query.setQueryOptions(options);
CustomClass.findAsync(query, new AsyncCallback<BackendlessCollection<CustomClass>>() {
#Override
public void handleResponse(BackendlessCollection<CustomClass> response) {
int size = response.getCurrentPage().size();
if (size > 0) {
addData(response.getData());
response.nextPage(this);
} else {
EventBus.getDefault().post(new FetchedDataEvent(data));
}
}
#Override
public void handleFault(BackendlessFault fault) {
EventBus.getDefault().post(new BackendlessFaultEvent(fault));
}
});
}
public List<CustomClass> getData(){
return this.data;
}
public void setData(List<CustomClass> data){
this.data = data;
}
public void addData(List<Poster> data){
this.data.addAll(data);
}
public List<CustomClass> getData(FilterEnum filter){
if(filter == FilterEnum.NOFILTER){
return getData();
}else{
// Filtering and returning filtered data
}
return getData();
}
}
The data is fetched correctly and the list actually contains data after it. Also, only one instance is created, as intended. However, whenever I call getData later, the length of this.data is 0. Because of this I also tried it with a subclass of Application holding the DataHelper object, resulting in the same problem.
Is there a good way of debugging this? Is there something like global watches in Android Studio?
Is there something wrong with my approach? Is there a better approach? I am mainly an iOS developer, so Android is pretty new to me. I am showing the data from the ArrayList in different views, thus I want to have it present in an the ArrayList as long as the application runs.
Thanks!
EDIT: Example use in a list view fragment (only relevant parts):
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
filter = FilterEnum.NOFILTER;
data = DataHelper.getInstance().getData(filter);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
customClassListAdapter = new customClassListAdapter(getActivity(), data);}
EDIT2: Added code where I fetch the data from Backendless, changed reference of DataHelper to reference of data in first EDIT
EDIT3: I usa a local EventBus for notifying the list view about the new data. This looks like this and works (initially the data gets populated, but after e.g. applying a filter, the ArrayList I get with getData is empty):
#Subscribe
public void onMessageEvent(FetchedDataEvent event) {
customClassListAdapter.notifyDataSetChanged();
}
Try instead of keeping reference to your DataHelper instance, keeping reference to your list of retrieved items. F.e. when you first fetch the list (and it's ok as you say), assign it to a class member. Or itarate through it and create your own array list of objects for future use.
Okay I finally found the problem. It was not about the object or memory management at all. Since I give the reference on getData to my ArrayAdapter, whenever I call clear (which I do when changing the filter) on the ArrayAdapter, it empties the reference. I basically had to create a copy of the result for the ArrayAdapter:
data = new ArrayList<>(DataHelper.getInstance().getData(filter));
I was not aware of the fact that this is a reference at all. So with this the data always stays in the helper entirely. I only did this because this:
customClassListAdapter.notifyDataSetChanged();
does hot help here, it does not call getData with the new filter again.
Thanks everyone for your contributions, you definitely helped me to debug this.
It is likely that getData does get called before the data is filled.
A simple way to debug this is to add (import android.util.Log) Log.i("MyApp.MyClass.MyMethod", "I am here now"); entries to strategic places in fetchData, addData and getData and then, from the logs displayed by adb logcat ensure the data is filled before getData gets called.
I feel like a broken record.
After many attempts, I have failed at getting a listview through Parse data to display a specific set of information.
Here is my model...this is all data from users:
#ParseClassName("Midwifefirm")
public class Midwifefirm extends ParseObject {
public Midwifefirm() {
// A default constructor is required.
}
//practice name
public String getPracticeName() {
return getString("practicename");
}
public void setPracticeName(String practicename) {
put("practicename", practicename);
}
//education
public String getEducation() {
return getString("education");
}
public void setEducation(String education) {
put("education", education);
}
//years in practice
public String getYearsinPractice() {
return getString("yearsinpractice");
}
public void setYearsinPractice(String yearsinpractice) {
put("yearsinpractice", yearsinpractice);
}
//practice philosophy
public String getPracticePhilosophy() {
return getString("practicephilosophy");
}
public void setPracticePhilosophy(String practicephilosophy) {
put("practicephilosophy", practicephilosophy);
}
I have this adapter; I am wondering what to place in the query section, as I just want to pull the data into the ListView that is defined in the data model:
public class CustomMidwifeAdapter extends ParseQueryAdapter<Midwifefirm> {
public CustomMidwifeAdapter(Context context) {
super(context, new ParseQueryAdapter.QueryFactory<Midwifefirm>() {
public ParseQuery<Midwifefirm> create() {
// Here we can configure a ParseQuery to display
// only top-rated meals.
ParseQuery query = new ParseQuery("Midwives");
return query;
}
});
}
#Override
public View getItemView(Midwifefirm midwifefirm, View view, ViewGroup parent) {
if (view == null) {
view = View.inflate(getContext(), R.layout.activity_midwife_result_list, null);
}
//use midwifefirm as item view/list
super.getItemView(midwifefirm, view, parent);
// find in layout the practice name
TextView titleTextView = (TextView) view.findViewById(R.id.practicename);
//in the midwifefirm data model, call getPracticename
titleTextView.setText(midwifefirm.getString("practicename"));
// Add education view
TextView EducationView = (TextView) view.findViewById(R.id.education);
EducationView.setText(midwifefirm.getString("education"));
// Add yearsexperience view
TextView ExperienceView = (TextView) view.findViewById(R.id.yearsinpractice);
ExperienceView.setText(midwifefirm.getString("yearsinpractice"));
//Add practice philosophy view
TextView PracticePhilosophyView = (TextView) view.findViewById(R.id.practicephilosophy);
PracticePhilosophyView.setText(midwifefirm.getString("practicephilosophy"));
return view;
}
}
And here is the Main Activity:
public class MidwifeResultList extends ListActivity {
private ParseQueryAdapter<ParseObject> mainAdapter;
private CustomMidwifeAdapter midwifeListAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//initialize main ParseQueryAdapter
mainAdapter = new ParseQueryAdapter<ParseObject>(this, Midwifefirm.class);
//which keys in Midwife object
mainAdapter.setTextKey("practicename");
mainAdapter.setTextKey("education");
mainAdapter.setTextKey("yearsinpractice");
mainAdapter.setTextKey("practicephilosophy");
// Initialize the subclass of ParseQueryAdapter
midwifeListAdapter = new CustomMidwifeAdapter(this);
// Default view is all meals
setListAdapter(mainAdapter);
}
Every time I run this, I get no results.
Thanks in advance for any help
Michael
I can tell you why I think it fails now and I can tell you why I'm very sure it will fail after you sort out the current issue.
It seems that you're trying to use different classes
#ParseClassName("Midwifefirm")
public class Midwifefirm extends ParseObject {
and
ParseQuery query = new ParseQuery("Midwives");
You need to be consistent and use the same name. Either use Midwives or Midwifefirm for both. Let's assume you picked the latter. You're also saying
all that is stored in the user table...wasn't sure if I needed to create new tables.
The query above wants to get all entries of type Midwives. If there's no such type, it'll return nothing. So you have two options:
In you Parse dashboard, reate a class Midwifefirm (don't forget to update the String inside #ParseClassName above) and store your Midwifefirm data in there. You don't need to change your query for this.
Add a column to your ParseUser class, such as type, that you can set to Midwifefirm or whatever if that user is a Midwifefirm or whatever. Then in your query you need to add:
ParseQuery query = new ParseQuery("Midwives");
query.whereEquals("type", "Midwifefirm");
I greatly prefer the former.
Anyway, once your done that, the issue is that you're not using a custom view for this. You're relying on the one provided by Android by default for ListActivity. I am fairly sure it doesn't have any of the fields you're after, so you should create a custom view for this, then at the top of onCreate in your Activity make sure you use it
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_custom_view);
By the way, the following are redundant if you populate then in getItemView anyway:
mainAdapter.setTextKey("practicename");
mainAdapter.setTextKey("education");
mainAdapter.setTextKey("yearsinpractice");
mainAdapter.setTextKey("practicephilosophy");
One final advice: if you're still having issues, set breakpoints and do some investigations first. What you need to check is:
Whether you get anything at all from Parse when you do your query. Your adapter has an useful addOnQueryLoadListener that you may use to check whether anything's been retrieved at all.
If stuff is retrieved successfully, you need to check whether the list view is populated correctly. Again, use breakpoints, this time in getItemView maybe.
I'm going to do a wild guess here using the lovely brainwrecking API help of Parse.com about ParseQueryAdapters
Before continuing, may I mind you that my experience with ParseQueryAdapters is a minimum but I think I have a basic knowledge about them + I have some experience with Parse on its own. ANYHOW,
As an example they use both these
final ParseQueryAdapter adapter = new ParseQueryAdapter(this, "Midwives");
adapter.setTextKey("name");
ListView listView = (ListView) findViewById(R.id.listview);
listView.setAdapter(adapter);
and
// Instantiate a QueryFactory to define the ParseQuery to be used for fetching items in this
// Adapter.
ParseQueryAdapter.QueryFactory<ParseObject> factory =
new ParseQueryAdapter.QueryFactory<ParseObject>() {
public ParseQuery create() {
ParseQuery query = new ParseQuery("Midwives");
return query;
}
};
// Pass the factory into the ParseQueryAdapter's constructor.
ParseQueryAdapter<ParseObject> adapter = new ParseQueryAdapter<ParseObject>(this, factory);
adapter.setTextKey("name");
// Perhaps set a callback to be fired upon successful loading of a new set of ParseObjects.
adapter.addOnQueryLoadListener(new OnQueryLoadListener<ParseObject>() {
public void onLoading() {
// Trigger any "loading" UI
}
public void onLoaded(List<ParseObject> objects, ParseException e) {
// Execute any post-loading logic, hide "loading" UI
}
});
// Attach it to your ListView, as in the example above
ListView listView = (ListView) findViewById(R.id.listview);
listView.setAdapter(adapter);
To start of, the reason why I think nothing is loading inside your list has to do with a little mixup between the initilization of your ParseQueryAdapter and your custom adapter.
You configure the basic adapter, and also initialize a custom adapter but you don't do anything with the custom adapter, tho the custom adapter seems to contain the logics to load your data model.
I think what you're looking for is something like this:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//initialize main ParseQueryAdapter
mainAdapter = new CustomMidwifeAdapter<ParseObject>(this);
//which keys in Midwife object
mainAdapter.setTextKey("practicename");
mainAdapter.setTextKey("education");
mainAdapter.setTextKey("yearsinpractice");
mainAdapter.setTextKey("practicephilosophy");
// Default view is all meals
setListAdapter(mainAdapter);
}
All you need to pass is the context (aka "this"), and the constructor of your custom class will handle the factory internal
super(context, new ParseQueryAdapter.QueryFactory<Midwifefirm>() {
public ParseQuery<Midwifefirm> create() {
// Here we can configure a ParseQuery to display
// only top-rated meals.
ParseQuery query = new ParseQuery("Midwives");
return query;
}
});
Tho to be honest since you do:
new ParseQueryAdapter<ParseObject>(this, Midwifefirm.class);
I wonder if you'd need to change your "QueryFactory" to
super(context, new ParseQueryAdapter.QueryFactory<Midwifefirm>() {
public ParseQuery<Midwifefirm> create() {
// Here we can configure a ParseQuery to display
// only top-rated meals.
ParseQuery query = new ParseQuery(MidWifefirm.class);
return query;
}
});
Where you pass a class to the the query rather than the tableName, but I could be wrong on that one.
Either way I hope this has helped in some way!
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"