RecyclerView not refreshing after a web service manual synced - java

I'm having an issue with RecyclerView not refreshing after I do a manual sync of a web service.
The manual sync is triggered by either swiping-down on the list or by tapping on an ActionBar item.
The manual sync uses a Volley Request to retrieve data in a JSON format, the data is parsed and saved to an SQLite database table. The sync datetime is also saved to an SQLite database table and later displayed in the Fragment's ActionBar Subtitle. The Volley Request is kicked of via a WorkManager OneTimeWorkRequest.
The problem being the RecyclerView list is not refreshed. However if I then trigger another manual sync, the sync datatime in the ActionBar Subtitle and RecyclerView contents are updated but with data from the previous manual sync. This becomes clear if I navigate away from the app to the device's Home screen and then navigate back to my app, which now shows the refreshed data from the most recent manual sync.
I have looked at numerous posts around this issue (see below) and whilst I think I have improved my code none of the recommended solutions have resolved the issue.
Recyclerview not call onCreateViewHolder
RecyclerView not calling onCreateViewHolder or onBindView
Recyclerview not call onCreateViewHolder
RecyclerView is not refreshing
get JSON data from web and display using RecyclerView
Recycler View appear blank and doesn't show SQLite data
RecyclerView onClick not working properly?
Why doesn't RecyclerView have onItemClickListener()?
ListView not updating after web service Sync
Other resources looked at:
https://www.mytrendin.com/display-data-recyclerview-using-sqlitecursor-in-android/
https://medium.com/#studymongolian/updating-data-in-an-android-recyclerview-842e56adbfd8
https://www.youtube.com/watch?v=ObU-wCqoo2I
https://www.youtube.com/watch?v=_0C18cbv6UE
So after a number of months trying to fix this issue I am now turning to the StackOverflow community for help.
Fragment
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_warning_list, container, false);
mSwipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.warning_swipe_refresh_layout);
/* Set the Refresh Listener for the Swipe gesture */
mSwipeRefreshLayout.setOnRefreshListener(
new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
SyncWarningsScheduler.oneTime();
updateUI();
}
}
);
mWarningRecyclerView = (RecyclerView) view.findViewById(R.id.warning_recycler_view);
/* Set the Toolbar to replace the default ActionBar, which has been hidden */
if (mActivity != null) {
Toolbar toolbar = (Toolbar) mActivity.findViewById(R.id.toolbar_abstract_single_fragment);
mActivity.setSupportActionBar(toolbar);
/* Set the toolbar title */
ActionBar actionbar = mActivity.getSupportActionBar();
if (actionbar != null) {
actionbar.setDisplayHomeAsUpEnabled(true);
actionbar.setTitle(getString(R.string.warning_list_fragment_toolbar_title));
}
}
updateUI();
return view;
}
#Override
public void onResume() {
super.onResume();
updateUI();
}
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.fragment_warning_list, menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.sync:
mSwipeRefreshLayout.setRefreshing(true);
SyncWarningsScheduler.oneTime();
updateUI();
return true;
case R.id.information:
/* Handle the Information Menu Item */
FragmentManager fm = getFragmentManager();
if (fm != null) {
WarningListFragmentTFBInformationDialogFragment dialog = new WarningListFragmentTFBInformationDialogFragment();
dialog.show(fm, TFB_INFO_DIALOG_TAG);
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void updateUI() {
WarningList warningList = WarningList.get(mActivity);
List<Warning> warnings = warningList.getWarnings();
if (mWarningAdaptor == null) {
mWarningAdaptor = new WarningAdaptor(warnings);
mWarningRecyclerView.setLayoutManager(new LinearLayoutManager(mContext));
mWarningRecyclerView.setAdapter(mWarningAdaptor);
} else {
mWarningAdaptor.setWarnings(warnings);
mWarningAdaptor.notifyDataSetChanged();
}
/* Update the ToolBar sub title to show the latest sync datetime */
updateToolBarSubTitle();
/* If visible, turn off the Swipe Refresh Progress Circle */
if (mSwipeRefreshLayout != null && mSwipeRefreshLayout.isRefreshing()) {
mSwipeRefreshLayout.setRefreshing(false);
}
}
private void updateToolBarSubTitle() {
SyncInformationList syncInformationList = SyncInformationList.get(mContext);
Date syncDate = syncInformationList.getSyncDatetime(ORMSync.getWarningSyncTypeKey());
ActionBar actionBar = mActivity.getSupportActionBar();
if (actionBar != null) {
actionBar.setSubtitle(DatabaseUtilities.formatDateSpecial(syncDate, true));
}
}
private class WarningHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private Warning mWarning;
private TextView mIssueForTextView;
private TextView mDeclarationTextView;
private View mStatusWarningViewLeft;
private View mStatusWarningViewRight;
public WarningHolder(LayoutInflater inflater, ViewGroup parent) {
super(inflater.inflate(R.layout.list_item_warning, parent, false));
/* Handlers a user press on a Warning */
itemView.setOnClickListener(this);
mIssueForTextView = (TextView) itemView.findViewById(R.id.issueFor_textView);
mDeclarationTextView = (TextView) itemView.findViewById(R.id.declaration_textView);
mStatusWarningViewLeft = (View) itemView.findViewById(R.id.status_warning_left);
mStatusWarningViewRight = (View) itemView.findViewById(R.id.status_warning_right);
}
public void bind(Warning warning) {
mWarning = warning;
String issueForDate;
issueForDate = DatabaseUtilities.formatDateSpecial(mWarning.getIssuedFor(), "d MMM yyyy");
mIssueForTextView.setText(issueForDate);
mDeclarationTextView.setText(mWarning.getTfbDeclaration());
/* Set Declaration text colour */
if (mWarning.isTfbStatus()) {
/* If the day is a TFB set text color to Red */
mIssueForTextView.setTextColor(getResources().getColor(R.color.red));
mDeclarationTextView.setTextColor(getResources().getColor(R.color.red));
}
/* Set left and right status warning colour based on TFB status */
mStatusWarningViewLeft.setBackgroundResource(mWarning.setStatusWarningColor());
mStatusWarningViewRight.setBackgroundResource(mWarning.setStatusWarningColor());
}
#Override
public void onClick(View v) {
/* Process onClick */
Intent intent = WarningPagerActivity.newIntent(mActivity, mWarning.getUID());
startActivity(intent);
}
}
private class WarningAdaptor extends RecyclerView.Adapter<WarningHolder> {
private List<Warning> mWarnings;
public WarningAdaptor(List<Warning> warnings) {
mWarnings = warnings;
}
#NonNull
#Override
public WarningHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(mActivity);
return new WarningHolder(layoutInflater, parent);
}
#Override
public void onBindViewHolder(#NonNull WarningHolder holder, int position) {
/* Bind data */
Warning warning = mWarnings.get(position);
holder.bind(warning);
}
#Override
public int getItemCount() {
return mWarnings.size();
}
public void setWarnings(List<Warning> warnings) {
mWarnings.clear();
mWarnings = warnings;
}
public List<Warning> getWarnings() {
return mWarnings;
}
}
}
WorkManager oneTimeWorkRequest Scheduler
public class SyncWarningsScheduler {
private static final String TAG = "SyncWarningsScheduler";
private static final String ONE_TIME_WORK_REQUEST = "OneTime";
private static final String ONE_TIME_WORK_REQUEST_TAG = TAG + ONE_TIME_WORK_REQUEST;
private static final String ONE_TIME_WORK_REQUEST_TAG_UNIQUE = ONE_TIME_WORK_REQUEST_TAG + "Unique";
/* Getters and Setters */
public static String getOneTimeWorkRequestTagUnique() {
return ONE_TIME_WORK_REQUEST_TAG_UNIQUE;
}
public static void oneTime() {
WorkManager workManager = WorkManager.getInstance();
/* Create a Constraints object that defines when and how the task should run */
Constraints constraints = new Constraints.Builder()
.setRequiresCharging(false)
.setRequiredNetworkType(NetworkType.CONNECTED)
.build();
/* Build the Input Data to pass to the Worker */
Data inputData = new Data.Builder()
.putString(SyncWarningsWorker.getWorkRequestTypeKey(), ONE_TIME_WORK_REQUEST)
.build();
/* Build the One Time Work Request */
OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(SyncWarningsWorker.class)
.setConstraints(constraints)
/* Sets the input data for the ListenableWorker */
.setInputData(inputData)
.addTag(ONE_TIME_WORK_REQUEST_TAG)
.build();
workManager.enqueueUniqueWork(ONE_TIME_WORK_REQUEST_TAG_UNIQUE, ExistingWorkPolicy.REPLACE, oneTimeWorkRequest);
}
}
WorkManager Worker
public class SyncWarningsWorker extends Worker {
private static final String TAG = "SyncWarningsWorker";
private Context mContext;
private SQLiteDatabase mDatabase;
private WarningList mWarningList;
private static final String WORK_REQUEST_TYPE_KEY = "warningworkrequesttype";
private static final String SYNC_DATE_TIME_KEY = "warningsyncdatetime";
public SyncWarningsWorker(#NonNull Context context, #NonNull WorkerParameters workerParams) {
super(context, workerParams);
/* Set the Context which must be the Application Context */
mContext = context;
mDatabase = IncidentsDatabaseHelper.get(context).getWritableDatabase();
/* Get a refer to the WarningList Singleton */
mWarningList = WarningList.get(context);
}
/* Getters and Setters */
public static String getWorkRequestTypeKey() {
return WORK_REQUEST_TYPE_KEY;
}
public static String getSyncDateTimeKey() {
return SYNC_DATE_TIME_KEY;
}
#NonNull
#Override
public Result doWork() {
/* Read passed-in argument(s) */
String workRequestType = getInputData().getString(WORK_REQUEST_TYPE_KEY);
LogUtilities.info(TAG, "doWork() - Processing EMV Warnings Work Request Type: " + workRequestType);
try {
downloadWarnings();
Date now = new Date();
long nowMilliSeconds = now.getTime();
now.setTime(nowMilliSeconds);
/* Update the WarningSyncType in SyncInformationList with the Warnings Sync Datetime */
SyncInformationList syncInformationList = SyncInformationList.get(mContext);
syncInformationList.updateSyncDatetime(ORMSync.getWarningSyncTypeKey(), now);
Data syncDateTime = new Data.Builder()
.putLong(SYNC_DATE_TIME_KEY, nowMilliSeconds)
.build();
return Result.success(syncDateTime);
} catch (Exception e){
LogUtilities.error(TAG, "doWork() - Can't download EMV Warnings data.\n\n" + e.toString());
return Result.failure();
}
}
private void downloadWarnings() {
VolleyRequestQueue volleyRequestQueue;
StringRequest request = new StringRequest(Request.Method.GET, JSONWarningsSchema.getTfbFdrJsonEndPoint(), onPostsLoaded, onPostsError);
volleyRequestQueue = VolleyRequestQueue.get(mContext);
volleyRequestQueue.addToVolleyRequestQueue(request);
}
private final Response.Listener<String> onPostsLoaded = new Response.Listener<String>() {
ContentValues contentvalues;
String noData = "NO DATA";
#Override
public void onResponse(String response) {
/* Delete all the Warning records from the SQLite table */
mWarningList.deleteAllWarnings();
try {
JSONObject jsonBody = new JSONObject(response);
/* Within jsonBody is one nested JSON Array */
JSONArray jsonArrayResults = jsonBody.getJSONArray(JSONWarningsSchema.getJsonRootArrayName());
for (int i = 0; i < jsonArrayResults.length(); i++) {
/*
* Within jsonArrayResults are 10 sometimes 9 JSON Objects, the first 5 Objects are for TFB declarations and
* the last 5 (4) Objects are for FDR declarations.
*/
JSONObject warningMetadata = jsonArrayResults.getJSONObject(i);
if (i < 5) {
/*
* The first 5 Objects are for Today and the next 4 days worth of TFB declarations. The TFB declaration in these Objects are
* used to INSERT new records into the warnings table using the issueFor date as the Alternate Primary Key.
* The FDR declarations for each day are defaulted to "NO DATA" to cater for the sometimes missing FDR data on the 5th day, this is
* to avoid null pointer errors when displaying the data in fragment_warning.
*/
Warning warning = new Warning();
String issueForDate = warningMetadata.getString(JSONWarningsSchema.Keys.getIssueFor());
warning.setIssuedFor(JSONUtilities.stringToDate(issueForDate, JSONWarningsSchema.getJsonIssueForDateFormat()));
String status = warningMetadata.getString(JSONWarningsSchema.Keys.getStatus());
warning.setTfbStatus(JSONUtilities.stringToBoolean(status));
warning.setTfbDeclaration(warningMetadata.getString(JSONWarningsSchema.Keys.getDeclaration()));
/*
* Within the warningMetadata JSONObject is a JSONArray called declareList. Need to get the Array and
* iterate through the Array to extract the TFB warning for each District for this day.
* We know the exact number of JSONObjects in the declareList Array (ie an Object for each District).
*/
JSONArray jsonArrayTFBDeclareList = warningMetadata.getJSONArray(JSONWarningsSchema.getJsonDeclareListArrayName());
/* Iterate through the TFB declareList Array */
for (int j = 0; j < jsonArrayTFBDeclareList.length(); j++) {
/* Get the JSON Object within the jsonArrayDeclareList Array */
JSONObject declareListMetadata = jsonArrayTFBDeclareList.getJSONObject(j);
/* Get the name and status pairs from the declareListMetadata Object */
String name = declareListMetadata.getString(JSONWarningsSchema.Keys.getDeclareListName());
String declareListStatus = declareListMetadata.getString(JSONWarningsSchema.Keys.getDeclareListStatus());
switch (name) {
case "Mallee":
warning.setTfbMallee(declareListStatus);
warning.setFdrMallee(noData);
break;
case "Wimmera":
warning.setTfbWimmera(declareListStatus);
warning.setFdrWimmera(noData);
break;
case "South West":
warning.setTfbSouthWest(declareListStatus);
warning.setFdrSouthWest(noData);
break;
case "Northern Country":
warning.setTfbNorthernCountry(declareListStatus);
warning.setFdrNorthernCountry(noData);
break;
case "North Central":
warning.setTfbNorthCentral(declareListStatus);
warning.setFdrNorthCentral(noData);
break;
case "Central":
warning.setTfbCentral(declareListStatus);
warning.setFdrCentral(noData);
break;
case "North East":
warning.setTfbNorthEast(declareListStatus);
warning.setFdrNorthEast(noData);
break;
case "West and South Gippsland":
warning.setTfbWestAndSouthGippsland(declareListStatus);
warning.setFdrWestAndSouthGippsland(noData);
break;
case "East Gippsland":
warning.setTfbEastGippsland(declareListStatus);
warning.setFdrEastGippsland(noData);
break;
default:
break;
}
}
contentvalues = ContentValueUtilities.getWarningListContentValues(warning, true);
mDatabase.beginTransaction();
try {
mDatabase.insert(ORMWarnings.getTableName(), null, contentvalues);
mDatabase.setTransactionSuccessful();
} catch (SQLiteException e) {
LogUtilities.error(TAG, "onPostsLoaded > onResponse - ERROR Inserting record into the '" + ORMWarnings.getTableName() + "' Table.\n\n" + e.toString());
} finally {
mDatabase.endTransaction();
}
} else {
/*
* The last 5 or sometimes 4 Objects are for Today and the next 4 days worth of FDR declarations. The FDR declarations
* in these Objects are used to UPDATE FDR attributes in the warnings table using the issueFor date to find the existing warnings
* record.
*/
String issueForFDR = warningMetadata.getString(JSONWarningsSchema.Keys.getIssueFor());
/* Ensure the retrieved issueFor date string is converted consistently */
Date issueForFDRDate = JSONUtilities.stringToDate(issueForFDR, JSONWarningsSchema.getJsonIssueForDateFormat());
/* Find the record in the warnings table by using the issueForFDRDate date. */
Warning warningExists = mWarningList.getWarning(issueForFDRDate);
/* Make sure a warning record has been returned */
if (warningExists != null) {
String issueAtDate = warningMetadata.getString(JSONWarningsSchema.Keys.getIssueAt());
warningExists.setFdrIssuedAt(JSONUtilities.stringToDate(issueAtDate, JSONWarningsSchema.getJsonIssueAtDateFormat()));
/*
* Within the warningMetadata JSONObject is a JSONArray called declareList. Need to get the Array and
* iterate through the Array to extract the FDR warning for each District for this day.
* We know the exact number of JSONObjects in the declareList Array (ie an Object for each District).
*/
JSONArray jsonArrayFDRDeclareList = warningMetadata.getJSONArray(JSONWarningsSchema.getJsonDeclareListArrayName());
/* Iterate through the FDR declareList Array */
for (int z = 0; z < jsonArrayFDRDeclareList.length(); z++) {
/* Get the JSON Object within the jsonArrayFDRDeclareList Array */
JSONObject declareListMetadataFDR = jsonArrayFDRDeclareList.getJSONObject(z);
/* Get the name and status pairs from the declareListMetadataFDR Object */
String nameFDR = declareListMetadataFDR.getString(JSONWarningsSchema.Keys.getDeclareListName());
String declareListStatusFDR = declareListMetadataFDR.getString(JSONWarningsSchema.Keys.getDeclareListStatus());
switch (nameFDR) {
case "Mallee":
warningExists.setFdrMallee(declareListStatusFDR);
break;
case "Wimmera":
warningExists.setFdrWimmera(declareListStatusFDR);
break;
case "South West":
warningExists.setFdrSouthWest(declareListStatusFDR);
break;
case "Northern Country":
warningExists.setFdrNorthernCountry(declareListStatusFDR);
break;
case "North Central":
warningExists.setFdrNorthCentral(declareListStatusFDR);
break;
case "Central":
warningExists.setFdrCentral(declareListStatusFDR);
break;
case "North East":
warningExists.setFdrNorthEast(declareListStatusFDR);
break;
case "West and South Gippsland":
warningExists.setFdrWestAndSouthGippsland(declareListStatusFDR);
break;
case "East Gippsland":
warningExists.setFdrEastGippsland(declareListStatusFDR);
break;
default:
break;
}
}
contentvalues = ContentValueUtilities.getWarningListContentValues(warningExists, false);
mDatabase.beginTransaction();
try {
mDatabase.update(ORMWarnings.getTableName(), contentvalues, ORMWarnings.getUUIDColumn() + " = ?", new String[] {warningExists.getUID().toString()});
mDatabase.setTransactionSuccessful();
} catch (SQLiteException e) {
LogUtilities.error(TAG, "onPostsLoaded > onResponse - ERROR Updating record in the '" + ORMWarnings.getTableName() + "' Table.\n\n" + e.toString());
} finally {
mDatabase.endTransaction();
}
} else {
/* Something went wrong can't find warning record using the issueForFDRDate date */
LogUtilities.wtf(TAG, "onPostsLoaded > onResponse - " + issueForFDRDate.toString() + " warning record not found.\n\n");
}
}
}
} catch (JSONException e) {
LogUtilities.error(TAG, "onPostsLoaded > onResponse - Failed to Parse JSON body.\n\n" + e.toString());
}
}
};
private final Response.ErrorListener onPostsError = new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
LogUtilities.error(TAG, "onPostsError > onErrorResponse - Failed to download JSON body.\n\n" + error.toString());
}
};
}

