I am trying to add ListView items one by one. So if I have say -- 60 items -- the application would add the views to the list view one at a time -- thus showing the user that the application is loading more things.
This is my code:
try {
JSONArray j = getTaggsJSON();
Log.v(TAG, String.valueOf(j.length()));
a = new createSpecialAdapter(this, R.layout.individual_tagg_view,
R.layout.list_item, view, j);
ListView v = this.getListView();
v.setStackFromBottom(true);
setListAdapter(a);
new addViewsToList().execute(j);
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ServiceException e) {
e.printStackTrace();
}
}
private class addViewsToList extends AsyncTask<JSONArray, View, List<View>> {
protected List<View> doInBackground(JSONArray... jsonArrays) {
List<View> v = new ArrayList<View>();
Log.v(TAG, String.valueOf(jsonArrays[0].length()));
for (int x = 0; x < jsonArrays[0].length(); x++) {
try {
Log.v(TAG, jsonArrays[0].getJSONObject(x).toString());
v.add(ViewAdapter.createTaggView(jsonArrays[0]
.getJSONObject(x), c));
} catch (JSONException e) {
e.printStackTrace();
}
publishProgress(v.get(x));
}
return v;
}
protected void onProgressUpdate(View... v) {
Log.v(TAG, "I'm updating my progress!");
a.notifyDataSetChanged();
}
protected void onPostExecute(List<View> views) {
Log.v(TAG, "i'm done!");
Log.v(TAG, String.valueOf(views.size()));
}
}
So, it looks like my code is printing out the views correctly, and putting them in the list correctly, however my activity displays nothing. What am I doing wrong? I thought setting the adapter before -- when there are no views in the list -- and then updating the adapter that the list was changed was the right way to go.... Obviously not.. Can anyone help me?
If you need clarification on my questions please let me know.
EDIT: Here is the adapter code to supplement my question.
private class SpecialAdapter extends ArrayAdapter<JSONArray> {
JSONArray json;
public SpecialAdapter(Context context, int textViewResourceId,
int listItem, JSONArray j) {
super(context, textViewResourceId);
this.json = j;
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
Log.v(TAG,"I'm inside the getView method.");
try {
JSONObject info = json.getJSONObject(position);
Log.v(TAG,info.toString());
if (convertView == null) {
LayoutInflater i = (LayoutInflater) RecentTaggs.this
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = i.inflate(R.layout.individual_tagg_view, parent,
false);
holder = new ViewHolder();
holder.username = (TextView) convertView
.findViewById(R.id.userName);
holder.review = (TextView)convertView.findViewById(R.id.review_text);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.review.setText(info.getString("review"));
return convertView;
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
My output doesn't output any of my Log.v statements, and doesn't show that its even inside the GetView method.
There is certainly something wrong
with your Adapter. You should never
save Views that should displayed in a
ListView on your own. Only save the
data that is needed to create the
View and overwrite the getView method
of your Adapter to create the needed
view.
The second thing is the list that you
are using in the background task is
visible in the scope of the
doInBackground method only. No change
to this list can have an effect on
your UI.
You are passing the JsonArray to your Adapter before starting the background thread. If your Adapter would be working correctly the ListView should be fully functional before the background thread even starts working. You should pass an empty JsonArray into the ListView and if you are extending an ArrayAdapter you can use addItem in your publish progress method.
Some a little bit unrelated things about the naming of your Adapter class. You called it createSpecialAdapter. This sounds like a method name but it is a classname. Try to start all class names with an uppercase letter and give them names of the things they represent like SpecialAdapter.
Related
I am using an FTP Client to get videos from a server and show them on a gridview. I would like to display duration of each gridview item (videos) as well.
I have tried using this code outside of the gridview adapter:
for (FTPFile file : files) {
if (file.isFile())
{
//GetDuration
try{
//NOTE: must not use https
mmr = new FFmpegMediaMetadataRetriever();
mmr.setDataSource(myDomain+skyVideos+"/"+username+"/"+file.getName());
long duration =Long.parseLong(mmr.extractMetadata(FFmpegMediaMetadataRetriever.METADATA_KEY_DURATION));
duration=duration/1000;
long minute=duration/(60);
long second=duration-(minute*60);
mmr.release();
strDuration = String.format("%02d:%02d" , minute, second);
//ALL VIEWS
for (int j = 0; j < gv.getChildCount(); j++) {
View child = gv.getChildAt(j);
holder.tvDuration = child.findViewById(R.id.tvDuration);
runOnUiThread(new Runnable() {
#Override
public void run() {
holder.tvDuration.setText(strDuration);
}
});
}
arrListStr_Duration.add(strDuration);
stringArr_Duration = new String[arrListStr_Duration.size()];
stringArr_Duration = arrListStr_Duration.toArray(stringArr_Duration);
} catch (IllegalStateException e) {
//
}catch (IllegalArgumentException e)
{
//
}
}
//SORT BY TIMESTAMP
Arrays.sort(files, Comparator.comparing((FTPFile remoteFile) -> remoteFile.getTimestamp()).reversed());
}
client.disconnect();
This works fine, even though it sets duration (holder.tvDuration.setText(strDuration);) of videos one after the other and not all at the same time (because of how the for loop works?) even though I'm running this method in a background: (I can live with this though)
new Thread(new Runnable() {
#Override
public void run() {
//code in background here
getFTP_Duration();
getFTP_SizeArray();
}
}).start();
Anyway this all fine until the user starts scrolling before the arrListStr_Duration is fully populated then the duration will be set on the wrong video because the views get recycled.
Now in the GridView adapter I have tried the following in the View getView method:
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
holder = new Holder();
if (convertView == null)
{
convertView = inflater.inflate(R.layout.activity_video_gridview_single_checkbox, null);
}
holder.ivImage = convertView.findViewById(R.id.ivImage);
holder.tvInvisibleDate = convertView.findViewById(R.id.tvInvisibleDate);
holder.ivCheckbox = convertView.findViewById(R.id.ivCheckbox);
holder.tvDuration = convertView.findViewById(R.id.tvDuration);
holder.ibDots = convertView.findViewById(R.id.ibDots);
holder.mypopupWindow = null;
if (arrListStr_File.size() == arrListStr_Duration.size())
{
//Duration finished populating
holder.tvDuration.setText(stringArr_Duration[position]);
}
else
{
//user scrolling while duration is still being populated
try {
for (int i = 0; i < gv.getChildCount(); i++) {
if (arrListStr_Duration.size() != 0)
{
View child = gv.getChildAt(i);
holder.tvDuration = child.findViewById(R.id.tvDuration);
try {
holder.tvDuration.setText(stringArr_Duration[i]);
}catch (IndexOutOfBoundsException e)
{
//
}
}
}
}catch (NullPointerException e)
{
//
}
}
...
This will also mix up the duration and videos because gv.getChildCount() only returns count of what can be seen on screen and this count is reset when user scrolls because of recycling. When the user scrolls after the duration has been fully populated then there are no issues but of cause this happens in the background and user should be able to scroll whenever they want to and still have the correct duration set on the correct videos, how can I achieve this? I thought of not using recycling at all but I understand that this is bad for performance. Hope I made sense.
Documentation of adapter's getView()
* #param position The position of the item within the adapter's data set of the item whose view we want.
* #return A View corresponding to the data at the specified position.
View getView(int position, View convertView, ViewGroup parent);
When the user scrolls after the duration has been fully populated then there are no issues.>
This is because you are correctly using position in this case, whereas in other case you are using child index, that's why GridView is mixing up your videos.
Your entire getView() can be simplified by creating a method to get video duration.
private String getVideoDuration(int index){
if(index <= stringArr_Duration.length)
return stringArr_Duration[index];
return null; // can return empty string also here.
}
Then call it from getView() and pass #param : position to it, not the child index.
I am creating a ListView using Holder
when I scroll slowly everything works perfectly fine and Endless ListView works great but when I open the page and scroll fast to end of the List ListViewfail to scroll further without any error at Logcat.Here is my Adapter class getView method
#Override
public View getView(final int i, View view, ViewGroup viewGroup) {
View row=view;
Holder holder=null;
if(row==null){
LayoutInflater inflater= (LayoutInflater)mContext.getSystemService(mContext.LAYOUT_INFLATER_SERVICE);
row= inflater.inflate(R.layout.half_movie,viewGroup,false);
holder=new Holder(row);
row.setTag(holder);
}else{
holder= (Holder) row.getTag();
}
//here setting all holers
Picasso.with(mContext).load("https://image.tmdb.org/t/p/w185"+temp.getImageUrl()).into(holder.poster);
if(reachedEndOfList(i)) loadMoreData();
return row;
}
private boolean reachedEndOfList(int position) {
// can check if close or exactly at the end
return position == list.size() - 4;
}
private void loadMoreData() {
if(isNetworkAvailable()) {
new MyAdapter.AsyncTaskParseJson().execute(url);
}else{
Toast.makeText(mContext, "Sorry No internet Connection Found!", Toast.LENGTH_SHORT).show();
}
}
public class AsyncTaskParseJson extends AsyncTask<String,String ,void> {
JSONArray dataJsonArr = null;
ProgressDialog progressDialog;
#Override
protected void onPreExecute() {
progressDialog = ProgressDialog.show(mContext,
"Movie Pal!",
"Loading Movies List..");
}
#Override
protected void doInBackground(String... arg0) {
String yourJsonStringUrl = arg0[0];
yourJsonStringUrl=yourJsonStringUrl+"&page="+pageNmber;
JSONParser jParser = new JSONParser();
JSONObject json = jParser.getJSONFromUrl(yourJsonStringUrl.replace(" ","%20"));
System.out.println(json);
try {
JSONArray jsonArray = json.getJSONArray("results");
for (int i=0; i<jsonArray.length(); i++) {
Movie newMovie = new Movie();
//setting to class object
list.add(newMovie);
}
pageNmber++;} catch (JSONException e) {
e.printStackTrace();
}
return json;
}
#Override
protected void onPostExecute() {
progressDialog.dismiss();
}
}
I don't know what i am missing here please help!
You need to call notifyDataSetChanged (or similar function like notifyDataSetInsert) when the new data is loaded so it knows that more data exists and it should allow more scrolling.
Also, you may not be adding enough padding (just 4 items) between the end of the list and when you fetch more data. That may result in stuttering scrolling- you'll reach the end and stop until more data is downloaded, then you can scroll again. As an example- I start fetching new data 10 items before the end, and I'm just hitting a local db, not a remote server.
I have a Listview that gets fed questions and their corresponding choices as such:
The following constructor gets all the necessary information to feed the Listview. All string values are in Arraylists.
public BaseQuestionAdapter(Activity a, ArrayList<String> b, ArrayList<String> c, ArrayList<String> d, ArrayList<String> e) {
activity = a;
this.questionTitleArray = b;
this.choicesArray = c;
this.questionIdArray = d;
this.userIdArray = e;
inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
Each row will hold a single question title and a variable number of choices. Dynamically created buttons will instantiate the choices and on each question title position the buttons will start generating. Like so, the following is the getView method in the BaseAdapter:
public View getView(int position, View convertView, ViewGroup parent) {
View vi = convertView;
vi = inflater.inflate(R.layout.question_layout, null);
LinearLayout questionContainer = (LinearLayout) vi.findViewById(R.id.question_container);
LinearLayout choicesContainer = (LinearLayout) vi.findViewById(R.id.choices_container);
ViewGroup answersContainerParent = (ViewGroup) choicesContainer.getParent();
if (answersContainerParent != null)
answersContainerParent.removeView(choicesContainer);
JSONArray choicesJSONArray = new JSONArray(choicesArray);
try {
String bhb = choicesJSONArray.get(position).toString();
jsnArray = new JSONArray(bhb);
} catch (JSONException e) {
e.printStackTrace();
}
for (int answersArrayIterator = 0; answersArrayIterator < jsnArray.length(); answersArrayIterator++) {
try {
final Button choiceButton = new Button(activity);
choiceButton.setId(buttonId);
String questionId = questionIdArray.get(position).toString();
choiceButton.setTag(questionId);
choiceButton.setTag(position);
ViewGroup layout = (ViewGroup) choiceButton.getParent();
if (layout != null)
layout.removeView(choiceButton);
choiceButton.setText(jsnArray.get(answersArrayIterator).toString());
choiceButton.setTextSize(16);
choiceButton.setBackgroundResource(R.drawable.question_button_template_style);
choiceButton.setGravity(Gravity.CENTER);
choiceButton.setWidth(270);
choiceButton.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
String choiceString = choiceButton.getText().toString();
Object questionId = choiceButton.getTag();
setDataToBeSent(userId, questionId, choiceString);
Integer index = (Integer) v.getTag();
System.out.println("SATURN ASCENDS: " + index);
choicesArray.remove(index);
notifyDataSetChanged();
new HttpAsyncTask2().execute();
return false;
}
});
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.setMargins(10, 10, 10, 10);
choiceButton.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
choiceButton.setLayoutParams(params);
buttonId++;
choicesContainer.addView(choiceButton);
} catch (JSONException e) {
e.printStackTrace();
}
}
TextView questionTitleET = (TextView) vi.findViewById(R.id.question_title);
String questionTitle = questionTitleArray.get(position).toString();
questionTitleET.setText(questionTitle);
ViewGroup questionTitleETParent = (ViewGroup) questionTitleET.getParent();
if (questionTitleETParent != null)
questionTitleETParent.removeView(questionTitleET);
questionContainer.addView(questionTitleET);
questionContainer.addView(choicesContainer);
return vi;
}
What I'm trying to achieve is to remove the parent Listview row whenever a button is LonClicked. I'm trying to achieve this by setting an OnLongClickListener on every button created. Like so:
choiceButton.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
Integer index = (Integer) v.getTag();
System.out.println("SATURN ASCENDS: " + index);
choicesArray.remove(index);
notifyDataSetChanged();
return false;
}
});
Problem is that I don't know what to reference precisely in order for that to happen. Don't know whether its the choicesArray or any of the other resources I'm receiving. Currently when I long click on a button nothing happens. Any clue as to where I'm going wrong? Thank you.
As Luksprog said on the comment, you need to remove the items from the container which is referenced in the getCount(). However I would also like to offer an improvement. Instead of using four different lists of Strings, use an Object which holds each of those string.
Eg.
public class MyClass {
public String questionTitle;
public String choice;
public String questionId;
public String userId;
}
After you have this class you can use a single list, which contains your object. This way you won't be confused as to which list items should be removed in order for the list to change.
I wonder if there is any way to update an information of a single item within a listview. Basically I press the button inside the adapter and it makes a new request, the request will set this returns the value of the adapter. It is a system of "like".
I do not want to call the asynchronous method that gets the list, it takes much again. The code to get all the items in the database is this:
protected ArrayList<Feed> doInBackground(MyTaskParams... params) {
page = params[0].page;
mFilter = params[0].filter;
backgroundItems = new ArrayList<Feed>();
ParseQuery<ParseObject> query = ParseQuery.getQuery("FeedPost");
ParseObject parseObject;
try {
responseList = query.find();
for (int i = 0; i < responseList.size(); i++) {
parseObject = responseList.get(i);
backgroundItems.add(new Feed(parseObject.getObjectId(),
parseObject.getString("Title"),
parseObject.getString("Description"),
parseObject.getString("CompleteText"),
parseObject.getString("imageURL"),
parseObject.getString("Link_on_Site"),
parseObject.getNumber("like_count")));
}
} catch (ParseException e) {
exceptionToBeThrown = e;
}
return backgroundItems;
}
You didn't show your adapter code or your task for updating the like count in the database, so I'm going to assume you're using an AsyncTask. In the getView or bindView function is where you need to update it.
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
ViewHolder holder;
if (convertView == null)
{
//inflate layout and initialize holder
}
else
{
holder = (ViewHolder)convertView.getTag();
}
//get item at position
MyItem item = getItem(position);
if (item != null)
{
holder.likeButton.setOnClickListener(
new OnClickListener()
{
#Override
public void onClick(View v)
{
//update database and set new value
IncrementLikeCountTask task = new IncrementLikeCountTask()
{
#Override
public void onPostExecute(int newValue)
{
item.setLikeCount(newValue);
notifyDataSetChanged();
}
}.execute(position);
}
}
);
}
return convertView;
}
So I assume you're persisting a list of Feed objects in your adapter.
What you need to do is after communicating with the server modify the like_count field of the appropriate Feed object in your adapter and call notifyDataSetChanged() on the adapter.
This will trigger a refresh of the ListView and render your list item with the updated value.
Hey everyone sorry in advance I have been looking frantically for the answer to this question. I know in theory I'm supposed to use
i.putExtra("jsonArray", jArray.toString());
in my intent but it either can't resolve or is giving me errors.
I'm trying to pass the person's name and the id from the array (the id and name from my query). and pass it so i can use the id in my next query on my next activity.
Person resultRow = new Person();
//set that person's attributes
resultRow.id = json_data.getString("id");
resultRow.icon = json_data.getString("birthday");
resultRow.name = json_data.getString("name");
resultRow.address = json_data.getString("address");
resultRow.city = json_data.getString("city");
//this is our arrayList object, we add our Person object to it
arrayOfWebData.add(resultRow);
}
}
catch(JSONException e){
Log.e("log_tag", "Error parsing data "+e.toString());
}
//get our listview
ListView myListView = (ListView)findViewById(R.id.myListView);
//we initialize our fancy adapter object, we already declared it above
//in the class definition
aa=new FancyAdapter();
// here we set the adapter, this turns it on
myListView.setAdapter(aa);
myListView.setOnItemClickListener(onListClick);
}
catch (Exception e)
{
// this is the line of code that sends a real error message to the log
Log.e("ERROR", "ERROR IN CODE: " + e.toString());
// this is the line that prints out the location in
// the code where the error occurred.
e.printStackTrace();
}
}
// here is where the magic begins
//we extend our ArrayAdapter, and use it to create custom views
//as well as execute other more complicated functions
//this time, our ArrayAdapter is an array of Persons, instead of strings like in other tutorials
class FancyAdapter extends ArrayAdapter<Person> {
FancyAdapter() {
super(ListActivity.this, android.R.layout.simple_list_item_1, arrayOfWebData);
}
public View getView(int position, View convertView,
ViewGroup parent) {
ViewHolder holder;
//we call an if statement on our view that is passed in,
//to see if it has been recycled or not. if it has been recycled,
//then it already exists and we do not need to call the inflater function
//this saves us A HUGE AMOUNT OF RESOURCES AND PROCESSING
//this is the proper way to do it
if (convertView==null) {
LayoutInflater inflater=getLayoutInflater();
convertView=inflater.inflate(R.layout.row, null);
//here is something new. we are using a class called a view holder
holder=new ViewHolder(convertView);
//we are using that class to cache the result of the findViewById function
//which we then store in a tag on the view
convertView.setTag(holder);
}
else {
holder=(ViewHolder)convertView.getTag();
}
holder.populateFrom(arrayOfWebData.get(position));
return(convertView);
}
}
class ViewHolder {
public TextView name=null;
public TextView address=null;
public TextView icon=null;
ViewHolder(View row) {
name=(TextView)row.findViewById(R.id.name);
address=(TextView)row.findViewById(R.id.birthday);
birthday=(TextView)row.findViewById(R.id.favorite_color);
}
//notice we had to change our populate from to take an arguement of type person
void populateFrom(Person r) {
name.setText(r.name);
address.setText(r.address);
icon.setText(r.birthday);
}
}
private AdapterView.OnItemClickListener onListClick=new android.widget.AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent i= new Intent(ListActivity.this, bar.class);
i.putExtra("jsonArray", Person(name).toString());
startActivity(i);
}
};
}
You could also implements Parcelable in your class Person to send the current instance of class Person
Or just send the person's id to the next activity with intent extra and get the Json after to compare the id:
intent.putExtra("PERSON_ID", current_person.id);