i'm trying to download a list of cases from a server and then populate an arraylist to use it in my RecyclerView Adapter but every time i try to populate the array list from asynctask i can print the data in every single step from the populating bu not from the Arraylist i'm using (i'm trying to store in an arrayList that contains objects of the type Case which i created)
this is the part of the code with the problem
this is my AsyncTask...
`
public class downloadingNewCases extends AsyncTask<Void,Void ,Boolean>{
private String userid ;
private String state ;
public downloadingNewCases(String userid ,String state ){
this.userid = userid ;
this.state = state ;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
Log.d(AppController.DEBUG, "download New Cases Started");
}
#Override
protected Boolean doInBackground(Void... params){
Log.d(AppController.DEBUG , "user id in the get cases link.." + userid);
String url = AppController.ApiUrl + "GetCases?UserName="+ userid +"&stat="+state;
Log.d(AppController.DEBUG_LINK,url);
try {
JsonArrayRequest request = new JsonArrayRequest(url, new Response.Listener<JSONArray>() {
#Override
public void onResponse(JSONArray response) {
Log.d("TEST", String.valueOf(response.length()));
for (int i = 0; i < response.length(); i++) {
try {
Log.d("TEST","downloading data" );
JSONObject object = response.getJSONObject(i);
JSONArray specs = object.getJSONArray("Spe");
ArrayList<Specialities> spe = new ArrayList<>();
for (int j = 0; j < specs.length(); j++) {
Log.d("TEST",specs.getJSONObject(j).getString("SName") );
spe.add(new Specialities(null, specs.getJSONObject(j).getString("SName")));
}
Case ca = new Case(object.getString("ID")
, object.getString("Serial")
, object.getString("Name")
, object.getString("gender")
, object.getString("Age")
, object.getString("NatioanlityID")
, object.getString("HealthProblem")
, object.getString("CityID")
, object.getString("Problem")
, object.getString("Date")
, object.getString("Status")
, spe);
Log.d("TEST",ca.toString());
cases.add(ca);
spe.clear();
} catch (JSONException e) {
e.printStackTrace();
}
}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError volleyError) {
Log.d(AppController.DEBUG , volleyError.toString());
}
});
request.setRetryPolicy(new DefaultRetryPolicy(
0,
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
MySingleton.getmInstance(getActivity()).addToRequestQueue(request);
}catch (Exception e){
Log.d(AppController.DEBUG,"error in the asynctask in the new cases freagment... ");
Log.d(AppController.DEBUG ,e.toString());
}
Log.d(AppController.DEBUG, "Cases size ::" + cases.size());
return true;
}
#Override
protected void onPostExecute(Boolean bool) {
super.onPostExecute(bool);
/* if(listener != null){
listener.getCases(cases);
}*/
adapter.notifyDataSetChanged();
// Log.d(AppController.DEBUG, "new Cases Downloaded...");
}
}
`
(I'm sure that the adapter and recycler view code is correct because I could use the same code now but when I came to test it again it did not work anymore and I can't remember if I changed some thing in it or not - I think not)..
this is my onViewCreate Method .
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.doctor_new_cases_fragment,container,false);
Log.d(AppController.DEBUG_SHARED,"userID in the new case activity ..." + userID);
SharedPreferences prefs = getActivity().getSharedPreferences(AppController.PREFERENCES_NAME , Context.MODE_PRIVATE);
userID = prefs.getString(DoctorProfileSubmitActivity.Shared_userid,null) ;
downloadingNewCases tast = new downloadingNewCases(userID ,"");
tast.execute();
recyclerView = (RecyclerView)view.findViewById(R.id.recyclervew_new_cases);
adapter = new CasesAdapter(view.getContext(), cases);
recyclerView.setLayoutManager(new LinearLayoutManager(view.getContext()));
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setAdapter(adapter);
ViewGroup parent = (ViewGroup) view.getParent();
if (parent != null){
parent.removeView(view);
}
return view;
}
and finally the Array list is declared as an instance variable of the class (the class extends Fragment + it is a fragment that I'm using in my view pager as a tab layout for my tabbed Activity .)
here's the ArrayList declaration
private final ArrayList<Case> cases = new ArrayList<>();
Since you say you can print the data every step of the way, the problem is probably not related to your data. The problem might be your Arraylist of case And your CaseAdapter.
Let's try a simple test, instead of filling your adapter with case objects, create a new ArrayList of string and fill it with object.getString("name") in your on response. Then change your oncreateview to display this list of names using a normal string arrayadapter.
If you can successfully display the names, the it means the bug is in your case and caseadapter, you would have to show more code for us to help.
sorry guys it was not worth it to add a question on the site right away
i fixed the problem . (it appears that my problem was here)
Case ca = new Case(object.getString("ID")
, object.getString("Serial")
, object.getString("Name")
, object.getString("gender")
, object.getString("Age")
, object.getString("NatioanlityID")
, object.getString("HealthProblem")
, object.getString("CityID")
, object.getString("Problem")
, object.getString("Date")
, object.getString("Status")
, spe);
i accidentally changed object.getString("Gender") to object.getString("gender")
which caused me a JSONException .
i fixed that and everything is working again .
thanks for your help and i'm sorry if i rushed to ask the question without a proper debugging session first .
Related
In my application I have draft messages which I can edit. Some of my drafts include attachments which I try to send to my edit activity and show it at recyclerview. In general I have managed to send my string arraylist and get it at my activity. But I can't show my attached files, especially their names at the recyclerview. I tried to make smth like that:
adapter.notifyDataSetChanged();
but it didn't help me.
So, firstly I get from my message names of attached files:
file_name = Objects.requireNonNull(response.body()).getAttachesNames();
then put this names into arraylist:
nameList = new ArrayList<>(Arrays.asList(file_name));
such result I can see in my logs:
W: [eZV9f.jpg, index.html]
and then I send my list via intent to another activity:
intent2.putStringArrayListExtra("attached_files", (ArrayList<String>) nameList);
receiving data from intent:
Intent intent = getIntent();
extras = intent.getExtras();
if (extras != null) {
if (extras.containsKey("attached_files")) {
draft_files = getIntent().getStringArrayListExtra("attached_files");
Log.w("MY_TAG", String.valueOf(draft_files));
}
}
results from logcat:
W: [eZV9f.jpg, index.html]
initialising of my adapter and recyclerview:
adapter = new AttachedFileAdapter(mNames);
recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(WriteResponseMess.this, LinearLayoutManager.VERTICAL, false));
and then I try to get single element from this list and add to my ArrayList<>:
for (int i = 0; i < draft_files.size(); i++) {
mNames.addAll(Collections.singleton(draft_files.get(i)));
adapter.notifyDataSetChanged();
recyclerView.setAdapter(adapter);
Log.w("MY_TAG", draft_files.get(i));
Log.w("MY_TAG", String.valueOf(mNames));
}
all previous pieces of code are used at my onCreate() method and as a result of all these actions I would like to see income data from another activity. Sometimes I managed to do it, but one element contained all income data and looked like this:
[eZV9f.jpg, index.html]
and it was wrong for me. I try to create the list which will contain all elements separately. I also tried to use some info from this link which is connected with Collections, but I didn't manage to reach the goal of my task. In general I'm sure that the solution is very simple but I can't see it.
update
all my activity class has more than 1000 lines and I will share all code which is connected with adding attachments and show attached data at my writing form:
Here is my dialog for getting directory list and choosing some files:
#Override
protected Dialog onCreateDialog(int id) {
Dialog dialog = null;
switch (id) {
case CUSTOM_DIALOG_ID:
dialog = new Dialog(WriteResponseMess.this, android.R.style.Theme_DeviceDefault_Light_NoActionBar_Fullscreen);
dialog.setContentView(R.layout.dialog_layout);
dialog.setCanceledOnTouchOutside(true);
Toolbar toolbar = dialog.findViewById(R.id.toolbar_d);
toolbar.setTitle("Add a new file.");
textFolder = dialog.findViewById(R.id.folder);
buttonUp = dialog.findViewById(R.id.up);
buttonUp.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
ListDir(curFolder.getParentFile());
}
});
dialog_ListView = dialog.findViewById(R.id.dialoglist);
final Dialog finalDialog1 = dialog;
dialog_ListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#RequiresApi(api = Build.VERSION_CODES.O)
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
File selected = new File(curFolder, fileList.get(position));
if (selected.isDirectory()) {
ListDir(selected);
}
if (selected.isFile()) {
if (array.size() == 0) {
array = uploadFiles(array, selected.getName(), convertFileToString(selected.getPath()));
adapter.notifyDataSetChanged();
getImages();
} else {
if (array.toString().contains(selected.getName())) {
Toast.makeText(WriteResponseMess.this, R.string.attaching_message, Toast.LENGTH_SHORT).show();
adapter.notifyDataSetChanged();
getImages();
} else {
array = uploadFiles(array, selected.getName(), convertFileToString(selected.getPath()));
adapter.notifyDataSetChanged();
getImages();
}
}
finalDialog1.dismiss();
ms.setArray(array);
}
}
});
break;
}
return dialog;
}
and method for showing attached list, and in general this method works when I create a new message and attach a new file, it works fine:
private void getImages() {
mNames.clear();
adapter.notifyDataSetChanged();
for (int i = 0; i < array.size(); i++) {
JsonObject object = array.get(i).getAsJsonObject();
if (extras != null) {
if (extras.containsKey("attached_files")) {
for (int j = 0; j < draft_files.size(); j++) {
mNames.clear();
mNames.add(draft_files.get(j));
adapter.notifyDataSetChanged();
//Log.w("MY_TAG", draft_files.get(i));
Log.w("MY_TAG", String.valueOf(mNames));
}
mNames.add(object.get("filename").toString().substring(1, object.get("filename").toString().length() - 1));
adapter.notifyDataSetChanged();
//Log.w("MY_TAG", Arrays.toString(draft_files));
Log.w("MY_TAG", Arrays.toString(new ArrayList[]{mNames}));
} else {
mNames.add(object.get("filename").toString().substring(1, object.get("filename").toString().length() - 1));
adapter.notifyDataSetChanged();
Log.w("MY_TAG", String.valueOf(mNames));
}
}
}
}
no need to set Adapter twice . remove this line
recyclerView.setAdapter(adapter);
From
for (int i = 0; i < draft_files.size(); i++) {
mNames.addAll(Collections.singleton(draft_files.get(i)));
adapter.notifyDataSetChanged();
//recyclerView.setAdapter(adapter); no need
Log.w("MY_TAG", draft_files.get(i));
Log.w("MY_TAG", String.valueOf(mNames));
}
Basically I'm using a RecyclerView with gridAdapter to load images from server. But the problem is items of RecyclerView are blinking in a quirky manner. I've tried all the possible solutions but none of them worked so far.
I tried to update glide version from 4.4.0 to 4.8.0 but it's futile. I also tried to disable the animations of RecyclerView but that couldn't help. Can anyone please help me to solve this issue?
Code:
GridLayoutManager gridLayoutManager = new GridLayoutManager(v.getContext(),3);
gridLayoutManager.setSmoothScrollbarEnabled(true);
gridLayoutManager.setItemPrefetchEnabled(true);
gridLayoutManager.setInitialPrefetchItemCount(20);
posts_rView.setLayoutManager(gridLayoutManager);
posts_rView.setItemAnimator(null);
posts_rView.setHasFixedSize(true);
gridPostAdapter=new StarredAdapter(timelineDataList);
posts_rView.setAdapter(gridPostAdapter);
Data Updation Code:
private Emitter.Listener handlePosts = new Emitter.Listener(){
#Override
public void call(final Object... args){
try {
JSONArray jsonArray=(JSONArray)args[0];
Needle.onMainThread().execute(() -> {
timelineDataList.clear();
swipeRefreshLayout.setRefreshing(false);
for(int i=0;i<jsonArray.length();i++){
try {
//JSONArray arr=jsonArray.getJSONArray(i);
JSONObject ob=jsonArray.getJSONObject(i);
post_username=ob.getString("_pid");
post_fullname=ob.getString("owner_fullname");
if(ob.has("owner_profPic"))post_profPic=ob.getString("owner_profPic");
else post_profPic="";
post_time=ob.getString("time");
post_link=ob.getString("img_link");
likes_counter=ob.getString("likes_counter");
comments_counter=ob.getString("comments_counter");
if(ob.has("caption")) post_caption=ob.getString("caption");
else post_caption=null;
//Skipping Private Posts
if(ob.getString("private_post_stat").equals("yes")&&!post_username.equals(my_username)) {
continue;
}
else
private_post_stat = ob.getString("private_post_stat");
comment_disabled=ob.getString("comment_disabled");
share_disabled=ob.getString("share_disabled");
download_disabled=ob.getString("download_disabled");
if(ob.has("short_book_content")) short_book_content=ob.getString("short_book_content");
else short_book_content=null;
society_name_adp=ob.getString("society");
addTimelineData(post_username,post_fullname,post_profPic,post_time,post_link,post_caption,
private_post_stat,comment_disabled,share_disabled,download_disabled,likes_counter,comments_counter,short_book_content,society_name_adp);
} catch (JSONException e) {
e.printStackTrace();
}
}
RecyclerView.Adapter adapter=posts_rView.getAdapter();
posts_rView.setAdapter(null);
posts_rView.setAdapter(adapter);
});
} catch (Exception e) {
Log.e("error",e.toString());
}
}
};
private void addTimelineData(String username,String fullname,String post_profPic,String time,String img_link,String caption,
String private_post_stat,String comment_disabled,String share_disabled,String download_disabled,String likes_counter,
String comments_counter,String short_book_content,String society_name_adp){
boolean isRepeated = false;
for(TimelineData data:timelineDataList){
if (data.getTime().equals(time)) {
isRepeated = true;
}
}
if(!isRepeated){
timelineDataList.add(new TimelineData(username,fullname,post_profPic,time,img_link,caption,private_post_stat,comment_disabled,share_disabled,download_disabled,likes_counter,comments_counter,short_book_content,society_name_adp));
gridPostAdapter.notifyDataSetChanged();
// posts_rView.scrollToPosition(gridPostAdapter.getItemCount()-1);
// posts_rView.scrollToPosition(0);
}
//gridPostAdapter.notifyItemInserted(timelineDataList.size()-1);
}
Adapter Class:
#Override
public void onBindViewHolder(StarViewHolder holder, int position) {
//loading img
Glide.with( parent.getContext()).asBitmap().load(arrayList.get(position)).apply(new RequestOptions()
.override(200, 200)
.dontAnimate()
.placeholder(new ColorDrawable(Color.parseColor("#20001919"))))
.thumbnail(0.1f)
.into(holder.img);
}
#Override
public int getItemCount() {
return arrayList.size();
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}
I do not quite understand yet why the flickering and repositioning to the first element is occurring in your case, as I do not see the updating policy of your RecyclerView. However, I would like to suggest something which might remove the problem.
I would like to suggest you remove the setInitialPrefetchItemCount and setItemPrefetchEnabled configurations while setting GridLayoutManager. This will aid in case of you are having a nested RecyclerView which is not your case.
You are setting adapter each time you are updating your data from the server. I guess you are calling the update action code when you are scrolling the items. Which you should not. Moreover, please remove setting up the adapter of your RecyclerView each time you are updating the dataset. Just use, notifyDataSetChanged after each of your updates. Do not clear the timelineDataList and append the new items instead, and then call notifyDataSetChanged on your adapter.
You are calling notifyDataSetChanged in addTimelineData after each insert to your list. This should be done only after adding all the elements to the list.
So I would like to propose the following code for setting your adapter and updating your RecyclerView. Please note that I have not tested the code and you might have to modify some errors which might occur.
The code for setting up the layout manager and the adapter.
GridLayoutManager gridLayoutManager = new GridLayoutManager(v.getContext(),3);
posts_rView.setLayoutManager(gridLayoutManager);
posts_rView.setHasFixedSize(true);
gridPostAdapter = new StarredAdapter(timelineDataList);
posts_rView.setAdapter(gridPostAdapter);
The code for updating the list.
private Emitter.Listener handlePosts = new Emitter.Listener(){
#Override
public void call(final Object... args){
try {
JSONArray jsonArray = (JSONArray)args[0];
Needle.onMainThread().execute(() -> {
// Do not clear the list. Just append the new data in the list instead
// timelineDataList.clear();
swipeRefreshLayout.setRefreshing(false);
for(int i = 0; i < jsonArray.length(); i++){
try {
JSONObject ob = jsonArray.getJSONObject(i);
post_username = ob.getString("_pid");
post_fullname = ob.getString("owner_fullname");
if(ob.has("owner_profPic")) post_profPic = ob.getString("owner_profPic");
else post_profPic = "";
post_time = ob.getString("time");
post_link = ob.getString("img_link");
likes_counter = ob.getString("likes_counter");
comments_counter = ob.getString("comments_counter");
if(ob.has("caption")) post_caption = ob.getString("caption");
else post_caption = null;
//Skipping Private Posts
if(ob.getString("private_post_stat").equals("yes")&&!post_username.equals(my_username)) {
continue;
}
else
private_post_stat = ob.getString("private_post_stat");
comment_disabled = ob.getString("comment_disabled");
share_disabled = ob.getString("share_disabled");
download_disabled = ob.getString("download_disabled");
if (ob.has("short_book_content")) short_book_content = ob.getString("short_book_content");
else short_book_content = null;
society_name_adp = ob.getString("society");
addTimelineData(post_username, post_fullname, post_profPic, post_time, post_link,post_caption, private_post_stat, comment_disabled, share_disabled, download_disabled, likes_counter, comments_counter, short_book_content, society_name_adp);
} catch (JSONException e) {
e.printStackTrace();
}
}
gridPostAdapter.notifyDataSetChanged();
});
} catch (Exception e) {
Log.e("error",e.toString());
}
}
};
private void addTimelineData(String username, String fullname, String post_profPic, String time, String img_link, String caption, String private_post_stat, String comment_disabled, String share_disabled, String download_disabled, String likes_counter, String comments_counter, String short_book_content, String society_name_adp) {
boolean isRepeated = false;
for(TimelineData data:timelineDataList){
if (data.getTime().equals(time)) {
isRepeated = true;
}
}
if(!isRepeated){
timelineDataList.add(new TimelineData(username,fullname,post_profPic,time,img_link,caption,private_post_stat,comment_disabled,share_disabled,download_disabled,likes_counter,comments_counter,short_book_content,society_name_adp));
// Do not call notifyDataSetChanged each time you are adding an item. This will be called in the call function above. So remove this line.
// gridPostAdapter.notifyDataSetChanged();
}
}
Try to add some pagination in the server side API if it is not there already so that you might fetch the next 20 dataset instead of fetching the whole data again when you are scrolling down.
Hope that helps.
So I have a list of strings (ISBNs), and I need to fill a listview with the objects (Book objects) associated with these strings. The problem is that the function I have to get the Book object using the string takes time, and so to get, say 30 books, the wait approaches 4 or 5 seconds.
One approach I've thought of is to get the Book objects one at a time, and to add them to the list as I get them. But this process will freeze the UI until it's done adding them all. If I try to put this process in a new thread, it won't let me add to the any UI objects (since it's from another thread). If I try to put it in an AsyncTask, I can't access the ListView since it's in the MainActivity class.
There must be a way to dynamically update a UI element, I'm sure I've seen it done. Any suggestions?
EDIT:
This is the code I'm using to actually add items to the list:
//List view and adapter setup
listView = (ListView) findViewById(R.id.listViewCheckout);
bookAdapter = new SearchBookAdapter(getApplicationContext(), R.layout.search_row_layout);
listView.setAdapter(bookAdapter);
for(int i = 0; i < searches.size(); i++) {
//Get the book
Book book = BackendFunctions.getBookFromISBN(fbSnapshot, searches.get(i));
//Assign data to the adapter variables
Bitmap cover = book.getCover();
String title = book.getTitle();
String author = book.getAuthor();
//Add data to the adapter and set the list
SearchBookDataProvider dataProvider = new SearchBookDataProvider(cover, title, author);
bookAdapter.add(dataProvider);
bookAdapter.notifyDataSetChanged();
}
Can you make some changes to your code like this it simple it think it will work
//List view and adapter setup
listView = (ListView) findViewById(R.id.listViewCheckout);
bookAdapter = new SearchBookAdapter(getApplicationContext(), R.layout.search_row_layout);
SearchBookDataProvider dataProvider;
listView.setAdapter(bookAdapter);
new AsyncTask() {
#Override
protected Object doInBackground(Object[] objects) {
for(int i = 0; i < searches.size(); i++) {
//Get the book
Book book = BackendFunctions.getBookFromISBN(fbSnapshot, searches.get(i));
//Assign data to the adapter variables
Bitmap cover = book.getCover();
String title = book.getTitle();
String author = book.getAuthor();
//Add data to the adapter and set the list
dataProvider = new SearchBookDataProvider(cover, title, author);
bookAdapter.add(dataProvider);
}
}
#Override
protected void onPostExecute(Object o) {
if (bookAdapter!= null) {
bookAdapter.notifyDataSetChanged();
}
super.onPostExecute(o);
}
}.execute();
you can use TimerTask to update the listview or runUiThread() or doInBackground(). But remember should use notifysetChanges() when you update the list.
Step 1: Declare a Executer service
private ExecutorService mExecuterService = null;
Step 2:Declare a class for your list iteration and view update
class ListViewUpdater implements Runnable{
public ListViewUpdater(/* if you need you can pass list params here */){
}
#Override
public void run() {
for(int i = 0; i < searches.size(); i++) {
//Get the book
Book book = BackendFunctions.getBookFromISBN(fbSnapshot, searches.get(i));
//Assign data to the adapter variables
Bitmap cover = book.getCover();
String title = book.getTitle();
String author = book.getAuthor();
}
//below code is important for Updating UI ,you should run UI Updates in UI thread
runOnUiThread(new Runnable() {
#Override
public void run() {
bookAdapter.notifyDataSetChanged();
}
});
}
}
Step 3: Initilize and call below methods
//Add data to the adapter and set the list
SearchBookDataProvider dataProvider = new SearchBookDataProvider(cover, title, author);
bookAdapter.add(dataProvider);
mExecuterService = Executors.newSingleThreadExecutor()
mExecuterService.execute(new ListViewUpdater());
It may solve your problems.
I think if you are open to use a open source project ,then my suggestion will be use RX-JAVA.Which is based in reactive and push based model.
link for rx-java.
rx-java example.
You can get the list of the books in a Thread and send a Broadcast with the data received.
Register a broadcast receiver in your MainActivity class and update the Adapter in the receiver. That should not freeze the UI.
EDIT -
BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
Book book = (Book)intent.getSerializableExtra("Book");
SearchBookDataProvider dataProvider = new SearchBookDataProvider(cover, title, author);
bookAdapter.add(dataProvider);
bookAdapter.notifyDataSetChanged();
}
};
Thread thread = new Thread(){
#Override
public void run()
{
for(int i = 0; i < searches.size(); i++) {
//Get the book
Book book = BackendFunctions.getBookFromISBN(fbSnapshot, searches.get(i));
Intent intent = new Intent();
intent.setAction("Book Received");
intent.putExtra("Book",book);
sendBroadcast(intent);
}
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.listView);
listView.setAdapter(bookAdapter);
registerReceiver(broadcastReceiver,new IntentFilter("Book Received"));
thread.start();
}
I've searched all the posts I can find, and none seem to help with my situation. I have an android project that uses web services to pull down hourly weather data and populate a listView with the results.
The weird problem I'm having is that when I debug the project on my android phone, the main activity is blank and the listView isn't populated. If I run the project from android studio with my phone locked, and then unlock my phone the app opens on my phone with all of the listView properly formatted and populated.
I feel like it's a race condition issue between the asynctask and the adapter, but I can't seem to resolve it. I tried making my asyncTask an inner private class and calling notifyDataSetChanged on the adapter inside the onPostExecute method, but to no avail. I feel it must be something simple, but I'm relatively new to Android dev, so I'm stuck.
I have three classes that I'll post the pertinent code from
MainActivity.java (onCreate)
public class MainActivity extends ActionBarActivity {
ArrayList<Weather> w = new ArrayList<Weather>();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DownloadWeatherTask myTask = new DownloadWeatherTask(w);
WeatherAdapter myAdapter = new WeatherAdapter(this,w);
ListView l = (ListView) findViewById(R.id.weatherList);
l.setAdapter(myAdapter);
myTask.execute();
}
}
WeatherAdapter.java
public class WeatherAdapter extends ArrayAdapter<Weather>{
public WeatherAdapter(Context context, ArrayList<Weather> weather) {
super(context, R.layout.item_weather, weather);
}
public View getView(int position, View convertView, ViewGroup parent) {
// Get the data item for this position
Weather forecast = getItem(position);
// Check if an existing view is being reused, otherwise inflate the view
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_weather, parent, false);
}
// Lookup view for data population
TextView tvTime = (TextView) convertView.findViewById(R.id.listTime);
TextView tvDescr = (TextView) convertView.findViewById(R.id.listDescr);
TextView tvTemp = (TextView) convertView.findViewById(R.id.listTemp);
TextView tvHumid = (TextView) convertView.findViewById(R.id.listHumid);
ImageView ivWeather = (ImageView) convertView.findViewById(R.id.weatherImg);
// Populate the data into the template view using the data object
tvTime.setText(forecast.time);
tvDescr.setText(forecast.description);
tvTemp.setText(forecast.temperature+"°(F)");
tvHumid.setText(forecast.humidity+"% humidity");
ivWeather.setImageBitmap(forecast.weatherImg);
// Return the completed view to render on screen
return convertView;
}
}
DownloadWeatherTask.java
public class DownloadWeatherTask extends AsyncTask<Void,Void,Void>{
ArrayList<Weather> data;
public DownloadWeatherTask(ArrayList<Weather> a){
data = a;
}
public ArrayList<Weather> getData() {
return data;
}
protected Void doInBackground(Void...params) {
try {
String website = "http://api.wunderground.com/api/1111111111111/geolookup/q/autoip.json";
URL site = new URL(website);
HttpURLConnection weatherUnderground = (HttpURLConnection) site.openConnection();
weatherUnderground.connect();
JsonParser weatherParser = new com.google.gson.JsonParser();
JsonElement weatherJson = weatherParser.parse(new InputStreamReader((InputStream) weatherUnderground.getContent()));
JsonObject weatherObj = weatherJson.getAsJsonObject();
String zip = weatherObj.get("location").getAsJsonObject().get("zip").getAsString();
String city = weatherObj.get("location").getAsJsonObject().get("city").getAsString();
String state = weatherObj.get("location").getAsJsonObject().get("state").getAsString();
String hourly = "http://api.wunderground.com/api/111111111111/hourly/q/" + state + "/" + city + ".json";
URL hourlySite = new URL(hourly);
HttpURLConnection hourlyConnection = (HttpURLConnection) hourlySite.openConnection();
hourlyConnection.connect();
com.google.gson.JsonParser hourlyParser = new com.google.gson.JsonParser();
JsonElement hourlyWeatherJson = weatherParser.parse(new InputStreamReader((InputStream) hourlyConnection.getContent()));
JsonArray weatherArr = hourlyWeatherJson.getAsJsonObject().get("hourly_forecast").getAsJsonArray();
int l = weatherArr.size();
for (int i = 0; i < l; i++) {
String date = weatherArr.get(i).getAsJsonObject().get("FCTTIME").getAsJsonObject().get("pretty").getAsString();
String temp = weatherArr.get(i).getAsJsonObject().get("temp").getAsJsonObject().get("english").getAsString();
String condition = weatherArr.get(i).getAsJsonObject().get("condition").getAsString();
String humidity = weatherArr.get(i).getAsJsonObject().get("humidity").getAsString();
String iconUrl = weatherArr.get(i).getAsJsonObject().get("icon_url").getAsString();
Bitmap icon = getBitmapFromURL(iconUrl);
data.add(new Weather(date, condition, temp, humidity, icon));
}
} catch (IOException e) {
Log.e("Error: ",e.toString());
}
return null;
}
protected void onPostExecute(Void...params){
}
}
Below are links to my screenshots showing the app not populating the listView, and the app working properly when the program is run while the phone is initially locked.
Any help would be greatly appreciated!!
Thanks
In postExecute(), you need to update the adapter's List and then invoke its notifyDataSetChanged method. I suspect that you were forgetting to update the adapter's data.
The other option is to create a new adapter with the new data, and set the new adapter on the ListView.
I figured out what the issue was! I hadn't added #Override to my onPostExecute() method so it was never being called.
I added the notifyDataSetChanged to my onPostExecute as suggested, which worked once I added the #override to my method.
I'm doing this code for my android programming school project and I've come into a problem which it seems that the JSON has an error in retrieving some of the data from the API it was to take from.
This is an water intake app I'm working on and I'm using the free API provided by openweathermap.org while referencing the code from http://code.tutsplus.com/tutorials/create-a-weather-app-on-android--cms-21587 . The difference is that instead of using a activity and a fragment, I've combined the fragment and activity together with minor modifications to make it runnable on Eclipse.
Here's my code where it goes wrong:
private void updateWeatherInfo(final String city) {
new Thread(){
public void run(){
final JSONObject json = FetchData.getJSON(MainActivity.this, city);
if(json == null) {
handleWeather.post(new Runnable() {
public void run() {
Toast.makeText(MainActivity.this, R.string.place_not_found, Toast.LENGTH_LONG).show();
}
});
}
else {
handleWeather.post(new Runnable() {
public void run() {
renderWeather(json);
}
});
}
}
}.start();
}
private void renderWeather(JSONObject json) {
try {
cityData.setText(json.getString("name").toUpperCase(Locale.US) + ", " + json.getJSONObject("sys").getString("country"));
JSONObject details = json.getJSONArray("weather").getJSONObject(0);
JSONObject main = json.getJSONObject("main");
detailsData.setText(details.getString("description").toUpperCase(Locale.US) + "\n" + "Humidity: " + main.getString("humidity") + "%" + "\n" + "Pressure: " + main.getString("pressure") + " hPa");
currentTemperatureData.setText(String.format("%.2f", main.getDouble("temp"))+ " ℃");
DateFormat df = DateFormat.getDateTimeInstance();
String updatedOn = df.format(new Date(json.getLong("dt")*1000));
updatedData.setText("Last update: " + updatedOn);
getWeatherIcon(details.getInt("id"), json.getJSONObject("sys").getLong("sunrise") * 1000, json.getJSONObject("sys").getLong("sunset") * 1000);
}
catch(Exception e) {
Log.e("SimpleWeather", "One or more fields not found in the JSON data");
}
}
The exception at the try/catch keeps one happening once it reaches cityData and detailsData. I know this because when I debugged it and reaching there it goes straight to the catch after a few step ins, and with commenting out the cityData the next time it got routed to the catch again was at detailsData.
Could anyone see what's wrong and help me point it out?
EDIT: here's the API im using - http://api.openweathermap.org/data/2.5/weather?q=Singapore,sg
EDIT2: It seems it is because my onCreateView which initialises my textViews was after the updateWeatherData and I've since moved it first before the updateWeatherData was called. Now, however, I realise that my onCreateView has also no #Override, and upon putting in the #Override an error occurs that it must implement a supertype method.
The code is as such:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.activity_main, container, false);
cityData = (TextView)rootView.findViewById(R.id.city_field);
updatedData = (TextView)rootView.findViewById(R.id.updated_field);
detailsData = (TextView)rootView.findViewById(R.id.details_field);
currentTemperatureData = (TextView)rootView.findViewById(R.id.current_temperature_field);
weatherIcon = (TextView)rootView.findViewById(R.id.weather_icon);
weatherIcon.setTypeface(weatherIcons);
return rootView;
}
I understand that this code was meant for a fragment, but implementing it into an Activity it seems I have missed a step in modifying it here. How do I proceed from here?
EDIT3: I have now implemented the onCreateView data into the onCreate , since the onCreateView is the fragment version of onCreate . However, I have run into another problem that is out of this question's scope in that my setTypeface returns a NullPointerException. he code of which can be seen below:
public class MainActivity extends Activity {
TextView cityData;
TextView updatedData;
TextView detailsData;
TextView currentTemperatureData;
TextView weatherIcon;
Typeface weatherIcons;
Handler handleWeather;
public MainActivity() {
handleWeather = new Handler();
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
cityData = (TextView) findViewById(R.id.city_field);
updatedData = (TextView) findViewById(R.id.updated_field);
detailsData = (TextView) findViewById(R.id.details_field);
currentTemperatureData = (TextView) findViewById(R.id.current_temperature_field);
weatherIcon = (TextView) findViewById(R.id.weather_icon);
weatherIcons = Typeface.createFromAsset(getAssets(), "weather.ttf");
weatherIcon.setTypeface(weatherIcons);
As such, I will continue my search to this problem for a while, and if I find my answer, I will not post up another question. However, I shall close this question as answered.