Use this in your Adapter
public void setWarnings(List<Warning> warnings) {
mWarnings.clear();
mWarnings = warnings;
notifyDataSetChanged();
}

I think you to updateUI(); method call after successfully api call because when api call it's working in background.
or
you can set
sleep(5000)
updateUI();

Related

Images in the RecyclerView are not refreshing

About project.
I am making an app, which takes data from website and shows it.
Now, I am working on the ListFragment, which is responsible for getting titles, dates, authors and images, from the list of last 6 posts, then putting them in the storage, and from storage to the RecyclerView.
click to see the website
Everything works fine, when I am running ListFragment. But when i try to refresh my RecyclerView with my SwipeRefreshLayout it refreshes only String data, like titles, dates and authors. Refreshing images in RecyclerView istn't working.
For example.
There is one new post on the website. I run my ListFragment without Internet connection, then turn Internet connection on and refresh RecyclerView swiping it down. Only String values are updated in RecyclerView items in a proper way, Images stay in the same places, despite there were downloaded new ones and put into the storage successfully.
How can I succesfully refresh images in my RecyclerView?
Thanks for any help!
onCreateView() method of my ListFragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
/* Inflating View */
view = inflater.inflate(R.layout.fragment_list, container, false);
ImageView banerImageView = view.findViewById(R.id.newsyBanerImageView);
/* Enabling RecyclerView, and setting adapter for it */
recyclerView = view.findViewById(R.id.recyclerView);
layoutManager = new LinearLayoutManager(getContext());
recyclerView.setLayoutManager(layoutManager);
/* SwipeRefreshLayout varriable for recyclerView refreshing operations */
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshLayout);
swipeRefreshLayout.setOnRefreshListener(this);
/* Executing methods in TakeDataFromWebsite class. It dowloads data to save it to
storage, then read it and put in UserInterface, in another Thread */
new TakeDataFromWebSite().execute();
swipeRefreshLayout.setRefreshing(true);
/* enabling prograssbar */
progressBar = view.findViewById(R.id.progressBar);
return view;
}
onRefresh() method of my SwipeRefreshLayout
#Override
public void onRefresh() {
new TakeDataFromWebSite().execute();
}
TakeDataFromWebsite class
public class TakeDataFromWebSite extends AsyncTask<Void, Void, Void> {
//
MyRecyclerAdapter recyclerAdapterInside = new MyRecyclerAdapter();
/* In these ArrayLists is put Content from website, and saved to files, contentSources
are adresses of articles contents */
private ArrayList<String> titlesFromWebsite = new ArrayList<>();
private ArrayList<String> datesFromWebsite = new ArrayList<>();
private ArrayList<String> authorsFromWebsite = new ArrayList<>();
private ArrayList<String> imagesSourcesFromWebsite = new ArrayList<>();
private ArrayList<String> contentSourcesFromWebsite = new ArrayList<>();
//
private ArrayList<RequestCreator> imagesFromWebsite = new ArrayList<>();
/* In these arraylists is put content, which is read from storage */
private ArrayList<String> titlesFromStorage = new ArrayList<>();
private ArrayList<String> datesFromStorage = new ArrayList<>();
private ArrayList<String> authorsFromStorage = new ArrayList<>();
private ArrayList<String> contentSourcesFromStorage = new ArrayList<>();
//
private ArrayList<File> imagesFromStorage = new ArrayList<>();
/* Number of downloaded divs */
int elementsCountFromWebsite;
/* It's used to do not always check if website is available in another thread */
boolean isUrlReachable = false;
/* Method in new Thread */
#Override
protected Void doInBackground(Void... params) {
if (isURLReachable(mContext, webSiteAddress)) {
isUrlReachable = true;
Document doc = new Document("doc");
try {
/* Getting whole document */
doc = Jsoup.connect(webSiteAddress).get();
} catch (Exception e) {
e.printStackTrace();
Log.e("ListFragment", "Doc from site getting Exception.");
}
/* Getting proper divs and spans */
Elements postThumbDivs = doc.select("div[class=post-thumb]");
Elements dates = doc.select("span[class=posted-on]");
Elements authors = doc.select("span[class=author vcard]");
/* Setting variable to number of post divs downloaded */
elementsCountFromWebsite = postThumbDivs.size();
/* Variables for iteration */
Element postThumbDiv;
Element date;
Element author;
for (int i = 0; i < elementsCountFromWebsite; i++) {
/* Getting titles and adding them to list */
postThumbDiv = postThumbDivs.get(i).select("a").first();
titlesFromWebsite.add(postThumbDiv.attr("title"));
/* Getting sources od articles contents and adding them to list */
postThumbDiv = postThumbDivs.get(i).select("a").first();
contentSourcesFromWebsite.add(postThumbDiv.attr("href"));
/* Getting images sources and adding them to list */
postThumbDiv = postThumbDivs.get(i).select("img").get(0);
imagesSourcesFromWebsite.add(postThumbDiv.attr("src"));
/* Adding picasso requestCreator to the arrayList, which is used to write
data in the storage later * */
imagesFromWebsite.add(Picasso.get().load(postThumbDiv.attr("src")));
/* Getting dates and adding them to list */
date = dates.get(i).getElementsByTag("time").first();
datesFromWebsite.add(date.text());
/* Getting authors and adding them to list */
author = authors.get(i);
authorsFromWebsite.add(author.text());
}
}
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
/* If website is available write data to storage */
if (isUrlReachable){
write(mContext, titlesFromWebsite, fileName + "Titles");
write(mContext, datesFromWebsite, fileName + "Dates");
write(mContext, authorsFromWebsite, fileName + "Authors");
write(mContext, imagesSourcesFromWebsite, fileName + "ImagesSources");
write(mContext, contentSourcesFromWebsite, fileName + "ContentSources");
for(int i = 0; i < elementsCountFromWebsite; i++){
imagesFromWebsite.get(i).into
(picassoImageTarget(mContext, imagesDirectoryName, Integer
.toString(i) + ".png"));
}
} else{
Snackbar.make(view, getText(R.string.brak_polaczenia_z_serwerem), Snackbar
.LENGTH_LONG).show();
}
/* Reading data from storage files. These arraylists are later sent to an adapter,
whch uses them as a source of content */
titlesFromStorage = readStringArraylist(mContext, fileName + "Titles");
datesFromStorage = readStringArraylist(mContext, fileName + "Dates");
authorsFromStorage = readStringArraylist(mContext, fileName + "Authors");
contentSourcesFromStorage = readStringArraylist(mContext, fileName + "ContentSources");
/* Number of elements taken from storage used while reading data from storage */
int itemsCountFromStorage = titlesFromStorage.size();
/* Getting Images from storage into file and adding to the files arraylist */
ContextWrapper contextWrapper = new ContextWrapper(mContext);
File directory = contextWrapper.getDir(imagesDirectoryName, Context.MODE_PRIVATE);
for(int i = 0; i < itemsCountFromStorage; i++){
File myImageFile = new File(directory, Integer.toString(i) + ".png");
imagesFromStorage.add(myImageFile);
}
/* Setting FragmentManager to the recyclerAdapter */
recyclerAdapterInside.setFragmentManager(getFragmentManager());
/* Sending String Arraylist to an adapter */
recyclerAdapterInside.setTitles(titlesFromStorage);
recyclerAdapterInside.setDates(datesFromStorage);
recyclerAdapterInside.setAuthors(authorsFromStorage);
recyclerAdapterInside.setContentSources(contentSourcesFromStorage);
/* Sending File Arraylist to an adapter */
recyclerAdapterInside.setImages(imagesFromStorage);
/* Context and directory sent from this fragment to an adapter. They are used for
reading images from storage */
recyclerAdapterInside.setmContext(mContext);
recyclerAdapterInside.setImagesDirectoryName(imagesDirectoryName);
/* Setting an adapter to the recyclerView. Not in CreateView() method, because it is
need to be done after loading all data from storage*/
recyclerAdapterInside.notifyDataSetChanged();
swipeRefreshLayout.setRefreshing(false);
recyclerView.setAdapter(recyclerAdapterInside);
}
}
My RecyclerView.Adapter class
public class MyRecyclerAdapter extends RecyclerView.Adapter<MyRecyclerAdapter.ViewHolder> {
/* rraylists used to provide content */
private ArrayList<String> titles = new ArrayList<>();
private ArrayList<String> dates = new ArrayList<>();
private ArrayList<String> authors = new ArrayList<>();
private ArrayList<String> contentSources = new ArrayList<>();
/* Arraylist used, which i set with images from storage */
private ArrayList<File> images = new ArrayList<>();
/* Fragment with content of article and manager from */
private ContentFragment contentFragment;
private FragmentManager fragmentManager;
/* Context and imagesDirectoryName from ListFragment */
private Context mContext;
private String imagesDirectoryName;
class ViewHolder extends RecyclerView.ViewHolder{
public TextView itemTitleTextView;
public TextView itemDateTextView;
public TextView itemAuthorTextView;
public ImageView itemImageView;
public ImageView itemDateImageView;
public ImageView itemAuthorImageView;
public ViewHolder(View itemView){
super(itemView);
itemTitleTextView = itemView.findViewById(R.id.itemTitleTextView);
itemDateTextView = itemView.findViewById(R.id.itemDateTextView);
itemAuthorTextView = itemView.findViewById(R.id.itemAuthorTextView);
itemImageView = itemView.findViewById(R.id.itemImageView);
itemDateImageView = itemView.findViewById(R.id.itemDateImageView);
itemAuthorImageView = itemView.findViewById(R.id.itemAuthorImageView);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int position = getAdapterPosition();
contentFragment = new ContentFragment();
/* Setting contentFragment attributes to proper values */
contentFragment.setWebSiteAddress(contentSources.get(position));
contentFragment.setItemIndex(position);
contentFragment.setDate(dates.get(position));
contentFragment.setAuthor(authors.get(position));
contentFragment.setTitle(titles.get(position));
contentFragment.setImagesDirectoryName(imagesDirectoryName);
/* Fragment transaction process */
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.menuFragment, contentFragment);
transaction.addToBackStack("contentFragment");
transaction.commit();
/* Test */
Snackbar.make(v, "Click detected on item " + position, Snackbar.LENGTH_LONG)
.setAction("Action",null).show();
}
});
}
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i){
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.card_layout,
viewGroup, false);
ViewHolder viewHolder = new ViewHolder(v);
return viewHolder;
}
#Override
public void onBindViewHolder(ViewHolder viewHolder, int i){
viewHolder.itemTitleTextView.setText(titles.get(i));
viewHolder.itemDateTextView.setText(dates.get(i));
viewHolder.itemAuthorTextView.setText(authors.get(i));
/* Process of reading images from arraylist and putting them to ImageViews */
Picasso.get().load(images.get(i)).into(viewHolder.itemImageView);
}
#Override
public int getItemCount(){
return titles.size();
}
public void setTitles(ArrayList<String> titles){
this.titles = titles;
}
public void setDates(ArrayList<String> dates){
this.dates = dates;
}
public void setAuthors(ArrayList<String> authors){
this.authors = authors;
}
public void setContentSources(ArrayList<String> contentSources){
this.contentSources = contentSources;
}
public void setFragmentManager(FragmentManager fragmentManager){
this.fragmentManager = fragmentManager;
}
public void setmContext(Context mContext){
this.mContext = mContext;
}
public void setImagesDirectoryName(String imagesDirectoryName) {
this.imagesDirectoryName = imagesDirectoryName;
}
public void setImages(ArrayList<File> images){
this.images = images;
}
}
You're notifying recyclerAdapterInside before providing it to recyclerview :
recyclerAdapterInside.notifyDataSetChanged();
swipeRefreshLayout.setRefreshing(false);
recyclerView.setAdapter(recyclerAdapterInside);
Just change that sequence inside your onPostExecute(Void aVoid) of your AsyncTask :
swipeRefreshLayout.setRefreshing(false);
recyclerView.setAdapter(recyclerAdapterInside); // Set adapter first.
recyclerAdapterInside.notifyDataSetChanged(); // Then notify for data change.
Note: Never try to update UI (i.e. Activity/Fragment) from
AsyncTask, it may throw NullPointerException for UI view objects. Use Interface to communicate between UI and
AsyncTask.

