In my application I need to download a lot of pictures from urls and display them in a gridView. (It can be between 1-200 pictures). I don't want to download all pictures at once. I read about lazy downloading and my question is: Can i get only one part of the Json, download the pictures in a different thread, and only if the user scroll down the gridView, I will continue to the other parts of the Json, and so on?
Edit: Hi again. I want to implement multi select in this gridView and i'm having difficulty to implement the code in the getView() method of the adapter. This is the example i'm using:example. How can I combine this code in my getView() method:
public View getView(int position, View convertView, ViewGroup parent) {
CheckableLayout l;
ImageView i;
if (convertView == null) {
i = new ImageView(Grid3.this);
i.setScaleType(ImageView.ScaleType.FIT_CENTER);
i.setLayoutParams(new ViewGroup.LayoutParams(50, 50));
l = new CheckableLayout(Grid3.this);
l.setLayoutParams(new GridView.LayoutParams(GridView.LayoutParams.WRAP_CONTENT,
GridView.LayoutParams.WRAP_CONTENT));
l.addView(i);
} else {
l = (CheckableLayout) convertView;
i = (ImageView) l.getChildAt(0);
}
ResolveInfo info = mApps.get(position);
i.setImageDrawable(info.activityInfo.loadIcon(getPackageManager()));
return l;
}
public class CheckableLayout extends FrameLayout implements Checkable {
private boolean mChecked;
public CheckableLayout(Context context) {
super(context);
}
public void setChecked(boolean checked) {
mChecked = checked;
setBackgroundDrawable(checked ?
getResources().getDrawable(R.drawable.blue)
: null);
}
public boolean isChecked() {
return mChecked;
}
public void toggle() {
setChecked(!mChecked);
}
}
my getView() code:
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHolder holder;
View vi = convertView;
if(convertView == null) {
vi = inflater.inflate(com.egedsoft.instaprint.R.layout.item_clickable, null);
holder = new ViewHolder();
holder.imgPhoto = (ImageView)vi.findViewById(com.egedsoft.instaprint.R.id.imageClickable);
vi.setTag(holder);
} else {
holder = (ViewHolder) vi.getTag();
}
if (!arrayUrls.get(position).getThumbnailUrl().isEmpty()){
imageLoader.DisplayImage(arrayUrls.get(position).getThumbnailUrl(), holder.imgPhoto);
}
return vi;
}
This is how I fetch multiple photos in my activity. You can use parts of it for fit your logic. I use this to fetch Facebook Images from an Album. So my needs are (I am assuming) different from your needs. But again, the logic may be of use to you.
Note: This will be lengthy. ;-)
These are the global declarations for use through the ACtivity:
// HOLD THE URL TO MAKE THE API CALL TO
private String URL;
// STORE THE PAGING URL
private String pagingURL;
// FLAG FOR CURRENT PAGE
int current_page = 1;
// BOOLEAN TO CHECK IF NEW FEEDS ARE LOADING
Boolean loadingMore = true;
Boolean stopLoadingData = false;
This is the code block that fetches the initial set of Images:
private class getPhotosData extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... arg0) {
// CHANGE THE LOADING MORE STATUS TO PREVENT DUPLICATE CALLS FOR
// MORE DATA WHILE LOADING A BATCH
loadingMore = true;
// SET THE INITIAL URL TO GET THE FIRST LOT OF ALBUMS
URL = "https://graph.facebook.com/" + initialAlbumID
+ "/photos&access_token="
+ Utility.mFacebook.getAccessToken() + "?limit=10";
try {
HttpClient hc = new DefaultHttpClient();
HttpGet get = new HttpGet(URL);
HttpResponse rp = hc.execute(get);
if (rp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
String queryAlbums = EntityUtils.toString(rp.getEntity());
JSONObject JOTemp = new JSONObject(queryAlbums);
JSONArray JAPhotos = JOTemp.getJSONArray("data");
// IN MY CODE, I GET THE NEXT PAGE LINK HERE
getPhotos photos;
for (int i = 0; i < JAPhotos.length(); i++) {
JSONObject JOPhotos = JAPhotos.getJSONObject(i);
// Log.e("INDIVIDUAL ALBUMS", JOPhotos.toString());
if (JOPhotos.has("link")) {
photos = new getPhotos();
// GET THE ALBUM ID
if (JOPhotos.has("id")) {
photos.setPhotoID(JOPhotos.getString("id"));
} else {
photos.setPhotoID(null);
}
// GET THE ALBUM NAME
if (JOPhotos.has("name")) {
photos.setPhotoName(JOPhotos.getString("name"));
} else {
photos.setPhotoName(null);
}
// GET THE ALBUM COVER PHOTO
if (JOPhotos.has("picture")) {
photos.setPhotoPicture(JOPhotos
.getString("picture"));
} else {
photos.setPhotoPicture(null);
}
// GET THE PHOTO'S SOURCE
if (JOPhotos.has("source")) {
photos.setPhotoSource(JOPhotos
.getString("source"));
} else {
photos.setPhotoSource(null);
}
arrPhotos.add(photos);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void result) {
// SET THE ADAPTER TO THE GRIDVIEW
gridOfPhotos.setAdapter(adapter);
// CHANGE THE LOADING MORE STATUS
loadingMore = false;
}
}
This is to detect when the user has scrolled to the end and fetch new set of images:
// ONSCROLLLISTENER
gridOfPhotos.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
int lastInScreen = firstVisibleItem + visibleItemCount;
if ((lastInScreen == totalItemCount) && !(loadingMore)) {
if (stopLoadingData == false) {
// FETCH THE NEXT BATCH OF FEEDS
new loadMorePhotos().execute();
}
}
}
});
And finally, this is how I fetch the next set of images:
private class loadMorePhotos extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... arg0) {
// SET LOADING MORE "TRUE"
loadingMore = true;
// INCREMENT CURRENT PAGE
current_page += 1;
// Next page request
URL = pagingURL;
try {
HttpClient hc = new DefaultHttpClient();
HttpGet get = new HttpGet(URL);
HttpResponse rp = hc.execute(get);
if (rp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
String queryAlbums = EntityUtils.toString(rp.getEntity());
// Log.e("PAGED RESULT", queryAlbums);
JSONObject JOTemp = new JSONObject(queryAlbums);
JSONArray JAPhotos = JOTemp.getJSONArray("data");
// IN MY CODE, I GET THE NEXT PAGE LINK HERE
getPhotos photos;
for (int i = 0; i < JAPhotos.length(); i++) {
JSONObject JOPhotos = JAPhotos.getJSONObject(i);
// Log.e("INDIVIDUAL ALBUMS", JOPhotos.toString());
if (JOPhotos.has("link")) {
photos = new getPhotos();
// GET THE ALBUM ID
if (JOPhotos.has("id")) {
photos.setPhotoID(JOPhotos.getString("id"));
} else {
photos.setPhotoID(null);
}
// GET THE ALBUM NAME
if (JOPhotos.has("name")) {
photos.setPhotoName(JOPhotos.getString("name"));
} else {
photos.setPhotoName(null);
}
// GET THE ALBUM COVER PHOTO
if (JOPhotos.has("picture")) {
photos.setPhotoPicture(JOPhotos
.getString("picture"));
} else {
photos.setPhotoPicture(null);
}
// GET THE ALBUM'S PHOTO COUNT
if (JOPhotos.has("source")) {
photos.setPhotoSource(JOPhotos
.getString("source"));
} else {
photos.setPhotoSource(null);
}
arrPhotos.add(photos);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void result) {
// get listview current position - used to maintain scroll position
int currentPosition = gridOfPhotos.getFirstVisiblePosition();
// APPEND NEW DATA TO THE ARRAYLIST AND SET THE ADAPTER TO THE
// LISTVIEW
adapter = new PhotosAdapter(Photos.this, arrPhotos);
gridOfPhotos.setAdapter(adapter);
// Setting new scroll position
gridOfPhotos.setSelection(currentPosition + 1);
// SET LOADINGMORE "FALSE" AFTER ADDING NEW FEEDS TO THE EXISTING
// LIST
loadingMore = false;
}
}
And this is the helper class to SET and GET the data collected from the queries above:
public class getPhotos {
String PhotoID;
String PhotoName;
String PhotoPicture;
String PhotoSource;
// SET THE PHOTO ID
public void setPhotoID(String PhotoID) {
this.PhotoID = PhotoID;
}
// GET THE PHOTO ID
public String getPhotoID() {
return PhotoID;
}
// SET THE PHOTO NAME
public void setPhotoName(String PhotoName) {
this.PhotoName = PhotoName;
}
// GET THE PHOTO NAME
public String getPhotoName() {
return PhotoName;
}
// SET THE PHOTO PICTURE
public void setPhotoPicture(String PhotoPicture) {
this.PhotoPicture = PhotoPicture;
}
// GET THE PHOTO PICTURE
public String getPhotoPicture() {
return PhotoPicture;
}
// SET THE PHOTO SOURCE
public void setPhotoSource(String PhotoSource) {
this.PhotoSource = PhotoSource;
}
// GET THE PHOTO SOURCE
public String getPhotoSource() {
return PhotoSource;
}
}
If you also want the adapter code, let me know. I use Fedor's Lazy Loading method in the adapter.
Phew. Hope any of this helps. If you have further question, feel free to ask. :-)
EDIT: Adapter code added:
public class PhotosAdapter extends BaseAdapter {
private Activity activity;
ArrayList<getPhotos> arrayPhotos;
private static LayoutInflater inflater = null;
ImageLoader imageLoader;
public PhotosAdapter(Activity a, ArrayList<getPhotos> arrPhotos) {
activity = a;
arrayPhotos = arrPhotos;
inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
imageLoader = new ImageLoader(activity.getApplicationContext());
}
public int getCount() {
return arrayPhotos.size();
}
public Object getItem(int position) {
return arrayPhotos.get(position);
}
public long getItemId(int position) {
return position;
}
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder;
View vi = convertView;
if(convertView == null) {
vi = inflater.inflate(R.layout.photos_item, null);
holder = new ViewHolder();
holder.imgPhoto = (ImageView)vi.findViewById(R.id.grid_item_image);
vi.setTag(holder);
} else {
holder = (ViewHolder) vi.getTag();
}
if (arrayPhotos.get(position).getPhotoPicture() != null){
imageLoader.DisplayImage(arrayPhotos.get(position).getPhotoPicture(), holder.imgPhoto);
}
return vi;
}
static class ViewHolder {
ImageView imgPhoto;
}
}
EDIT: Added steps to show Progress while loading:
Add a ProgressBar to you XML where you have the GridView right below it. Play around with the weight if it causes any problems.
<LinearLayout
android:id="#+id/linlaProgressBar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal" >
<ProgressBar
style="#style/Spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="2dp" />
</LinearLayout>
In your Java, declare the Linearlayout linlaProgressBar as Global and cast it in the onCreate() and set it's visibility as linlaProgressBar.setVisibility(View.GONE);
And in the onPreExecute() use it like this:
#Override
protected void onPreExecute() {
// SHOW THE BOTTOM PROGRESS BAR (SPINNER) WHILE LOADING MORE PHOTOS
linlaProgressBar.setVisibility(View.VISIBLE);
}
And finally add, this in the onPostExecute()
// HIDE THE BOTTOM PROGRESS BAR (SPINNER) AFTER LOADING MORE ALBUMS
linlaProgressBar.setVisibility(View.GONE);
You can, take a look to Using adapter Views and GridView from Android Documentation.
The most important thing is that the adapter call the method getView passing only the position of the entries showing on screen, and asking for different positions when user scrolls.
The easy way to do is download the required image on the getView method of your adapter with and AsyncTask.
There is an example
Talking from experience, it's tricky to achieve smooth scrolling (and overall responsiveness) while consuming memory reasonably.
It would be a good idea to look for existing solutions first, e.g., start here:
Lazy load of images in ListView
We ended up with a custom proprietary solution. It is a background thread that queues download requests and downloads and caches on the external storage only the images that are still visible. When a new image arrives, the view gets notified and decides when to notify the adapter to update.
It also saves the bandwidth, which was important in some cases.
I found IceMAN's answer very useful, but I also recommend avoid using two AsyncTasks and you can make this easily.
You need to create a universal method to fetch needed data, where you can make an if/else condition (as an example):
movies = fetchMovie.execute(sort).get();
if (movies == null) {
movieList = new ArrayList<>();
} else if (addMovies) {
movieList.addAll(movies);
} else {
movieList = movies;
}
addMovies is a boolean in your onScroll method.
In AsyncTask provide current page in query URL and voila - you made your code smaller :)
Related
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 method that add days in the date of the listview, the problem is when I scroll down and up again the values are changed, I have 2 kinds of date, so the method has to be called when the second kind appears in the list, but how can I use the method once so it doesn't keep adding each time I scroll it?
public class FaturasAdapter extends ArrayAdapter<Faturas> {
private Activity activity;
private LayoutInflater inflater;
private List<Faturas> faturasItens;
private Ferramentas mFerramentas;
private String entrada;
private String parcela = "";
private Map<Integer, Integer> intervaloMap;
private String data ="";
public FaturasAdapter(Activity activity, RealmList<Faturas> inItems) {
super(activity, R.layout.faturas_adapter, inItems);
this.activity = activity;
this.faturasItens = inItems;
this.mFerramentas = new Ferramentas();
this.intervaloMap = new HashMap<Integer, Integer>();
}
#Override
public int getCount() {
return faturasItens.size();
}
#Override
public Faturas getItem(int location) {
return faturasItens.get(location);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View vi = convertView;
final ViewHolder holder;
final Faturas mFaturas = faturasItens.get(position);
if (inflater == null)
inflater = (LayoutInflater) activity
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (convertView == null) {
vi = inflater.inflate(R.layout.faturas_adapter, null);
holder = new ViewHolder();
holder.numero = (TextView) vi.findViewById(R.id.numero);
holder.intervalo = (TextView) vi.findViewById(R.id.intervalo);
holder.valor = (TextView) vi.findViewById(R.id.valor);
holder.data = (TextView) vi.findViewById(R.id.data);
vi.setTag(holder);
} else {
holder = (ViewHolder) vi.getTag();
}
addIntervalo(mFaturas.getIntervalo(), mFaturas.getTipo());
data = mFerramentas.dataText(intervaloMap.get(mFaturas.getTipo()));
holder.intervalo.setText(String.valueOf(mFaturas.getIntervalo()));
if (mFaturas.getTipo() == Faturas.intervaloConstanteEntrada) {
entrada = "ENT - ";
holder.numero.setText(entrada + mFaturas.getOrder() + "/" + mFaturas.getQtParcelasEntrada());
holder.valor.setText(String.valueOf(mFaturas.getValor()) + " ");
} else {
parcela = "PAR - ";
holder.numero.setText(parcela + mFaturas.getOrder() + "/" + mFaturas.getQtParcela());
holder.valor.setText(String.valueOf(mFaturas.getValor()) + " ");
}
holder.data.setText(data);
return vi;
}
private void addIntervalo(int intervalo, int tipo) {
int intervaloSum = intervalo;
if (!intervaloMap.isEmpty()) {
if (intervaloMap.get(tipo) != null)
intervaloSum += intervaloMap.get(tipo);
}
intervaloMap.put(tipo, intervaloSum);
}
public List<Faturas> getfaturasItens() {
return faturasItens;
}
public void setData(List<Faturas> fat) {
this.faturasItens.addAll(fat);
this.notifyDataSetChanged();
}
public class ViewHolder {
TextView numero;
TextView intervalo;
TextView data;
TextView valor;
}
}
As I said in my comment I don't see why do you need to call the addIntervalo() method in the getView() method. The problem with this is that getView() will be called a lot as the user uses the ListView so you'll end up adding the same data again and again.
From your code it seems you just show the data calculated with addIntervalo()(I'm assuming each item will present its data relative to the total that you calculate for that type that you calculate with addIntervalo()) so you could simply calculate in advance the values and then in getView() simply use that.
// in the constructor you get the data so calculate the values
// iterating over the data
public FaturasAdapter(Activity activity, RealmList<Faturas> inItems) {
super(activity, R.layout.faturas_adapter, inItems);
this.activity = activity;
this.faturasItens = inItems;
this.mFerramentas = new Ferramentas();
this.intervaloMap = new HashMap<Integer, Integer>();
foreach(Faturas f : inItems) {
addIntervalo(f.getIntervalo(), f.getTipo());
}
}
You also have the setData() method where you update the data list so you also need to calculate the result of addIntervalo() for the new items that are about to be added to the adapter:
public void setData(List<Faturas> fat) {
// because you're adding the fat list to the current set of items
// simply calculate addIntervalo() for them to add their count to the total
foreach(Faturas f : fat) {
addIntervalo(f.getIntervalo(), f.getTipo());
}
this.faturasItens.addAll(fat);
this.notifyDataSetChanged();
}
In getView() remove the line:
addIntervalo(mFaturas.getIntervalo(), mFaturas.getTipo());
as you already calculated the values.
Oh, it's problem for base android list's widget to change view. You should know, that ListView and RecycleView caching and invalidating views per scrolling! So your views will be changed to default type!
RecycleView has solution, it's using several type from method getItemType(). But you work with ListView. Anyway! Better solution it's use special list widget which supports custom views and changing that views in anytime. For this task use LinkedListView!
I use Ion library to receive data from server. I have problem, that my code makes get request multiple times, so I have data duplication in my list.
My adapter code:
public class ArticleAdapter extends ArrayAdapter<Article> {
Future<List<Article>> loadingOfPrevious;
static class ArticleHolder {
ImageView articleImage;
TextView articleCaption;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ArticleHolder holder;
if (convertView == null) {
convertView = layoutInflater.inflate(R.layout.list_view_item, null);
holder = new ArticleHolder();
holder.articleImage = (ImageView) convertView.findViewById(R.id.articleImage);
holder.articleCaption = (TextView) convertView.findViewById(R.id.articleCaption);
convertView.setTag(holder);
} else {
holder = (ArticleHolder) convertView.getTag();
}
Article article = getItem(position);
holder.articleCaption.setText(article.getTitle());
Ion.with(holder.articleImage)
.placeholder(R.drawable.placeholder)
.error(R.drawable.default_article_image)
//.crossfade(true)
.load(article.getImageUrl());
// we're near the end of the list adapter, so load more items
if (position >= getCount() - 3) {
loadPrevious(getItem(getCount() - 1).getId());
}
return convertView;
}
private void loadPrevious(long id) {
// don't attempt to load more if a load is already in progress
if (loadingOfPrevious != null && !loadingOfPrevious.isDone() && !loadingOfPrevious.isCancelled()) {
return;
}
String url = "http://example.com/rest-api/"
url = url + "?id=" + id;
url = url + "&count=" + 30;
// This request loads a URL as JsonArray and invokes
// a callback on completion.
final String articleUrl = url;
loadingOfLatest = Ion.with(getContext())
.load(articleUrl)
.as(new TypeToken<List<Article>>() {
})
.setCallback(new FutureCallback<List<Article>>() {
#Override
public void onCompleted(Exception e, List<Article> result) {
// this is called back onto the ui thread, no Activity.runOnUiThread or Handler.post necessary.
if (e != null) {
Toast.makeText(getContext(), "Error.", Toast.LENGTH_LONG).show();
return;
}
// add the article
if (result != null) {
Collections.reverse(result);
for (int i = 0; i < result.size(); i++) {
add(result.get(i));
}
notifyDataSetChanged();
}
}
});
}
}
I think the problem is in this part:
// we're near the end of the list adapter, so load more items
if (position >= getCount() - 3) {
loadPrevious(getItem(getCount() - 1).getId());
}
The way I did this was to use an OnScrollListener on the ListView. When the user scrolls and firstVisibleItem + visibleItemCount is near your list limit, start your server data load.
Once the data load is complete, update your list adapter with the new data and call onNotifyDataSetChanged(). The list will then redisplay with your updated data.
You are right! It's because of this code.
if (position >= getCount() - 3) {
loadPrevious(getItem(getCount() - 1).getId());
}
According to your condition, "loadPrevious()" will be called twice.
"getView()" works to return one view for each time. So for the last two items, your condition is true and then call "loadPrevious()" twice.
So change and try like this
if (position >= getCount() - 2) {
loadPrevious(getItem(getCount() - 1).getId());
}
OR
recheck this code
if (loadingOfPrevious != null && !loadingOfPrevious.isDone() && !loadingOfPrevious.isCancelled()) {
return;
}
BTW, I want to suggest to try other ways, like using library, customize listview
Android Endless List
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.
I have two issues with my ListFragment:
I want to refresh the the ListFragment once I click a button which I define in the XML File.I initially load the Data of the DataAdapter within a AsyncTask in the TitlesFragment.
I have not found a way to create the code for the button which could access the AsyncTask - and refresh my TitlesFragment
On a different note: The Listfragment updates itself everytime I change the orientation of the phone, which is absolute not the desired behaviour.
public class ClosestPlaces extends FragmentActivity {
private static KantinenListe kantinen;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_ACTION_BAR);
setContentView(R.layout.kantinen_results);
}
/**
* This is the "top-level" fragment, showing a list of items that the
* user can pick. Upon picking an item, it takes care of displaying the
* data to the user as appropriate based on the currrent UI layout.
*/
public static class TitlesFragment extends ListFragment {
boolean mDualPane;
int mCurCheckPosition = 0;
private class BuildKantinen extends AsyncTask<String, Integer, KantinenListe> {
private KantinenListe kantinen;
#Override
protected KantinenListe doInBackground(String... params) {
try{
Gson gson = new Gson();
// SOAP Test
String NAMESPACE = "http://tempuri.org/";
String METHOD_NAME = "fullSyncGPS";
String SOAP_ACTION = "http://tempuri.org/IDatenService/fullSyncGPS";
String URL = "http://webserviceURL?wsdl";
SoapObject request = new SoapObject(NAMESPACE,METHOD_NAME);
PropertyInfo pi = new PropertyInfo();
request.addProperty("radius",10);
request.addProperty("lat", "14.089201");
request.addProperty("lng", "02.136459");
request.addProperty("von", "01.09.2011");
request.addProperty("bis", "01.09.2011");
SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
envelope.dotNet = true;
envelope.setOutputSoapObject(request);
HttpTransportSE androidHttpTransport = new HttpTransportSE(URL);
androidHttpTransport.call(SOAP_ACTION, envelope);
SoapPrimitive result = (SoapPrimitive)envelope.getResponse();
String resultData = result.toString();
resultData = "{\"meineKantinen\":"+resultData+"}";
this.kantinen = gson.fromJson(resultData, KantinenListe.class);
Log.i("test", "blubber" );
}
catch(Exception e)
{
e.printStackTrace();
}
return this.kantinen;
}
#Override
protected void onPostExecute(KantinenListe result) {
// populate the List with the data
Log.i("test", "postexecute" );
setListAdapter( new MenuAdapter(getActivity(), R.layout.simple_list_item_checkable_1, kantinen.getMeineKantinen()));
}
}
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
new BuildKantinen().execute("test");
if (savedInstanceState != null) {
// Restore last state for checked position.
mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("curChoice", mCurCheckPosition);
}
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
showDetails(position);
}
/**
* Helper function to show the details of a selected item, either by
* displaying a fragment in-place in the current UI, or starting a
* whole new activity in which it is displayed.
*/
void showDetails(int index) {
mCurCheckPosition = index;
// Otherwise we need to launch a new activity to display
// the dialog fragment with selected text.
Log.i("Test",Integer.toString(index));
Intent intent = new Intent();
intent.setClass(getActivity(), BeAPartner.class);
startActivity(intent);
}
}
public static class MenuAdapter extends ArrayAdapter {
private LayoutInflater mInflater;
private List<Kantine> items;
private Context context;
public MenuAdapter(Context context, int textViewResourceId, List<Kantine> items) {
super(context, textViewResourceId);
mInflater = LayoutInflater.from(context);
this.items = items;
this.context = context;
}
#Override
public int getCount() {
return items.size();
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.menu_row, parent, false);
holder = new ViewHolder();
holder.color = (TextView) convertView.findViewById(R.id.color);
holder.title = (TextView) convertView.findViewById(R.id.detail);
holder.subdetail = (TextView) convertView.findViewById(R.id.subdetail);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
// Fill in the actual story info
Kantine s = items.get(position);
s.setName( Html.fromHtml(s.getName()).toString() );
if (s.getName().length() > 35)
holder.title.setText(s.getName().substring(0, 32) + "...");
else
holder.title.setText(s.getName());
Log.i("display", "Here I am");
return convertView;
}
}
static class ViewHolder {
TextView color;
TextView title;
TextView subdetail;
}
Well to stop the activity from being restarted you can just set android:configChanges attribute on the activity that is running the fragment.
android:configChanges="orientation"
Setting that tells the system to not restart the activity on an orientation change just to change the orientation.
As for the button, set your click listener in XML by using the attribute android:onClick="myFunction". Then in your fragment define this function:
public void myFunction(View v)
{
new myAsync.execute('test');
}
When you change the phone orientation, it restarts the activity. Anything you need to be persistent you'll need to save in onSaveInstanceState(Bundle savedInstanceState) and restore with onRestoreInstanceState(Bundle savedInstanceState).
As far as updating the data when clicking a button, try calling notifyDataSetChanged() on the adapter. If that doesn't work, you'll likely just have to run your asynctask again.
Ok, I thought it's best practice to post a possible solution in a new answer - for the obvious reasons of readability:
public void reload(View v)
{
if (getSupportFragmentManager().findFragmentById(R.id.titles) != null) {
Fragment titles = (ListFragment)getSupportFragmentManager().findFragmentById(R.id.titles);
//recreate the fragment
titles.onActivityCreated(null);
}
}
Does exactly what I want ( reload the data )! However ... I think the memory footprint of this solution might be bad.