I am trying to apply a tutorial on a basic app i am building as a method to learn Android Studio, and part of what i am doing is having a new window ( activity ) with a ListView to view information saved in a CSV file. When i load the main app, it is fine, but when i click on the button that suppose to take me to the new window where the List view is to show the CSV content, the app stops ( shut down ).
Attached, a screen shot of the project layout, and the window where the ListView is.
Following are the codes;
This is the code from the class where the code of the ListView is written, the class name is LogerView, and there is a button at the MainActivity class, the main window for the app, that would call the LogerView;
public class LogerView extends AppCompatActivity {
CSVAdapter mAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.viewer);
ListView mList = (ListView) findViewById(R.id.mList);
mAdapter = new CSVAdapter(this, -1);
mList.setAdapter(mAdapter);
mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View v, int pos, long id) {
Toast.makeText(v.getContext(), mAdapter.getItem(pos).getListCode(), Toast.LENGTH_SHORT).show();
}
}
);
}
public void toasting(String msg) {
Toast.makeText(LogerView.this, msg, Toast.LENGTH_SHORT).show();
}
public void switchToMain(View v) {
Intent MainActivity = new Intent(this, MainActivity.class);
startActivity(MainActivity);
}
}
This is the Code in the CSVAdapter :
public class CSVAdapter extends ArrayAdapter<fileView>{
Context ctx;
public CSVAdapter(Context context, int textViewResourceId) {
super(context, textViewResourceId);
this.ctx = context;
loadArrayFromFile();
}
#Override
public View getView(final int pos, View convertView, final ViewGroup parent){
TextView mView = (TextView)convertView;
if(null == mView){
mView = new TextView(parent.getContext());
mView.setTextSize(15);
}
mView.setText(getItem(pos).getListCode());
mView.setText(getItem(pos).getListNumber());
mView.setText(getItem(pos).getListRadio());
mView.setText(getItem(pos).getListCheckBox());
mView.setText(getItem(pos).getListNoteText());
mView.setText(getItem(pos).getListTime());
return mView;
}
private void loadArrayFromFile(){
try {
String FILENAME = "entry_log.csv";
InputStream is = ctx.openFileInput(FILENAME);
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = reader.readLine()) != null) {
String[] RowData = line.split("/");
if (RowData.length < 6) {
Log.e("SOME_TAG", "Invalid or empty line! . . .");
continue;
} else {
fileView cur = new fileView();
cur.setListCode(RowData[0]);
cur.setListNumber(RowData[1]);
cur.setListRadio(RowData[2]);
cur.setListCheckBox(RowData[3]);
cur.setListNoteText(RowData[4]);
cur.setListTime(RowData[5]);
this.add(cur);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
I renamed the PltInfo class to fileView, and the Code is:
package net.testerapp.loger;
public class fileView {
private String listCode, listNumber, listRadio, listCheckBox, listNoteText, listTime;
public String getListCode() {
return listCode;
}
public void setListCode(String listCode) {
this.listCode = listCode;
}
public String getListNumber() {
return listNumber;
}
public void setListNumber(String listNumber) {
this.listNumber = listNumber;
}
public String getListRadio() {
return listRadio;
}
public void setListRadio(String listRadio) {
this.listRadio = listRadio;
}
public String getListCheckBox() {
return listCheckBox;
}
public void setListCheckBox(String listCheckBox) {
this.listCheckBox = listCheckBox;
}
public String getListNoteText() {
return listNoteText;
}
public void setListNoteText(String listNoteText) {
this.listNoteText = listNoteText;
}
public String getListTime() {
return listTime;
}
public void setListTime(String listTime) {
this.listTime = listTime;
}
}
The MainActivity Code is as following, and so far this one operate in a perfect way, saving info, clearing text, but when clicked on the view Button which should switch to the PltInfo class to display the ListView, is where the app shut down. ( NO CHANGES TO THIS CODE )*
My CSV file is not located in an Assets folder as the tutorial, i view it through Android Device Monitor on this path emulator/data/data/net.testerapp.loger/files/entry_log.csv
so i created a string FILENAME = entry_log.csv;
and placed it in the code after InputStream is = ctx.openFileInput(FILENAME);
not sure if this is the problem and how to solve it ?
Plus the ListView layout, is not well organized in a way where it would show all the items, i just wanted to first test it, by showing the first item as in the tutorial but did not lunch the window at all in order to go from there reorganizing the layout of the List ... any guidance on how to fix that too will be appreciated.
The link to the tutorial is : https://www.youtube.com/watch?v=S8_HnA7aLd0
** Canceld the logcat entry since i am not getting an error now
[![enter image description here][2]][2]
[![enter image description here][3]][3]
Well, i went over the steps in the Tutorial and i redid it, this time i did not get any Error, but still no response from the app, i was apple to switch to the next window activity from the MainActivity after hitting the view button, but then in hte second window, i get an empty space, without the List, i made sure the list in the layout, and the id of the list match what is in the code but not sure why it is not applying the code !?
I edited the code in the main post, to what i have right now, and attached the current screen shots of the app.
One last thing this is the viewer layout .xml file :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:layout_width="wrap_content"
android:layout_height="235dp"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:id="#+id/mList" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="View File"
android:id="#+id/ViewFileBtn"
android:layout_marginTop="60dp"
android:layout_gravity="center"
android:onClick="ViewFile" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Back"
android:id="#+id/BackBtn"
android:layout_gravity="center|bottom"
android:onClick="switchToMain" />
</LinearLayout>
[enter image description here][4]
I am trying to understand the problem here, and awaiting some help, here are some screen shots i took running the debugger, which i am not really familiar with, thus kind of lost trying to understand the threads.
[![enter image description here][5]][5]
[enter image description here][6]
from the exception:
Caused by: java.lang.ArrayIndexOutOfBoundsException: length=1; index=1
at
net.testerapp.loger.CSVAdapter.loadArrayFromFile(CSVAdapter.java:103)
it says you are trying to read array item at index 1, while the array does not have an item in that position
i.e: the array length is shorter than the position you are trying to read.
to avoid this, check the RowData length before creating the object of PltInfo
in method loadArrayFromFile():
while ((line = reader.readLine()) != null) {
//Split to separate the name from the capital
String[] RowData = line.split(",");
if(RowData.legth<6){
Log.e("SOME_TAG","invalid or empty line! skipping...");
continue;
}else{
//Create a State object for this row's data.
PltInfo cur = new PltInfo();
cur.setEntryCode(RowData[0]);
cur.setEntryNum(RowData[1]);
cur.setSelected(RowData[2]);
cur.setCheckBoxText(RowData[3]);
cur.setNoteText(RowData[4]);
cur.setGetNow(RowData[5]);
//Add the State object to the ArrayList (in this case we are the ArrayList).
this.add(cur);
}
}
I believe the reason for the crash is that you are executing loadArrayFromFile on the main thread, this is a long running process as it involves opening and reading a file. try wrapping loadArrayFromFile in an AsyncTask and it's better to move it out of the Adapter class.
**
EDIT
**
You can implement the AsyncTask like this:
private class OpenFileTask extends AsyncTask<Void, Void, ArrayList<PltInfo>> {
protected Long doInBackground(Void... params) {
ArrayList<PltInfo> data=loadArrayFromFile();
return data;
}
private void loadArrayFromFile(){
try {
ArrayList<PltInfo>data=new ArrayList<PltInfo>();
// Get input stream and Buffered Reader for our data file.
String FILENAME = "entry_log.csv";
InputStream is = ctx.openFileInput(FILENAME);
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String line;
//Read each line
while ((line = reader.readLine()) != null) {
//Split to separate the name from the capital
String[] RowData = line.split(",");
//Create a State object for this row's data.
PltInfo cur = new PltInfo();
cur.setEntryCode(RowData[0]);
cur.setEntryNum(RowData[1]);
cur.setSelected(RowData[2]);
cur.setCheckBoxText(RowData[3]);
cur.setNoteText(RowData[4]);
cur.setGetNow(RowData[5]);
//Add the State object to the ArrayList (in this case we are the ArrayList).
data.add(cur);
}
return data;
} catch (IOException e) {
e.printStackTrace();
}
}
protected void onPostExecute(ArrayList<PltInfo> result) {
if(result!=null){
}
}
}
Then pass the data to your adapter like this:
private ArrayList<PltInfo>mData;
public CSVAdapter(Context context, int textViewResourceId,ArrayList<PltInfo> data) {
super(context, textViewResourceId);
//Store a reference to the Context so we can use it to load a file from Assets.
this.ctx = context;
mData=data;
}
then call the async task from your activity like this:
OpenFileTask task=new OpenFileTask();
task.execute();
So, Problem solved, and the CODE is sound ... the file had to be uploaded again, and the if statement as #Yazan mentioned really helped ... Thanks to Freenode #android-dev and a Heor *** Zharf ** who insisted i learn how to use the Debugger and followed with me through the problem it is clear.
Must admit, the outcome of the listview is not what expected, i just need to go over the layout and figure out what is the problem with it.
Related
Here is my issue. I can save an object, but if I save another object, it will erase the previous item. I'm using gson lib to save my items. After some researches I've seen this How to use SharedPreferences to save more than one values?
But I can't use it because of my custom objects, if I use .toString(), I will not be able to get back my original item. I know that's it's the same key used to save object that will erase the previous one but I dont really know how to give a different key every time I will save an item.
Code to add :
addFav.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (currentProduit.getIsAdded() ==0) {
SharedPreferences.Editor prefsEditor = mPrefs.edit();
Gson gson = new Gson();
String myJson = gson.toJson(currentProduit);
Log.i("INFO", "Value of saved data" + myJson);
prefsEditor.putString("myproduct", myJson);
prefsEditor.apply();
Toast.makeText(getApplicationContext(), "Data saved !", Toast.LENGTH_SHORT).show();
addFav.setText(R.string.delete_fav);
currentProduit.setIsAdded(1);
} else {
addFav.setText(R.string.add_fav);
currentProduit.setIsAdded(0);
SharedPreferences.Editor editor = mPrefs.edit();
editor.remove("myproduct").apply();
Toast.makeText(getApplicationContext(), "Data removed !", Toast.LENGTH_SHORT).show();
}
}
});
Code to get back from other activity:
String myJson = mPrefs.getString("myproduct", "");
Log.i("INFO", "Value of loaded data" + myJson);
if (myJson.isEmpty() && favProductList.isEmpty()) {
listview_R.setAdapter(null);
Log.i("INFO", "No items");
title.setText(getString(R.string.fav));
} else if (myJson.isEmpty() && favProductList != null) {
myCustomAdapterVersionR = new CustomAdapter_VersionR(getApplicationContext(), favProductList);
listview_R.setAdapter(myCustomAdapterVersionR);
} else {
Product savedProduct = gson.fromJson(myJson, Product.class);
favProductList.add(savedProduct);
Log.i("INFO", "Favorite was added");
myCustomAdapterVersionR = new CustomAdapter_VersionR(getApplicationContext(), favProductList);
listview_R.setAdapter(myCustomAdapterVersionR);
}
Thanks for helping ! Btw, since it's not saving a lot of items, I didnt use sqlite db, cheers !
EDIT: I tried Juan Cortés solution, but I have this error after getting back the shared preferences --> error: incompatible types: CustomProduct[] cannot be converted to List, here is the code
if (fromPrefs.isEmpty() && favProductList.isEmpty()) {
listview_R.setAdapter(null);
Log.i("INFO", "No items");
title.setText(getString(R.string.fav));
} else {
//Product savedProduct = gson.fromJson(fromPrefs, Product.class);
//favProductList.add(savedProduct);
//Get the Object array back from the String `fromPrefs`
CustomProduct[] reInflated = gson.fromJson(fromPrefs,CustomProduct[].class);
Log.i("INFO", "Favorite was added");
myCustomAdapterVersionR = new CustomAdapter_VersionR(getApplicationContext(), reInflated); //error
listview_R.setAdapter(myCustomAdapterVersionR);
}
Thanks !
As an overly simplified app for example, you could define a custom class as follows (of course you'll have to adapt it to your particulars). The concept is create an array of custom objects, convert it to json, store it. It's really straightforward once you see it.
The code
Gson gson = new Gson();
//Create an array to work with it, dummy content
CustomProduct[] exampleList = new CustomProduct[10];
for(int i=0;i<10;i++){
exampleList[i] = new CustomProduct("string","number:"+i);
}
//Get a String representation of the objects
String forStoring = gson.toJson(exampleList);
//HERE you can store and retrieve to SharedPreferences
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.edit().putString("myarrayofcustomobjects", forStoring).commit();
//Get the string back from the SharedPreferences
String fromPrefs = prefs.getString("myarrayofcustomobjects","");
//Get the Object array back from the String `fromPrefs`
CustomProduct[] reInflated = gson.fromJson(fromPrefs,CustomProduct[].class);
Notes
If you already have a set of objects in an array, you'll need to inflate the array as shown above, create a new array with those elements + the one you want to add, convert them to a string again, and store them. Once this becomes too much of a hassle, you'll move to another means of persisting data for you app, but for as long as there are not that many, it should be ok.
Assuming
To get this to work, I'm assuming you have a Custom object named CustomProduct with the following definition:
public class CustomProduct {
String field1,field2;
public CustomProduct(String field1, String field2){
super();
this.field1 = field1;
this.field2 = field2;
}
#Override
public String toString() {
return "CustomProduct [field1="+field1+",field2="+field2+"]";
}
}
Update
User wants to show the results in a listview. You can define a custom adapter like the following to get it to work. Let this be the time for me to advise you to soon move towards RecyclerView instead of ListView but first tackle the problem you have, make it work, then improve upon it
public class CustomAdapter extends BaseAdapter{
private CustomProduct[] mProducts;
private LayoutInflater mInflater;
public CustomAdapter(Context context, CustomProduct[] products){
mProducts = products;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public int getCount() {
return mProducts.length;
}
public CustomProduct getItem(int i) {
return mProducts[i];
}
public long getItemId(int i) {
return i;
}
public View getView(int i, View convertView, ViewGroup parent) {
//Purposely not doing view recycling for sake of clarity
View row = mInflater.inflate(R.layout.custom_row,parent,false);
//Set the data from the row
((TextView)row.findViewById(R.id.field1)).setText(getItem(i).field1);
((TextView)row.findViewById(R.id.field2)).setText(getItem(i).field2);
//Return the view
return row;
}
}
By setting this adapter to your ListView and creating the layout (which simply consists in two textviews with the given ids) you will get the following result. You can try removing the part where it creates the data after it's run the first time and leaving only the part where it fetches the data to ensure it's persisted.
I have created a class that holds common variables and functions and is inherited by the activity classes that interface with the different UI pages in my app. I have been passing information between classes and activities using getVariable() and setVariable(input) functions. Suddenly, I can no longer pass information this way (it had been working well until recent edits, and now I can't figure out which change screwed this up). I have used Log outputs to determine that the data is storing properly - with the setVariable(input) functions - but when called later with the getVariable() functions it returns null. Any thoughts?
*Note, I recently started incorporating fragments into my project, extending FragmentActivity instead of Activity on my main class. I don't think this is causing the problem, but could it? If it does, whats the best practice to pass global variable info, and use fragments?
Code samples:
Main Inherited class:
public class MenuBarActivity extends FragmentActivity {
private String keyA;
private String keyB;
private int token;
private String Salt;
private long expires;
public String getKeyB() {
return keyB;
}
public String getKeyA() {
return keyA;
}
public int getTokenID() {
return token;
}
public void setToken(int tkn) {
token = tkn;
}
public void setKeyB(String kyB) {
keyB = kyB;
}
public void setKeyA(String kyA) {
keyA = kyA;
}
//Other common functions
}
LogIn Activity Class (gets log in info from web, stores into global variables):
public class WebContentGet extends MenuBarActivity{
public int tryLogOn(String uEmail, String pw) {
//call to get new keys on start up
JSONObject jObSend = new JSONObject();
try {
jObSend.put("email", uEmail);
jObSend.put("password", pw);
t.start();
t.join();
if(getStatus() == USER_STATUS_SUCCESSFULLOGIN){
String data = getData();
JSONObject jObReturn = new JSONObject(data);
String kyA = jObReturn.getString("keyA");
String kyB = jObReturn.getString("keyB");
int tkn = Integer.parseInt(jObReturn.getString("tokenID"));
String salt = jObReturn.getString("salt");
long exp = Long.parseLong(jObReturn.getString("expiration"));
int uID = Integer.parseInt(jObReturn.getString("userID"));
// Log outputs confirm data being read properly, and reported to setX() functions
setToken(tkn);
setKeyA(kyA);
setKeyB(kyB);
setSalt(salt);
setExpires(exp);
Log.d("WebContentGet tryLogIn","login values stored");
}
return getStatus();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return getStatus();
}
}
Activity Class, checks if keyA/B/etc stored:
public class UserLogIn2 extends MenuBarActivity implements EmailListener {
String emailIn;
String pwIn;
Context context = this;
#Override
public void onEmailLogInClick(String email, String pw) {
Log.d("UserLogin2", "onEmailLogInClick");
emailIn = email;
pwIn = pw;
emailIn = emailIn.trim();
emailIn = emailIn.toUpperCase();
Log.d("prepped email", emailIn);
pwIn = pwIn.trim();
WebContentGet webOb = new WebContentGet();
int webLog = webOb.tryLogOn(emailIn, pwIn);
if (webLog == USER_STATUS_SUCCESSFULLOGIN) {
int tkn = getTokenID();
long exp = getExpires();
String kya = getKeyA();
String kyb = getKeyB();
String slt = getSalt();
Log.d("UserLogIn2 - token", String.valueOf(tkn));
//Log statements confirm that getX() functions returning null
session.storeLoginSession(emailIn, pwIn, thisUser, tkn, exp, kya, kyb, slt);
Intent intent1 = new Intent(context, MainActivitiy.class);
startActivity(intent1);
} else {
showDialog(this, "Log in failure", "Incorrect Password");
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.userlogin2);
}
}
This cannot work, because you have two differend instances of your MenuBarActivity. Also that is not the way to pass data from one activity to another in android.
If you want to use data from one activity in another activity, you have to add them to an intent in the activity which provides the data, and extract them in the other. For more information see here: How do I pass data between Activities in Android application?
If you don't want to start the activity and send the data with the intent, you have to store the data somewhere e.g. SharedPreferences and fetch them again: How to use SharedPreferences in Android to store, fetch and edit values
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?
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"
I have extensive use of ArrayAdapter in my app because most Activities are holding a ListView and I need some custom stuff in them.
I took a look at the test classes in the android developer documentation but wasn't able to find some examples or a proper testclass...
1) Are there any best practices for (unit)-testing ArrayAdapter in Android?
2) May I have chosen the wrong approach (with the adapters) and killed testability this way?
You can write the test extending AndroidTestCase It will looks something like this:
public class ContactsAdapterTest extends AndroidTestCase {
private ContactsAdapter mAdapter;
private Contact mJohn;
private Contact mJane;
public ContactsAdapterTest() {
super();
}
protected void setUp() throws Exception {
super.setUp();
ArrayList<Contact> data = new ArrayList<Contact>();
mJohn = new Contact("John", "+34123456789", "uri");
mJane = new Contact("Jane", "+34111222333", "uri");
data.add(mJohn);
data.add(mJane);
mAdapter = new ContactsAdapter(getContext(), data);
}
public void testGetItem() {
assertEquals("John was expected.", mJohn.getName(),
((Contact) mAdapter.getItem(0)).getName());
}
public void testGetItemId() {
assertEquals("Wrong ID.", 0, mAdapter.getItemId(0));
}
public void testGetCount() {
assertEquals("Contacts amount incorrect.", 2, mAdapter.getCount());
}
// I have 3 views on my adapter, name, number and photo
public void testGetView() {
View view = mAdapter.getView(0, null, null);
TextView name = (TextView) view
.findViewById(R.id.text_contact_name);
TextView number = (TextView) view
.findViewById(R.id.text_contact_number);
ImageView photo = (ImageView) view
.findViewById(R.id.image_contact_photo);
//On this part you will have to test it with your own views/data
assertNotNull("View is null. ", view);
assertNotNull("Name TextView is null. ", name);
assertNotNull("Number TextView is null. ", number);
assertNotNull("Photo ImageView is null. ", photo);
assertEquals("Names doesn't match.", mJohn.getName(), name.getText());
assertEquals("Numbers doesn't match.", mJohn.getNumber(),
number.getText());
}
}
Probably you will have to test getView several times with different arguments, to test all scenarios.