Improve Large ListView Adapter smooth scroll, sometimes jerky

I'm trying to see what is making my listview jerk sometimes when scroll, at times it's bad especially when the application first launches.
All the conditions I have are necessary, unless there is something I don't know(highly likely).
I'm not running certain tasks on a seperate thread because they are dependent on the data I receive from the backend(I'm coding both, so backend suggestions are welcome as well). Product is in beta but really need to make this a slightly bit smoother. I'm compressing the images, and they are a bit long but it's not the problem because when I upload the images from the device, I also include the width and height of the image and send that along to the backend. These dimensions come back when loading the list.
One thing I wonder is if calculating/converting the dimensions for the specific device's screen is causing the slight lag. Not sure how resource intensive that task is, but without it(without knowing the dimensions, each row would start out flat and then expand to the actual picture size which would cause the list to jump, so I can't run that calculation on the background either.)
Basically the scrolling isn't bad, but I need to improve this somehow.
Here is my Adapter:
public class VListAdapter extends BaseAdapter {
ViewHolder viewHolder;
private boolean isItFromProfile;
/**
* fields For number formating, ex. 1000
* would return 1k in the format method
*/
private static final NavigableMap<Long, String> suffixes = new TreeMap<>();
static {
suffixes.put(1_000L, "k");
suffixes.put(1_000_000L, "M");
suffixes.put(1_000_000_000L, "G");
suffixes.put(1_000_000_000_000L, "T");
suffixes.put(1_000_000_000_000_000L, "P");
suffixes.put(1_000_000_000_000_000_000L, "E");
}
private Context mContext;
private LayoutInflater mInflater;
private ArrayList<Post> mDataSource;
private static double lat;
private static double lon;
public VListAdapter(Context context, ArrayList<Post> items) {
mContext = context;
mDataSource = items;
//mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
isItFromProfile = false;
mInflater = LayoutInflater.from(context);
}
public VListAdapter() {
}
public VListAdapter(Context baseContext, ArrayList<Post> posts, boolean b) {
mContext = baseContext;
mDataSource = posts;
//mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
isItFromProfile = b;
mInflater = LayoutInflater.from(baseContext);
}
public void addElement(Post post) {
mDataSource.add(0, post);
this.notifyDataSetChanged();
}
#Override
public int getCount() {
return mDataSource.size();
}
#Override
public Object getItem(int position) {
return mDataSource.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
int limit = Math.min(position + 4, getCount());
for (int i = position; i < limit; i++) {
Glide.with(mContext).load(((Post) getItem(i)).getFilename().toString()).preload();
}
// StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectDiskReads()
// .detectDiskWrites().detectNetwork()
// .penaltyLog().build());
View rowView = convertView;
if (rowView == null) {
viewHolder = new ViewHolder();
rowView = mInflater.inflate(R.layout.mylist, parent, false);
viewHolder.titleTextView = (TextView) rowView.findViewById(R.id.usernameinlist);
viewHolder.timeago = (TextView) rowView.findViewById(R.id.timeago);
//viewHolder.sharebutton = (ImageView) rowView.findViewById(R.id.sharebutton);
viewHolder.likesTextView = (TextView) rowView.findViewById(R.id.likestext);
viewHolder.viewcount = (TextView) rowView.findViewById(R.id.viewcount);
viewHolder.distance = (TextView) rowView.findViewById(R.id.distance);
viewHolder.footprints = (TextView) rowView.findViewById(R.id.footprintcount);
viewHolder.postText = (TextView) rowView.findViewById(R.id.posttext);
viewHolder.profilePic = (ImageView) rowView.findViewById(R.id.profilethumb);
viewHolder.caption = (TextView) rowView.findViewById(R.id.captiontext);
viewHolder.moremenu = (ImageView) rowView.findViewById(R.id.dots);
viewHolder.likesPic = (ImageView) rowView.findViewById(R.id.likeimage);
viewHolder.mapitPic = (ImageView) rowView.findViewById(R.id.mapimage);
viewHolder.playbutton = (ImageView) rowView.findViewById(R.id.playbutton);
viewHolder.videoThumb = (ImageView) rowView.findViewById(R.id.videothumb);
viewHolder.listphoto = (ImageView) rowView.findViewById(R.id.listphoto);
viewHolder.rainbow = (ImageView) rowView.findViewById(R.id.rainbow);
rowView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) rowView.getTag();
}
final Post post = (Post) getItem(position);
int color = Color.parseColor("#dddddd");
viewHolder.likesPic.setColorFilter(color);
viewHolder.mapitPic.setColorFilter(color);
viewHolder.moremenu.setColorFilter(color);
if (Hawk.count() == 0)
initHawkWithDataFromServer();
if (isItFromProfile) {
viewHolder.profilePic.setVisibility(View.GONE);
viewHolder.titleTextView.setVisibility(View.GONE);
viewHolder.distance.setVisibility(View.GONE);
}
viewHolder.titleTextView.setText(post.getUsername());
PrettyTime prettyTime = new PrettyTime();
DateTime dateTime = new DateTime(post.getUploadDate().get$date());
viewHolder.timeago.setText(prettyTime.format(dateTime.toDate()));
viewHolder.likesTextView.setText(String.valueOf(format(post.getLikes())));
viewHolder.footprints.setText(String.valueOf(format(post.getLocation().size() - 1)));
//don't display 0 if there are no likes, just show heart icon
if (viewHolder.likesTextView.getText().equals("0"))
viewHolder.likesTextView.setVisibility(View.GONE);
else
viewHolder.likesTextView.setVisibility(View.VISIBLE);
//don't display 0 if there are no footprints
if (viewHolder.footprints.getText().equals("0"))
viewHolder.footprints.setVisibility(View.GONE);
else
viewHolder.footprints.setVisibility(View.VISIBLE);
double[] loc = post.getLocation().get(0);
viewHolder.distance.setText("~" + PostListFragment.distance(loc[0], loc[1], 'M') + " Miles");
if (post.getViews() != null)
viewHolder.viewcount.setText(format(post.getViews()) + (post.getViews() == 1 ? " View" : " Views"));
String profilePictureS3Url = "https://s3-us-west-2.amazonaws.com/moleheadphotos/" + post.getUsername()
+ ".jpg";
String filename = post.getS3link();
final String videoThumbURL = "https://s3-us-west-2.amazonaws.com/moleheadphotos/" + filename;
Glide.with(mContext).load(profilePictureS3Url).asBitmap().centerCrop().into(new BitmapImageViewTarget(viewHolder.profilePic) {
#Override
protected void setResource(Bitmap resource) {
RoundedBitmapDrawable circularBitmapDrawable =
RoundedBitmapDrawableFactory.create(mContext.getResources(), resource);
circularBitmapDrawable.setCircular(true);
viewHolder.profilePic.setImageDrawable(circularBitmapDrawable);
}
});
int height = ((Post) getItem(position)).getHeight();
int width = ((Post) getItem(position)).getWidth();
if (height != 0 && width != 0) {
ViewGroup.LayoutParams params = viewHolder.listphoto.getLayoutParams();
Resources r = mContext.getResources();
height = (int) getHeight(height, width);
params.height = height;
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
viewHolder.listphoto.setLayoutParams(params);
} else {
ViewGroup.LayoutParams params = viewHolder.listphoto.getLayoutParams();
params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
viewHolder.listphoto.setLayoutParams(params);
}
if (post.getType() == null) {
Glide.clear(viewHolder.listphoto);
viewHolder.listphoto.setVisibility(View.GONE);
//Glide.clear(viewHolder.listphoto);
viewHolder.videoThumb.setVisibility(View.VISIBLE);
viewHolder.rainbow.setVisibility(View.VISIBLE);
Glide.with(mContext).load(videoThumbURL).fitCenter()
.diskCacheStrategy(DiskCacheStrategy.ALL).dontAnimate().into(viewHolder.videoThumb);
viewHolder.playbutton.setVisibility(View.VISIBLE);
}
if (post.getType() != null) {
if (post.getType().equals("video")) {
viewHolder.playbutton.setVisibility(View.VISIBLE);
Glide.clear(viewHolder.listphoto);
viewHolder.listphoto.setVisibility(View.GONE);
Glide.clear(viewHolder.postText);
viewHolder.postText.setVisibility(View.GONE);
viewHolder.videoThumb.setVisibility(View.VISIBLE);
viewHolder.rainbow.setVisibility(View.VISIBLE);
Glide.with(mContext).load(videoThumbURL).fitCenter()
.diskCacheStrategy(DiskCacheStrategy.ALL).dontAnimate().into(viewHolder.videoThumb);
}
if (post.getType().equals("image")) {
Glide.clear(viewHolder.videoThumb);
viewHolder.videoThumb.setVisibility(View.GONE);
viewHolder.rainbow.setVisibility(View.GONE);
Glide.clear(viewHolder.playbutton);
viewHolder.playbutton.setVisibility(View.GONE);
Glide.clear(viewHolder.postText);
viewHolder.postText.setVisibility(View.GONE);
viewHolder.listphoto.setVisibility(View.VISIBLE);
viewHolder.listphoto.setBottom(0);
Glide.with(mContext).load(post.getFilename().toString())
.diskCacheStrategy(DiskCacheStrategy.ALL).dontAnimate()
.into(viewHolder.listphoto);
}
if (post.getType().equals("text")) {
Glide.clear(viewHolder.videoThumb);
viewHolder.videoThumb.setVisibility(View.GONE);
viewHolder.rainbow.setVisibility(View.GONE);
Glide.clear(viewHolder.playbutton);
viewHolder.playbutton.setVisibility(View.GONE);
Glide.clear(viewHolder.listphoto);
viewHolder.listphoto.setVisibility(View.GONE);
viewHolder.postText.setVisibility(View.VISIBLE);
viewHolder.postText.setText(post.getText());
}
}
if (Hawk.contains("liked" + post.getId().get$oid())) {
viewHolder.likesPic.clearColorFilter();
Glide.with(mContext).load(R.drawable.heartroundorange).into(viewHolder.likesPic);
((ImageView) viewHolder.likesPic).setColorFilter(Color.parseColor("#ff3a6f"));
} else {
Glide.with(mContext).load(R.drawable.heartroundgray).diskCacheStrategy(DiskCacheStrategy.ALL)
.into(viewHolder.likesPic);
}
if (Hawk.contains("mapped" + post.getId().get$oid())) {
viewHolder.mapitPic.clearColorFilter();
((ImageView) viewHolder.mapitPic).setImageResource(R.drawable.dropmaincolororange);
((ImageView) viewHolder.mapitPic).setColorFilter(Color.parseColor("#444444"));
} else {
Glide.with(mContext).load(R.drawable.dropdarkgray).diskCacheStrategy(DiskCacheStrategy.ALL)
.into(viewHolder.mapitPic);
}
if (!Hawk.contains("mapped" + post.getId().get$oid())) {
viewHolder.mapitPic.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Hawk.put("mapped" + post.getId().get$oid(), 1);
((ImageView) viewHolder.mapitPic).setImageResource(R.drawable.dropmaincolororange);
viewHolder.footprints.setText(String.valueOf(post.getLocation().size() + 1));
post.getLocation().add(new double[]{PostListFragment.lon, PostListFragment.lat});
notifyDataSetChanged();
Thread t = new Thread(new Runnable() {
#Override
public void run() {
postMappedToServer(post.getId().get$oid());
}
});
t.start();
TastyToast.makeText(mContext, "Post dropped off here.", TastyToast.LENGTH_SHORT, TastyToast.CONFUSING);
}
});
} else {
viewHolder.mapitPic.setClickable(false);
}
if (!Hawk.contains("liked" + post.getId().get$oid())) {
viewHolder.likesPic.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Hawk.put("liked" + post.getId().get$oid(), 1);
viewHolder.likesPic.setClickable(false);
((ImageView) viewHolder.likesPic).setImageResource(R.drawable.heartroundorange);
viewHolder.likesTextView.setText(String.valueOf(post.getLikes() + 1));
post.setLikes(post.getLikes() + 1);
notifyDataSetChanged();
Thread t = new Thread(new Runnable() {
#Override
public void run() {
postLikeToServer(post);
}
});
t.start();
}
});
} else {
viewHolder.likesPic.setClickable(false);
}
if (post.getType() == null || post.getType().equals("video"))
viewHolder.videoThumb.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (VListAdapter.this.mContext instanceof ProfileFeed) {
((ProfileFeed) VListAdapter.this.mContext).closeActivity();
}
Intent broadcast = new Intent();
broadcast.setAction("com.molehead.openout.POST");
broadcast.putExtra("postId", post.getFilename().toString());
broadcast.putExtra("hawkId", post.getId().get$oid());
broadcast.putExtra("s3link", post.getS3link());
broadcast.putExtra("username", post.getUsername());
if (Hawk.contains("liked" + post.getId().get$oid()))
broadcast.putExtra("liked", "yes");
else
broadcast.putExtra("liked", "no");
broadcast.putExtra("likecount", post.getLikes().toString());
App.post = post;
LocalBroadcastManager.getInstance(mContext.getApplicationContext()).sendBroadcast(broadcast);
}
});
viewHolder.moremenu.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
PopupMenu popup = new PopupMenu(mContext.getApplicationContext(), viewHolder.moremenu, Gravity.CENTER);
//Inflating the Popup using xml file
popup.getMenuInflater().inflate(R.menu.menu_main, popup.getMenu());
//registering popup with OnMenuItemClickListener
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_share:
String postId = post.getId().get$oid();
Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
sharingIntent.setType("text/plain");
String shareBody = postId + ".jpg"; //https://openout.herokuapp.com/posts/" + postId;
String shareSub = "Shared via Molehead";
sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, shareSub);
sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, shareBody);
sharingIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Intent new_intent = Intent.createChooser(sharingIntent, "Share");
new_intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.getApplicationContext().startActivity(new_intent);
break;
}
return true;
}
});
popup.show();
}
});
return rowView;
}
private void initHawkWithDataFromServer() {
SharedPreferences settings = mContext.getApplicationContext().getSharedPreferences("userinfo", 0);
String username = settings.getString("username", "ok");
String password = settings.getString("password", "ok");
LoginService loginService =
ServiceGenerator.createService(LoginService.class, username, password);
final Call<List<Post>> call = loginService.getLikes(username);
Log.i("lonlat", String.valueOf(lon) + " and " + String.valueOf(lat));
call.enqueue(new Callback<List<Post>>() {
#Override
public void onResponse(Call<List<Post>> call, Response<List<Post>> response) {
ArrayList<Post> posts = new ArrayList<>();
posts = (ArrayList<Post>) response.body();
if (!posts.isEmpty())
for (Post p : posts) {
Hawk.put("liked" + p.getId().get$oid(), 1);
}
}
#Override
public void onFailure(Call<List<Post>> call, Throwable t) {
}
});
}
private void postMappedToServer(String oid) {
SharedPreferences settings = mContext.getSharedPreferences("userinfo", 0);
String username = settings.getString("username", "ok");
String password = settings.getString("password", "ok");
LoginService loginService =
ServiceGenerator.createService(LoginService.class, username, password);
Log.i("postlistfraglat", String.valueOf(PostListFragment.lat));
Call<ResponseBody> call = loginService.addLocation(oid, PostListFragment.lon, PostListFragment.lat);
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (response.isSuccessful())
Log.i("mapped", "success");
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
});
}
public void postLikeToServer(Post post) {
SharedPreferences settings = mContext.getSharedPreferences("userinfo", 0);
String username = settings.getString("username", "ok");
String password = settings.getString("password", "ok");
LoginService loginService =
ServiceGenerator.createService(LoginService.class, username, password);
Call<ResponseBody> call = loginService.like(post, 1, username);
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (response.isSuccessful()) {
try {
Log.i("call", response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.i("MFEED", "like request failed");
}
});
}
public static String format(long value) {
//Long.MIN_VALUE == -Long.MIN_VALUE so we need an adjustment here
if (value == Long.MIN_VALUE) return format(Long.MIN_VALUE + 1);
if (value < 0) return "-" + format(-value);
if (value < 1000) return Long.toString(value); //deal with easy case
Map.Entry<Long, String> e = suffixes.floorEntry(value);
Long divideBy = e.getKey();
String suffix = e.getValue();
long truncated = value / (divideBy / 10); //the number part of the output times 10
boolean hasDecimal = truncated < 100 && (truncated / 10d) != (truncated / 10);
return hasDecimal ? (truncated / 10d) + suffix : (truncated / 10) + suffix;
}
static class ViewHolder {
private TextView titleTextView;
private TextView timeago;
private TextView likesTextView;
private TextView viewcount;
private TextView distance;
private TextView footprints;
private ImageView profilePic;
private ImageView moremenu;
private ImageView likesPic;
private ImageView mapitPic;
private ImageView rainbow;
//private ImageView sharebutton;
private TextView caption;
private ImageView listphoto;
private ImageView videoThumb;
private ImageView playbutton;
private TextView postText;
private Post post;
}
private float getHeight(float height, float width) {
WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
return (height * size.x / width);
}
}
It is impossible to point to a specific issue because there is so much code in your Adapter. One thing is sure, though - switching to RecyclerView won't help you in this case.
Adapters should not contain business logic - they should only "adapt" input objects to the underlying Views. In your case, it seems like the adapter performs calculations, spawns new threads, performs network requests, etc.
You need to refactor your code such that the adapter will be similar to this:
public class PostsListAdapter extends ArrayAdapter<Post> {
private Context mContext;
public PostsListAdapter(Context context, int resource) {
super(context, resource);
mContext = context;
}
public void bindPosts(List<Post> posts) {
clear();
addAll(posts);
notifyDataSetChanged();
}
#NonNull
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
// assign new View to convertView
// create new ViewHolder
// set ViewHolder as tag of convertView
// set listeners
} else {
// get a reference to existing ViewHolder
}
// populate ViewHolder's elements with data from getItem(position)
// kick off asynchronous loading of images
// NOTE: no calculations allowed here - just simple bidding of data to Views
return convertView;
}
}
Your code needs to be structured in such a way, that business logic that involves calculations and transformation of data executed before you bind a new data to ListView, and Post objects that you pass to bindPosts() method already contain the results of the aforementioned calculations and transformations.
Adapter just "adapts" the final data from Posts to Views - nothing more.
If you're short on time now, and just need to "make it work", then I would start by removing the logic that spawns new threads and makes network requests. See if this improves performance.
Change your implementation to RecyclerView which is more efficient in terms of scrapping views or recycling.
We can also enable optimizations if the items are static and will not change for significantly smoother scrolling:
recyclerView.setHasFixedSize(true);
Create an intent service and register BroadcastReceiver as data return callback or error callback when api request, business rule, data modification completed. Use synchronous call to execute initHawkWithDataFromServer() in advance and after getting result from api continue modifying or applying business logic. After that create new adapter or update existing adapter data set.
Move all the below data calculation or data value formatting logic from adapter's getView() to above intent service.
You can add more getter and setter to existing Post pojo.
DateTime dateTime = new DateTime(post.getUploadDate().get$date());
viewHolder.timeago.setText(prettyTime.format(dateTime.toDate()));
viewHolder.likesTextView.setText(String.valueOf(format(post.getLikes())));
viewHolder.footprints.setText(String.valueOf(format(post.getLocation().size)) - 1)));
Post{
//Your existing property
#Expose(serialize = false, deserialize = false)
//equals neither serialize nor deserialize or
private DateTime uploadedDateTime;
//etc. prettyTime.format, String.valueOf
}
Removes unnecessary reflection:
GsonBuilder builder = new GsonBuilder();
builder.excludeFieldsWithoutExposeAnnotation();
Gson gson = builder.create();
new Retrofit.Builder().addConverterFactory(GsonConverterFactory.create(gson)).build();
and it to your retrofit Service creation class.
You can also use transient(private transient DateTime uploadedDateTime;)
Remove
public void addElement(Post post) { mDataSource.add(0, post);
this.notifyDataSetChanged();}
and whenever you need to notify if a single or more items inserted, deleted etc. Use the below:
notifyItemChanged(int)
notifyItemInserted(int)
notifyItemRemoved(int)
notifyItemRangeChanged(int, int)
notifyItemRangeInserted(int, int)
notifyItemRangeRemoved(int, int)
We can use these from the activity or fragment:
//Add a new contact
items.add(0, new Post("Barney"));
//Notify the adapter that an item was inserted at position 0
adapter.notifyItemInserted(0);
Above methods are more efficient. Every time we want to add or remove items from the RecyclerView, we will need to explicitly inform to the adapter of the event. Unlike the ListView adapter, a RecyclerView adapter should not rely on notifyDataSetChanged() since the more granular actions should be used. See the API documentation for more details
Also, if you are intending to update an existing list, make sure to get the current count of items before making any changes. For instance, a getItemCount() on the adapter should be called to record the first index that will be changed.
// record this value before making any changes to the existing list
int curSize = adapter.getItemCount(); // replace this line with wherever you get new records
ArrayList<Post> newItems = Post.createPostsList(20);
// update the existing list
items.addAll(newItems);
// curSize should represent the first element that got added
// newItems.size() represents the itemCount
adapter.notifyItemRangeInserted(curSize, newItems.size());
Diffing Larger Changes
A new DiffUtil class has been added in the v24.2.0 of the support library to help compute the difference between the old and new list. Details
Don't preload images via glide if your image sizes are different. Try creating your own. Also try looking at.
Create color as the class member
int color = Color.parseColor("#dddddd");
Write View.GONE or View.VISIBLE in Post pojo itself, which will be executed in background thread from Retrofit if IntentService. Try api return boolean in json instead "0" as String.
Move all below to IntentService
//don't display 0 if there are no likes, just show heart icon
if (viewHolder.likesTextView.getText().equals("0"))
viewHolder.likesTextView.setVisibility(View.GONE);
else
viewHolder.likesTextView.setVisibility(View.VISIBLE);
//don't display 0 if there are no footprints
if (viewHolder.footprints.getText().equals("0"))
viewHolder.footprints.setVisibility(View.GONE);
else
viewHolder.footprints.setVisibility(View.VISIBLE);
double[] loc = post.getLocation().get(0);
viewHolder.distance.setText("~" + PostListFragment.distance(loc[0], loc[1], 'M') + " Miles");
All String concatenation also in Post or IntnetService like:
String profilePictureS3Url = "https://s3-us-west-2.amazonaws.com/moleheadphotos/" + post.getUsername() + ".jpg";
Also you can create color filter in advance and one time only. Remove scrollbar from listview as it calculates height to show scroll bar.
Too many things to improve here. Here is some examples.
I see this
if (Hawk.count() == 0)
initHawkWithDataFromServer();
I believe that the method initHawkWithDataFromServer will be called many times during the time the list appears.
This call can be done only once when the activity was created.
Glide.with(mContext).load(videoThumbURL).fitCenter()
But you should refactor your code first, moving the logic to another class. Try to remove some code like this (it should be done by using some layout attribites)
ViewGroup.LayoutParams params = viewHolder.listphoto.getLayoutParams();
Resources r = mContext.getResources();
height = (int) getHeight(height, width);
params.height = height;
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
viewHolder.listphoto.setLayoutParams(params);

