0

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());
        }
    }; 
} 

2 Answers2

0

Use this in your Adapter

 public void setWarnings(List<Warning> warnings) {
        mWarnings.clear();
        mWarnings = warnings;
        notifyDataSetChanged();

    }
Jawad Ahmed
  • 306
  • 1
  • 5
  • 9
0

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();
Sabbir Ahmed
  • 351
  • 1
  • 13
  • Thanks for the quick response Sabbir, I tried your code but apart from the introduced delay, it didn't change the behaviour. Any other suggestions? – GeNextAppLabs Jul 15 '19 at 12:13