I'm pretty new to Android development, I feel like I have a relatively simple question here and have managed to tie down the more complex parts but overlook the more simple bits. I've setup an ImageAdapter class which handles displaying images into a GridView in another one of my Fragments. Originally I was following a tutorial that simply displayed a list of items in an Array.
I'm using an AsyncTask to populate an ArrayList, and then converting the ArrayList to a standard array that Picasso can deal with when displaying content.
My problem is that the AsyncTask section of my ImageAdapter is just not getting executed, thus my imageArr[] that Picasso uses is just remaining empty.
How can I make sure that the AsyncTask section of my Adapter is actually executed?
I've tried this, but it just doesn't seem to be working and I think I'm a little bit off...
public void onCreate() {
new GetProjects().execute();
}
I've attached my code bellow, any help would be really appreciated!
Note; ServiceHandler is just retrieving the data at the URL and then turning it into a string which can be parsed.
public class ImageAdapter extends BaseAdapter {
//JSON URL
private static String url = "www.myjsonsourceurl.com";
//JSON NODES
private static final String TAG_LOGO = "logopath";
ArrayList<String> imageUrls = new ArrayList<String>();
String[] imageArr = imageUrls.toArray(new String[imageUrls.size()]);
private Context mContext;
public ImageAdapter(Context c) {
mContext = c;
}
public int getCount() {
return imageArr.length;
}
public Object getItem(int position) {
return null;
}
public long getItemId(int position) {
return 0;
}
public void onCreate() {
new GetProjects().execute();
}
// create a new ImageView for each item referenced by the Adapter
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) {
// if it's not recycled, initialize some attributes
imageView = new ImageView(mContext);
imageView.setLayoutParams(new GridView.LayoutParams(185, 185));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(3, 3, 3, 3);
} else {
imageView = (ImageView) convertView;
}
Picasso.with(mContext).setIndicatorsEnabled(true);
Picasso.with(mContext).setLoggingEnabled(true);
Picasso.with(mContext).load(imageArr[position]).placeholder(R.drawable.ajaxloader).error(R.drawable.imageunavailable).into(imageView);
return imageView;
}
// references to our images
//ASYNC task to get json by making HTTP call
public class GetProjects extends AsyncTask<Void, Void, Void> {
#Override
protected void onPreExecute() {
super.onPreExecute();
// Nothing right now
}
#Override
protected Void doInBackground(Void... arg0) {
// Creating service handler class instance
ServiceHandler sh = new ServiceHandler();
// Making a request to url and getting response
String jsonStr = sh.makeServiceCall(url, ServiceHandler.GET);
Log.d("Response: ", "> " + jsonStr);
if (jsonStr != null) {
try {
JSONArray json = new JSONArray(jsonStr);
// looping through All Applications
for (int i = 0; i < json.length(); i++) {
JSONObject p = json.getJSONObject(i);
String logopath = p.getString(TAG_LOGO);
imageUrls.add(logopath);
}
} catch (JSONException e) {
e.printStackTrace();
}
} else {
Log.e("ServiceHandler", "Couldn't get any data from the url");
}
return null;
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
}
}
}
You need to call in activity or fragment where list is defined not in adapter like this. move asynctask class to your activity and call it from there.
AsyncTask gives methods
onPreExecute() and onPostExecute() where you can toast message that task is started or completed. And you should call setAdapter() in onPostExecute() of class.
There`s no onCreate method for BaseAdapter which you can override. Execute your code
new GetProjects().execute();
either in contructor or call your onCreate function (I suggest changing its name) manualy from outside of the adapter.
Related
Is there a way to overwrite the "week_kg" variable with the async task in the oncreateview method?
The async task overwrites the variable, but the View is already created and doesn't contain the new value.
It's a Rest Communications with a Node.js Server and a Mysql database.
public class Feedback extends Fragment {
private RecyclerView mRecylcerView;
private RecyclerView.Adapter mAdapter;
private RecyclerView.LayoutManager mLayoutManager;
public String username = "ilovenature";
public double week_kg;
String resturl = "http://192.168.178.199:3000/"; //ip
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_feedback, container, false);
new HttpGet().execute(resturl + "user/" + username);
ArrayList < Feedback_inhalt > feedback_inhalt = new ArrayList<>();
feedback_inhalt.add(new Feedback_inhalt(R.mipmap.ic_plastic_color, "Verwertungskosten", "kg eingespart", week_kg));
mRecylcerView = view.findViewById(R.id.feedback_view);
mRecylcerView.setHasFixedSize(true);
mLayoutManager = new LinearLayoutManager(this.getActivity());
mAdapter = new FeedbackAdapter(feedback_inhalt);
mRecylcerView.setLayoutManager(mLayoutManager);
mRecylcerView.setAdapter(mAdapter);
return view;
}
public class HttpGet extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String...strURLs) {
URL url;
String output;
HttpURLConnection conn;
try {
url = new URL(strURLs[0]);
conn = (HttpURLConnection) url.openConnection();
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder sb = new StringBuilder();
while ((output = br.readLine()) != null) {
sb.append(output);
}
return sb.toString();
} catch (IOException e) {
return "Unable to retrieve web page. URL may be invalid.";
}
}
#Override
protected void onPostExecute(String result) {
try {
JSONArray jsonArray = new JSONArray(result);
JSONObject jsonObject = jsonArray.getJSONObject(0);
week_kg = jsonObject.getDouble("wochenkilogramm");
} catch (JSONException e) {
e.printStackTrace();
}
}
}
}
The onCreateView() method is only called once when the view is created.
However, you can refresh your RecyclerView once your AsyncTask has finished executing. To achieve this, add the newly fetched data to your ArrayList feedback_halt and then call notifyDataSetChanged() on your adapter.
When you call notifyDataSetChanged(), the views will be refreshed based on the data in your ArrayList.
So, inside onPostExecute() after you have set the value of week_kg:
feedback_inhalt.add(new Feedback_inhalt(R.mipmap.ic_plastic_color, "Verwertungskosten", "kg eingespart", week_kg));
mAdapter.notifyDatSetChanged()
Finally, the notifyDataSetChanged() method should only be called from a UI thread but the onPostExecute() method runs on the UI thread by design so you are good to go :-)
I have a gallery application that is parsing the Reddit API and populating a recyclerview list with images. Each page contains 25 pictures from that subreddit. But this code is making duplicate data calls which puts the same pages in the gallery when you scroll 2 or 3 times before it get the next page. How would I be able to fix this problem?
public class DesktopGalleryFragment extends Fragment{
private int count;
private RecyclerView mDesktopRecyclerView;
private List<DesktopItems> mList = new ArrayList<>();
private String after, nullString;
private Parcelable recyclerViewState;
public String getAfter(String s){
return this.after = s;
}
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setRetainInstance(true);
new FetchItemsTask().execute(nullString);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
mDesktopRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener(){
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState){
PhotoAdapter adapter = (PhotoAdapter) recyclerView.getAdapter();
int lastPostion = adapter.getLastBoundPosition();
GridLayoutManager layoutManager = (GridLayoutManager) recyclerView.getLayoutManager();
int loadBufferPosition = 1;
if (lastPostion >= adapter.getItemCount() - layoutManager.getSpanCount() - loadBufferPosition){
new FetchItemsTask().execute(after);
}
}
});
return v;
}
private class FetchItemsTask extends AsyncTask<String, Void, List<DesktopItems>>{
#Override
protected List<DesktopItems> doInBackground(String... params){
return new RedditParser().fetchItems(params[0]);
}
#Override
protected void onPostExecute(List<DesktopItems> items){
if (count > 1){
getAfter(DesktopItems.getAfter());
mList.addAll(items);
mDesktopRecyclerView.getAdapter().notifyDataSetChanged();
} else{
mList = items;
recyclerViewState = mDesktopRecyclerView.getLayoutManager().onSaveInstanceState();
mDesktopRecyclerView.getLayoutManager().onRestoreInstanceState(recyclerViewState);
setupAdapter();
}
count++;
}
}
}
RedditParser.java
public List<DesktopItems> fetchItems(String after)
{
List<DesktopItems> items = new ArrayList<>();
int count = 0;
int counter = count + 25;
try
{
String url = Uri.parse("https://www.reddit.com/r/battlestations/hot.json")
.buildUpon()
.appendQueryParameter("after", after)
.build()
.toString();
String jsonString = getUrlString(url);
Log.i(TAG, "Received JSON: " + jsonString);
JSONObject jsonBody = new JSONObject(jsonString);
parseItems(items, jsonBody);
return items;
}
You are getting overlapping calls to FetchItemsTask due to how onScrollStateChanged works. If you swipe up and let things settle, here is how onScrollStateChanged is invoked:
onScrollStateChanged: SCROLL_STATE_DRAGGING
onScrollStateChanged: SCROLL_STATE_SETTLING
onScrollStateChanged: SCROLL_STATE_IDLE
This is what you would expect. If, however, you swipe up and then swipe up again before the view settles, this is what happens:
onScrollStateChanged: SCROLL_STATE_DRAGGING
onScrollStateChanged: SCROLL_STATE_SETTLING
onScrollStateChanged: SCROLL_STATE_DRAGGING
onScrollStateChanged: SCROLL_STATE_SETTLING
onScrollStateChanged: SCROLL_STATE_IDLE
This may seem surprising at first, but it makes sense if you think about it.
You are also not doing any checks for the newState in onScrollStateChanged.
If you are trying to implement an endless RecyclerView try searching for "endless recyclerview". There are many good resources on the web.
I've a RecyclerView, Async Task, Json File .
What i want :
Get the Json and add an item for every loop .
My problem :
I'm getting the json, and looping, But the "List.add(Item)" isn't adding any line, So the recycler is empty .
Code :
Adapter RcAdapter;
public static List<CatItem> Items;
String DATA_URL;
#SuppressLint("InflateParams")
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinLayout = (LinearLayout) inflater.inflate(R.layout.cat_recycler,
container, false);
setHasOptionsMenu(true);
DATA_URL = this.getArguments().getString(MainActivity.DATA);
initListViews();
return LinLayout;
}
private void initListViews() {
List<CatItem> rowListItem = getAllItemList();
LinearLayoutManager gridLayout = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false);
RecyclerView RcView = (RecyclerView) LinLayout.findViewById(R.id.cat_rv);
RcView.setHasFixedSize(true);
RcView.setLayoutManager(gridLayout);
RcAdapter = new Adapter(getActivity(), rowListItem);
RcView.setAdapter(RcAdapter);
}
private List<CatItem> getAllItemList() {
Items = new ArrayList<>();
new GetDevices().execute(DATA_URL);
return Items;
}
class GetDevices extends AsyncTask<String, String, String> {
#Override
protected void onPreExecute() {
super.onPreExecute();
pDialog = new ProgressDialog(getActivity());
pDialog.setMessage("Loading Devices, Please Wait..");
pDialog.setIndeterminate(false);
pDialog.setCancelable(true);
pDialog.show();
}
protected String doInBackground(String... params) {
return JsonUtils.getJSONString(params[0]);
}
#Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
if(pDialog != null)
pDialog.dismiss();
if(result == null){
Toast.makeText(getActivity(),"Failed To Fetch Data, Please Refresh",Toast.LENGTH_LONG).show();
} else {
try {
JSONObject MainJSON = new JSONObject(result);
JSONArray DevicesArrays = MainJSON.getJSONArray("roms_center");
for (int i = 0; i < DevicesArrays.length() ; i++){
CatItem Item = new CatItem();
JSONObject first = DevicesArrays.getJSONObject(i);
Log.LogInfo("I'm In Position " + i);
Item.setDevice_Name(first.getString("device_name"));
Item.setTotal_Downloads(first.getInt("roms_count"));
Item.setDevice_ID(first.getInt("device_id"));
Items.add(Item);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
What's the problem ?
Also, In my holder i've implemented OnClick, and i want to get something from the pressed position, I'm using the following way :
Main.Items.get(getLayoutPosition()).getDevice_ID()
As you can see, I'm getting the static List, Any better way to achieve this ?
The program flow between these two lines is not what you think it is.
new GetDevices().execute(DATA_URL);
return Items;
GetDevices is an AsyncTask, which means it will run asynchronously.
The return statement in the next line will not wait for the async task to finish, and it returns null because async task takes time to finish.
After you've added an item to your list you have to tell your adapter about it. So you could do it in two ways:
RcAdapter.notifyItemInserted(Items.size() - 1);
or
RcAdapter.notifyDataChanged();
I am trying to populate data from my main activity using the adapter below. When i run the activity the screen remains blanked. I believe it has to do with the ArrayList which is null perhaps. Can someone tell me why my data is not being displayed. am on this bug for three days now :/
public class CopyOfSecondWheelAdapter extends AbstractWheelTextAdapter {
ArrayList<convertor_pst> PostList = new ArrayList<convertor_pst>();
public ImageLoader imageLoader;
Convertor main;
public CopyOfSecondWheelAdapter(Context context) {
super(context, R.layout.count_layout, NO_RESOURCE);
setItemTextResource(R.id.country_name);
}
#Override
public View getItem(int index, View cachedView, ViewGroup parent) {
View view = super.getItem(index, cachedView, parent);
ImageView img = (ImageView) view.findViewById(R.id.flag);
imageLoader.DisplayImage(PostList.get(index).getDevise(), img);
System.out.println("get item count:"+getItemsCount() );
TextView text = (TextView)view.findViewById(R.id.lib);
text.setText(PostList.get(index).getQuotite());
return view;
}
#Override
public int getItemsCount() {
return PostList.toArray().length;
}
#Override
protected CharSequence getItemText(int index) {
return PostList.get(index).getDevise().toString();
}
}
UPDATE:
In my Main class i have already an
ArrayList<convertor_pst> PostList = new ArrayList<convertor_pst>();
which is populated.
Here is my main class that is my convertor.class
ArrayList<convertor_pst> PostList = new ArrayList<convertor_pst>();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.convertor);
context = this;
text_devise_two = (TextView)findViewById(R.id.text_spacetwo);
final WheelView country = (WheelView) findViewById(R.id.country);
country.setVisibleItems(10);
country.setViewAdapter(new FirstWheelAdapter(this));
edt_validate = (EditText)findViewById(R.id.edt_validate);
current_type_loc = (TextView)findViewById(R.id.current_type_loc);
refresh_header= (TextView)findViewById(R.id.refresh_header);
//set current time
Calendar c = Calendar.getInstance();
SimpleDateFormat df = new SimpleDateFormat("dd/MMM/yyyy");
String formattedDate = df.format(c.getTime());
refresh_header.setText(getResources().getString(R.string.mise_a_jour)+" "+formattedDate);
image_one = (ImageView)findViewById(R.id.image_one);
image_two = (ImageView)findViewById(R.id.image_two);
final WheelView currency = (WheelView) findViewById(R.id.currency);
currency.setVisibleItems(10);
currency.setViewAdapter(new CopyOfSecondWheelAdapter(this));
country.addChangingListener(new OnWheelChangedListener() {
#Override
public void onChanged(WheelView wheel, int oldValue, int newValue) {
if (!scrolling) {
}
}
});
country.addScrollingListener( new OnWheelScrollListener() {
#Override
public void onScrollingStarted(WheelView wheel) {
scrolling = true;
}
#Override
public void onScrollingFinished(WheelView wheel) {
scrolling = false;
//1.
wheelSelector(country.getCurrentItem());
}
});
currency.addScrollingListener( new OnWheelScrollListener() {
#Override
public void onScrollingStarted(WheelView wheel) {
scrolling = true;
}
#Override
public void onScrollingFinished(WheelView wheel) {
scrolling = false;
//1.
secondWheel(currency.getCurrentItem());
}
});
country.setCurrentItem(1);
currency.setCurrentItem(3);
new loadingTask().execute();
}
/*1. Change currency */
public void wheelSelector (int id){
if (id==0){
current_type_loc.setText("EUR");
image_one.setBackgroundResource(R.drawable.eur);
}else {
current_type_loc.setText("USD");
image_one.setBackgroundResource(R.drawable.usd);
}
}
class loadingTask extends AsyncTask<Void, Void,Void> {
#Override
protected void onPreExecute() {
// TODO Auto-generated method stub
pd = ProgressDialog.show(Convertor.this, "", "Chargement en cours..", true);
super.onPreExecute();
}
#Override
protected void onPostExecute(Void result) {
// TODO Auto-generated method stub
super.onPostExecute(result);
pd.dismiss();
doc = Jsoup.parse(getxml,"", Parser.xmlParser());
taux = doc.select("taux");
for (int i = 0; i < taux.size(); i++) {
PostList.add(new convertor_pst(taux.get(i).getElementsByTag("devise").text().toString(),
taux.get(i).getElementsByTag("dateCours").text().toString(),
taux.get(i).getElementsByTag("libelle").text().toString(),
taux.get(i).getElementsByTag("quotite").text().toString(),
taux.get(i).getElementsByTag("fixing").text().toString()));
}
}
#Override
protected Void doInBackground(Void... params) {
// TODO Auto-generated method stub
envelope =
"soap content"
String requestEnvelope=String.format(envelope, "28-03-2013","true");
getxml = Util.CallWebService(URL,SOAP_ACTION,requestEnvelope);
System.out.println(getxml);
return null;
}
}
public void secondWheel(int index){
text_devise_two.setText(PostList.get(index).getDevise());
edt_validate.setText(" "+PostList.get(index).getFixing());
}
/*
*
* (non-Javadoc)
* #see android.app.Activity#onPause()
* check if activity go to background
*/
#Override
protected void onPause() {
// TODO Auto-generated method stub
super.onPause();
if (Util.isApplicationBroughtToBackground(Convertor.this)==true){
startActivity(new Intent(Convertor.this,Compte.class));
}
}
}
This is the original wheel adapter class
public class CopyOfSecondWheelAdapter extends AbstractWheelTextAdapter {
ArrayList<convertor_pst> PostList;
public ImageLoader imageLoader;
// Countries names
private String countries[] =
new String[] {"EUR", "USD","EUR", "USD","EUR", "USD","EUR", "USD","EUR", "USD","EUR", "USD"};
// Countries flags
private int flags[] = new int[] {R.drawable.eur, R.drawable.usd,R.drawable.eur, R.drawable.usd,R.drawable.eur, R.drawable.usd,R.drawable.eur, R.drawable.usd,R.drawable.eur, R.drawable.usd,R.drawable.eur, R.drawable.usd};
/**
* Constructor
*/
Convertor main;
public CopyOfSecondWheelAdapter(Context context) {
super(context, R.layout.count_layout, NO_RESOURCE);
setItemTextResource(R.id.country_name);
}
#Override
public View getItem(int index, View cachedView, ViewGroup parent) {
View view = super.getItem(index, cachedView, parent);
ImageView img = (ImageView) view.findViewById(R.id.flag);
img.setImageResource(flags[index]);
TextView text = (TextView)view.findViewById(R.id.lib);
text.setText("code");
return view;
}
#Override
public int getItemsCount() {
return countries.length;
}
#Override
protected CharSequence getItemText(int index) {
return countries[index];
}
}
As far as I understand
currency.setViewAdapter(new CopyOfSecondWheelAdapter(this));
this line creates the adapter, but you fill it up at this line :
new loadingTask().execute();
which is after, so you must call
yourAdapter.notifyDataSetChanged();
on your adapter to update the data.
Android Developer Help says
notifyDataSetChanged()
Notifies the attached observers that the
underlying data has been changed and any View reflecting the data set
should refresh itself.
So in your case you must
create an adapter (yourAdapter = new CopyOfSecondWheelAdapter ....)
assign it with the setViewAdater (WheelView.setViewAdapter(yourAdapter))
in the "postExecute" of your async task, do a call with yourAdapter.notifyDataSetChanged();
By the way, I am not sure to understand what you are doing, but in case you need to have a set of data displayed at two different locations, you don't need to duplicate (create a copy). The two list display can share the same adapter.
UPDATE
You have done an update to your question and I answer to that update :
In the original adapter the countries are not loaded in the async task. So when you assign the adapter, the display show the correct values because they are present in the adapter at the moment you assign it.
In your case, you load the values in the async task. When you create the adapter it is empty and you assign it empty, so the display shows an empty list. You should notify your display of the data change.
So in the original, no need to notify as the data is the correct one at the time of assignment. In your case you have to implement a notifyDataSetChanged(). Or change the type of adapter you are extending.
If I see it correctly, you have 2 times a variable name PostList which confuses you. One is created in your activity and one in your adapter and ass you call add() to the variable of your activity, the list in your adapter never gets the items.
Create a setter for the list in your adapter and set the list in your onPostExecute().
public class PageActivity extends Activity {
private int numPages = 31;
private TouchImageView[] imageViews = new TouchImageView[numPages];
private String URL = "http://www.smbc-comics.com/comics/201108";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewPager viewPager = new ViewPager(this);
for (int i = 0; i < numPages; i++) {
imageViews[i] = new TouchImageView(this);
imageViews[i].setBackgroundResource(R.drawable.blank);
imageViews[i].setMaxZoom(4f);
}
setContentView(viewPager);
ImagePagerAdapter adapter = new ImagePagerAdapter();
viewPager.setAdapter(adapter);
viewPager.setOffscreenPageLimit(2);
}
private class ImagePagerAdapter extends PagerAdapter {
#Override
public int getCount() {
return numPages;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == ((TouchImageView) object);
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Context context = PageActivity.this;
String pageURL = URL;
if (imageViews[position].getDrawable() == null) {
ImageFetcher imagefetcher = new ImageFetcher();
imagefetcher.execute(
pageURL + String.format("%02d", position+1) + ".gif",
String.valueOf(position+1));
}
((ViewPager) container).addView(imageViews[position], 0);
return imageViews[position];
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
((ViewPager) container).removeView((TouchImageView) object);
imageViews[position].removeCallbacks(null);
}
}
public class ImageFetcher extends AsyncTask<String, Integer, Drawable> {
int fillthisPos;
public Drawable doInBackground(String... urls) {
try {
InputStream is = (InputStream) new URL(urls[0]).getContent();
fillthisPos = Integer.parseInt(urls[1]);
Drawable d = Drawable.createFromStream(is, "src name");
return d;
} catch (Exception e) {
return null;
}
}
#Override
protected void onPostExecute(Drawable result) {
super.onPostExecute(result);
imageViews[fillthisPos].setImageDrawable(result);
result = null;
}
}
}
I am trying to create an image viewer which can load images from given URLs.The code above implement this using
TouchImageView for touch handling on single image.
ViewPager and PageAdapter to let user move to next page.
Asyn Tasks to download the image when a page is instantiated.
When i have downloaded some images , the app crashes. I checked from DDMS/MAT that the Bitmaps for the images are not being freed . It is because the views i am destroying are not being collected by GC as they are being still referenced somewhere.
I was wondering if their is some way to explicitly free up the memory in such cases? I tried removing all references from my code but it still doesn't release the memory.
PS: excuse me for putting up the code first , i tried to move it down but the formatting gets messed up.
You have no need to keep an array of ImageFetcher tasks. By doing this, you are maintaining references to the loaded drawables. Try eliminating the array from your code. The AsyncTasks will run to completion without you needing to maintain references to them.