Android Error: IllegalStateException

I am working on a Bitcoin dashboard for Android. The following fragment uses the entered wallet address to display the balance in BTC. When an address is entered, it will add to the listview. When an item in the listview is selected, it will set the edittext to that address.
It is not yet complete, but for now it is throwing an error with the message, "The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread."
I currently have two example addresses in place for testing. If I select one then the other then the first again etc. it works fine. The error appears when I select one, press the button, then select the other.
public class WalletFragment extends Fragment {
ArrayList<String> savedWallets;
ArrayAdapter<String> listAdapter;
String newWalletAddress, jsonString, address, balance;
JSONObject jsonObj, data;
Double balanceDouble;
DecimalFormat df = new DecimalFormat("#.####");
private WalletListener listener;
public interface WalletListener {
void onCreateWallet(String newWalletAddress);
}
public WalletFragment() {
// Required empty public constructor
}
public static WalletFragment newInstance(ArrayList<String> wallets) {
WalletFragment fragment = new WalletFragment();
Bundle args = new Bundle();
args.putStringArrayList("savedWallets", wallets);
fragment.setArguments(args);
return fragment;
}
public static WalletFragment newInstance(ArrayList<String> wallets, String json) {
WalletFragment fragment = new WalletFragment();
Bundle args = new Bundle();
args.putStringArrayList("savedWallets", wallets);
args.putString("jsonString", json);
fragment.setArguments(args);
return fragment;
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof WalletListener) {
listener = (WalletListener) context;
}
else {
throw new ClassCastException(context.toString()
+ " must implement MyListFragment.OnItemSelectedListener");
}
}
#Override
public void onDetach() {
super.onDetach();
listener = null;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_wallet, container, false);
ListView lv = (ListView) v.findViewById(R.id.walletListView);
df.setRoundingMode(RoundingMode.CEILING);
final EditText walletAddressEditText = (EditText) v.findViewById(R.id.walletAddressEditText);
TextView addressTV = (TextView) v.findViewById(R.id.walletAddresstextView);
TextView balanceTV = (TextView) v.findViewById(R.id.walletBalanceTextView);
savedWallets = getArguments().getStringArrayList("savedWallets");
if (savedWallets == null) {
savedWallets = new ArrayList<>();
}
savedWallets.add("198aMn6ZYAczwrE5NvNTUMyJ5qkfy4g3Hi");
savedWallets.add("1L8meqhMTRpxasdGt8DHSJfscxgHHzvPgk");
// TODO remove test addresses
jsonString = getArguments().getString("jsonString");
if (jsonString != null) {
try {
jsonString = getArguments().getString("jsonString");
jsonObj = new JSONObject(jsonString);
data = new JSONObject(jsonObj.getString("data"));
balance = data.getString("balance");
balanceDouble = Double.parseDouble(balance);
address = data.getString("address");
String walletAddressText = getResources().getString(R.string.wallet_address, address);
addressTV.setText(walletAddressText);
String walletBalanceText = getResources().getString(R.string.wallet_balance, df.format(balanceDouble));
balanceTV.setText(walletBalanceText);
// TODO add viewing for other wallet data at some point
} catch (Exception e) {
Log.d("TickerException", e.toString());
}
}
listAdapter = new ArrayAdapter<>(getActivity(), R.layout.main_list_rows, savedWallets);
lv.setAdapter(listAdapter);
lv.setOnItemClickListener(new AdapterView.OnItemClickListener()
{
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
String address = savedWallets.get(position);
Log.d("wallet", "Selected: " + address);
walletAddressEditText.setText(address);
}
});
Button button = (Button) v.findViewById(R.id.createWalletButton);
View.OnClickListener ocl = new View.OnClickListener() {
#Override
public void onClick(View view) {
newWalletAddress = walletAddressEditText.getText().toString();
if (walletAddressEntryStructuralValidation(newWalletAddress)) {
if (newWalletAddress != null) {
listener.onCreateWallet(newWalletAddress);
}
else {
Toast.makeText(getActivity(), "newWalletAddress is null", Toast.LENGTH_SHORT).show();
}
}
else {
Toast.makeText(getActivity(), "Please enter a valid wallet address (length is currently " + newWalletAddress.length() + ").", Toast.LENGTH_SHORT).show();
}
}
};
// TODO check if wallet is already on list
button.setOnClickListener(ocl);
return v;
}
public boolean walletAddressEntryStructuralValidation(String address) {
return ((address.length() > 25) &&
(address.length() < 36) && (
(address.substring(0,1).equals("1") ||
(address.substring(0,1).equals("3")))));
}
// Wallet addresses are 26-35 alphanumeric characters and begin with 1 or 3
}
I believe this is all the relevant code but I will be closely watching this thread if anyone needs to request additional source.
That message means that the contents of the adapter (the order of items you see in getItem) changed but notifyDataSetChanged or similar function wasn't called. When changing the items in your adapter contents (which in this case is the savedWallets array list) you must call one of those functions.
Note: If you're adding several objects at once, you only need to call it once after all are added/removed. If you're mutating an object but not adding/removing it, you do not need to call it, but calling it may be the easiest way of doing a redraw.

Good practice to use Fragments and ViewPager

Problem Description
I am writing application which use fragments and ViewPager below you can see my FragmentPagerAdapter. I just want to know is it right way to write FragmentPagerAdapter? because in some cases when I call getActivity() in fragment it return null.
public class FragmentAdapter extends FragmentPagerAdapter {
/* Fragments */
private Fragments[] mFragments;
/**
* Constructor.
* #param fm
*/
public FragmentAdapter(FragmentManager fm) {
super(fm);
/* Initialize Fragments. */
mFragments = Fragments.values();
}
public enum Fragments {
Favorites(App.getStringByResId(R.string.favorites), new FragmentFavorites()),
Categories(App.getStringByResId(R.string.categories), new FragmentCategories()),
YellowPages(App.getStringByResId(R.string.yellow_pages), new FragmentYellowPages());
/**
* Constructor.
* #param title Fragment title.
* #param fragment Fragment object.
*/
Fragments(String title, BaseListFragment fragment) {
this.mTitle = title;
this.mFragment = fragment;
}
/* Fragment Title Text. */
private String mTitle;
/* Fragment */
private BaseListFragment mFragment;
/**
* Get Fragment Title.
* #return Title.
*/
public String getTitle() {
return mTitle;
}
/**
* Get Fragment
* #return Fragment.
*/
public BaseListFragment getFragment() {
return mFragment;
}
};
#Override
public CharSequence getPageTitle(int position) {
return mFragments[position].getTitle();
}
#Override
public Fragment getItem(int position) {
return mFragments[position].getFragment();
}
#Override
public int getCount() {
return mFragments.length;
};
};
Edited
from main activity I call this code. is it correct ? In most cases in this case getActivity() return null.
/* Call Search function for the given fragment. */
Fragments.values()[tabControl.getCurrentItem()].getFragment().search(tv.getText().toString());
Fragment
public class FragmentFavorites extends BaseListFragment {
final static String TAG = FragmentFavorites.class.getSimpleName();
/* Controllers. */
ListView lvFavorites;
TextView tvInfo;
/* Simple Cursor Adapter. */
SimpleCursorAdapter scAdapter;
/* This member keep current database language, every time then onResume method called
* application must check if database language was changed, if language changed application
* must update list view by creating new adapter. */
String language;
/* This flag shows that new favorite was added and list need to be updated. */
boolean newFavoriteAdded = false;
boolean canUpdateOnSearch = false;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View favoritesPagesView = inflater.inflate(R.layout.fragment_favorites, container, false);
Log.i(TAG, "onCreateView( )");
/* Initialize Controllers. */
tvInfo = (TextView) favoritesPagesView.findViewById(R.id.tvNoFavoritesText);
lvFavorites = (ListView) favoritesPagesView.findViewById(android.R.id.list);
return favoritesPagesView;
} /* onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(TAG, "onCreate( )");
/* Tell the framework to try to keep this fragment around
during a configuration change. */
setRetainInstance(true);
/* Get context, if context is null set default language "English". */
final Context context = getActivity();
if (context != null) {
Log.wtf(TAG, "onCreate(Context is null, set default language \"en\".)");
/* Set language when fragment first created, default language is "English". */
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
language = preferences.getString(Preferences.LANGUAGE, Preferences.EN);
}
else language = Preferences.EN;
} /* onCreate(Bundle savedInstanceState) */
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.i(TAG, "onActivityCreated( )");
ListView listView = getListView();
/* Set on item click listener */
listView.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapterView, View v, int position, long id) {
Cursor cursor = (Cursor)adapterView.getAdapter().getItem(position);
final String _id = cursor.getString(cursor.getColumnIndex("_id"));
Log.i(TAG, "onItemClick(_id = " + _id + ")");
/* Get current selected language. */
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
final String lang = preferences.getString(Preferences.LANGUAGE, "en");
ActionDialog dialog = new ActionDialog(getActivity());
dialog.setFavoriteRemovedListener(new OnActionDialogListeners() {
#Override
public void onFavoriteRemoved() {
/* Get Saved favorites from the preferences, if there are no favorites return form onResume. */
final String favorites = preferences.getString(Preferences.FAVORITE, null);
/* Update favorites adapter with new favorite contacts. */
updateAdapterIfNeeded(lang, favorites, true);
}
#Override
public void onFavoriteAdded() { /* Not used... */ }
});
dialog.setID(_id);
dialog.setDeleteFavorite(true);
dialog.setTitle(cursor.getString(cursor.getColumnIndex(String.format("name_%s", language))));
dialog.setPhone(cursor.getString(cursor.getColumnIndex("telephones")));
dialog.show();
}
});
} /* onActivityCreated(Bundle savedInstanceState) */
#Override
public void onResume() {
super.onResume();
Log.i(TAG, "onResume( )");
/* Check if database current language was changed. */
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
final String currentLanguage = preferences.getString(Preferences.LANGUAGE, Preferences.EN);
/* Get Saved favorites from the preferences, if there are no favorites return form onResume. */
final String favorites = preferences.getString(Preferences.FAVORITE, null);
if (favorites == null || favorites.length() == 0) {
/* Hide List View and show Text View. */
Log.i(TAG, "There are no saved favorites in the preferences.");
lvFavorites.setVisibility(View.GONE);
tvInfo.setVisibility(View.VISIBLE);
tvInfo.setText(getActivity().getString(R.string.no_selected_favorites));
return;
}
if (!currentLanguage.equalsIgnoreCase(language)) {
Log.i(TAG, "onResume(Database language was changed.)");
/* Update language with changed one. */
language = currentLanguage;
/* Update Adapter, do not pay attention if it is already created or no. */
updateAdapterIfNeeded(currentLanguage, favorites, true);
}
else if (newFavoriteAdded) {
Log.i(TAG, "onResume(New favorite was added to the list, update needed");
/* Update adapter as new favorite was added. */
updateAdapterIfNeeded(currentLanguage, favorites, true);
newFavoriteAdded = false;
}
else {
/* Update Adapter only in the case if no adapter is created (This will create new adapter) */
updateAdapterIfNeeded(currentLanguage, favorites, false);
}
} /* void onResume() */
/**
* This function update Simple Cursor Adapter in the case if it is needed or
* cursor adapter is not created yet.
*
* #param currentLanguage Current language of database.
* #param forceUpdate if true adapter will be updated in any case; otherwise it will
* not be updated.
*/
void updateAdapterIfNeeded(final String lang, final String favs, final boolean forceUpdate) {
Log.i(TAG, String.format("updateAdapterIfNeeded(%s, %b)", lang, forceUpdate));
/* Show "Favorites" list view and hide Info text view. */
lvFavorites.setVisibility(View.VISIBLE);
tvInfo.setVisibility(View.GONE);
if (forceUpdate || scAdapter == null) {
/* Preparing Adapter Settings. */
final int[] to = new int[]{ R.id.tvContactTitle, R.id.tvContactTelephone };
final String[] from = new String[] { String.format("name_%s", lang), "telephones" };
/* Get Contacts from the SQLite Database. */
final Cursor cursor = DataBaseManager.instance().getAllFavorites(favs, lang);
Log.i(TAG, "updateAdapterIfNeeded(Creating new Adapter ...)");
/* Create New Simple Cursor Adapter, even if it is already exists. */
scAdapter = new FragmentCursorAdapter(getActivity(), R.layout.contact_row, cursor, from, to, 0);
Log.i(TAG, "updateAdapterIfNeeded(Adapter was successfully created ...)");
/* Set Adapter to the list. */
this.setListAdapter(scAdapter);
}
} /* updateAdapterIfNeeded(final String currentLanguage, boolean forceUpdate) */
void updateAdapterOnSearch(final String tts, final String lang, final String favs) {
Log.i(TAG, String.format("updateAdapterOnSearch(%s, %s)", tts, lang));
/* Show "Favorites" list view and hide Info text view. */
lvFavorites.setVisibility(View.VISIBLE);
tvInfo.setVisibility(View.GONE);
/* Preparing Adapter Settings. */
final int[] to = new int[]{ R.id.tvContactTitle, R.id.tvContactTelephone };
final String[] from = new String[] { String.format("name_%s", lang), "telephones" };
/* Get Contacts from the SQLite Database. */
final Cursor cursor = DataBaseManager.instance().getSearchedFavorites(favs, lang, tts);
if (cursor.getCount() == 0) {
lvFavorites.setVisibility(View.GONE);
tvInfo.setVisibility(View.VISIBLE);
tvInfo.setText(String.format(getActivity().getString(R.string.no_companies_were_found), tts));
}
Log.i(TAG, "updateAdapterOnSearch(Creating new Adapter ...)");
/* Create New Simple Cursor Adapter, even if it is already exists. */
scAdapter = new FragmentCursorAdapter(getActivity(), R.layout.contact_row, cursor, from, to, 0);
Log.i(TAG, "updateAdapterOnSearch(Adapter was successfully created ...)");
/* Set Adapter to the list. */
this.setListAdapter(scAdapter);
} /* updateAdapterOnSearch(final String tts, final String lang) */
#Override
public void search(final String tts) {
final Context context = getActivity();
Log.i(TAG, String.format("search(%s)", tts));
/* Get application context. */
if (context == null) {
Log.wtf(TAG, "search(Search can't be completed as context is null)");
return;
}
/* Get Database current language. */
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
if (preferences == null) {
Log.w(TAG, "search(Search can't be completed as preferences object is null)");
return;
}
/* Get language from the preferences */
final String lang = preferences.getString(Preferences.LANGUAGE, Preferences.EN);
/* Get Saved favorites from the preferences, if there are no favorites return form onResume. */
final String favorites = preferences.getString(Preferences.FAVORITE, null);
/* If user search for the text. */
if (tts != null && tts.length() != 0) {
updateAdapterOnSearch(tts, lang, favorites);
canUpdateOnSearch = true;
}
if ((tts == null || tts.length() == 0) && canUpdateOnSearch) {
/* In this case all contacts will be shown in the list view. */
updateAdapterIfNeeded(lang, favorites, true);
canUpdateOnSearch = false;
}
} /* search(final String tts) */
public void newFavoriteAdded(final boolean value) {
Log.i(TAG, String.format("newFavoriteAdded(%b)", value));
newFavoriteAdded = value;
}
};
You should call getActivity() only inside or after Fragment.onActivityCreated() is called, in this way activity will never be null, as in example.
Also here is another case of where null activity may occurs, when you start thread inside fragment, and call getActivity() while user already closed fragment.
new Thread() {
#Override
public void run() {
// long operations goes here
// user decided to close fragment
Activity activity = getActivity();
// here 'activity' is null because fragment is destroyed
}
}.start();

Adapter not display content

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().

Categories

Resources