In my app, there are two methods getData and getItemsByLabel. Each one is getting different lists by retrofit Callback method, and I used the navigation drawer method onNavigationItemSelected so that whenever a user clicks on a particular item, a different list is displayed in the RecyclerView.
The problem is that I use a method addOnScrollListener to detect scroll behavior from any list on RecyclerView which causes the items overlapping in the displayed list.
Hence the problem is when scrolling down occurs, the items from the main list and the list of the selected category/item are overlapping.
Here is my code.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Fabric.with(this, new Crashlytics());
// Crashlytics.logException(new Exception("My first Android non-fatal error"));
// I'm also creating a log message, which we'll look at in more detail later
// Crashlytics.log("MainActivity started");
swipeRefreshLayout = findViewById(R.id.swipeRefreshLayout);
recyclerView = findViewById(R.id.recyclerView);
manager = new LinearLayoutManager(this);
emptyView = (TextView) findViewById(R.id.empty_view);
progressBar = findViewById(R.id.spin_kit);
adapter = new PostAdapter(this, items);
recyclerView.setLayoutManager(manager);
recyclerView.setAdapter(adapter);
toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayShowTitleEnabled(false);
toolbar.setTitle(R.string.home);
DrawerLayout drawer = findViewById(R.id.drawer_layout);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawer.addDrawerListener(toggle);
toggle.syncState();
NavigationView navigationView = findViewById(R.id.nav_view);
navigationView.getMenu().getItem(0).setChecked(true);
navigationView.setNavigationItemSelectedListener(this);
swipeRefreshLayout.setColorSchemeColors(getResources().getColor(R.color.colorPrimaryGreen));
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
if (navigationView.getMenu().getItem(0).isChecked()) {
if (Utils.hasNetworkAccess(MainActivity.this)) {
getData();
} else {
Toast.makeText
(MainActivity.this, "You must connect to the Internet to update the list"
, Toast.LENGTH_LONG).show();
}
} else {
for (int i = 1; i < 7; i++) {
if (navigationView.getMenu().getItem(i).isChecked()) {
getItemsByLabel(navigationView.getMenu().getItem(i).getTitle().toString());
}
}
}
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
swipeRefreshLayout.setRefreshing(false);
}
}, 3000);
}
});
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
isScrolling = true;
if (!recyclerView.canScrollVertically(1)) {
progressBar.setVisibility(View.GONE);
}
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dy > 0) {
currentItems = manager.getChildCount();
totalItems = manager.getItemCount();
scrollOutItems = manager.findFirstVisibleItemPosition();
if (isScrolling && (currentItems + scrollOutItems == totalItems)) {
isScrolling = false;
getData(); // This is where I call getData <--
}
}
}
});
if (Utils.hasNetworkAccess(this)) {
getData();
} else {
if (runtimeExceptionDaoItems == null || runtimeExceptionDaoItems.queryForAll().isEmpty()) {
Toast.makeText(this, "There's no data", Toast.LENGTH_LONG).show();
} else {
items.addAll(runtimeExceptionDaoItems.queryForAll());
Toast.makeText(this, "From Database", Toast.LENGTH_LONG).show();
}
}
}
long lastPress;
Toast backpressToast;
@Override
public void onBackPressed() {
DrawerLayout drawer = findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
long currentTime = System.currentTimeMillis();
if (currentTime - lastPress > 5000) {
backpressToast = Toast.makeText(getBaseContext(), "Press back again to exit", Toast.LENGTH_LONG);
backpressToast.show();
lastPress = currentTime;
} else {
if (backpressToast != null) backpressToast.cancel();
super.onBackPressed();
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE);
SearchView searchView = (SearchView) menu.findItem(R.id.app_bar_search).getActionView();
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
searchView.setQueryHint(getResources().getString(R.string.searchForPosts));
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String keyword) {
getItemsBySearch(keyword);
return false;
}
@Override
public boolean onQueryTextChange(String keyword) {
return false;
}
});
searchView.setOnCloseListener(() -> {
emptyView.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
getData();
return false;
});
return true;
}
@SuppressWarnings("StatementWithEmptyBody")
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
// Handle navigation view item clicks here.
switch (item.getItemId()) {
case R.id.home:
getData();
break;
case R.id.accessory:
toolbar.setTitle(R.string.accessory);
getItemsByLabel("Accessory");
break;
case R.id.arcade:
toolbar.setTitle(R.string.arcade);
getItemsByLabel("Arcade");
break;
case R.id.fashion:
toolbar.setTitle(R.string.fashion);
getItemsByLabel("Fashion");
break;
case R.id.food:
toolbar.setTitle(R.string.food);
getItemsByLabel("Food");
break;
case R.id.heath:
toolbar.setTitle(R.string.heath);
getItemsByLabel("Heath");
break;
case R.id.lifeStyle:
toolbar.setTitle(R.string.lifestyle);
getItemsByLabel("Lifestyle");
break;
case R.id.sports:
toolbar.setTitle(R.string.sports);
getItemsByLabel("Sports");
break;
case R.id.settings:
break;
}
DrawerLayout drawer = findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
return true;
}
private void getData() {
progressBar.setVisibility(View.VISIBLE);
String url = BloggerAPI.BASE_URL + "?key=" + BloggerAPI.KEY;
if (token != "") {
url = url + "&pageToken=" + token;
}
if (token == null) {
return;
}
final Call<PostList> postList = BloggerAPI.getService().getPostList(url);
postList.enqueue(new Callback<PostList>() {
@Override
public void onResponse(@NonNull Call<PostList> call, @NonNull Response<PostList> response) {
if (response.isSuccessful()) {
progressBar.setVisibility(View.GONE);
PostList list = response.body();
if (list != null) {
token = list.getNextPageToken();
items.addAll(list.getItems());
adapter.notifyDataSetChanged();
} else {
progressBar.setVisibility(View.GONE);
recyclerView.setVisibility(View.GONE);
emptyView.setVisibility(View.VISIBLE);
int sc = response.code();
switch (sc) {
case 400:
Log.e("Error 400", "Bad Request");
break;
case 404:
Log.e("Error 404", "Not Found");
break;
default:
Log.e("Error", "Generic Error");
}
}
}
@Override
public void onFailure(@NonNull Call<PostList> call, @NonNull Throwable t) {
Toast.makeText(MainActivity.this, "Error occured", Toast.LENGTH_LONG).show();
Log.e(TAG, "onFailure: " + t.toString());
Log.e(TAG, "onFailure: " + t.getCause());
progressBar.setVisibility(View.GONE);
recyclerView.setVisibility(View.GONE);
emptyView.setVisibility(View.VISIBLE);
}
});
}
//=============================================================================================
public void getItemsByLabel(String label) {
progressBar.setVisibility(View.VISIBLE);
String url = BloggerAPI.BASE_URL + "search?q=label:" + label + "&key=" + BloggerAPI.KEY;
Log.e("Label :", url);
if (token != "") {
url = url + "&pageToken=" + token;
}
if (token == null) {
return;
}
final Call<PostList> postList = BloggerAPI.getService().getPostList(url);
postList.enqueue(new Callback<PostList>() {
@Override
public void onResponse(@NonNull Call<PostList> call, @NonNull Response<PostList> response) {
if (response.isSuccessful()) {
progressBar.setVisibility(View.GONE);
items.clear();
recyclerView.swapAdapter(adapter, false);
PostList list = response.body();
if (list != null) {
token = list.getNextPageToken();
items.addAll(list.getItems());
adapter.notifyDataSetChanged();
}
} else {
progressBar.setVisibility(View.GONE);
recyclerView.setVisibility(View.GONE);
emptyView.setVisibility(View.VISIBLE);
int sc = response.code();
switch (sc) {
case 400:
Log.e("Error 400", "Bad Request");
break;
case 404:
Log.e("Error 404", "Not Found");
break;
default:
Log.e("Error", "Generic Error");
}
}
}
@Override
public void onFailure(@NonNull Call<PostList> call, @NonNull Throwable t) {
Toast.makeText(MainActivity.this, "Error occured", Toast.LENGTH_LONG).show();
Log.e(TAG, "onFailure: " + t.toString());
Log.e(TAG, "onFailure: " + t.getCause());
progressBar.setVisibility(View.GONE);
recyclerView.setVisibility(View.GONE);
emptyView.setVisibility(View.VISIBLE);
}
});
}
Methods that I tried to solve this problem :
ScrollListener inside each method getData and getItemsByLabel
ScrollListener on it.onScrolled method to detect which item on the drawer menu is checked to get the list it's own. But unfortunately, none is working.I do not think the problem is arising for adding the common addOnScrollListener to your RecyclerView. I think this problem is occurring due to the implementation of control flow and for the race condition among threads while fetching the lists using API calls. Let me describe my idea briefly.
Both APIs called in getData and getItemsByLabel functions are asynchronous and hence you cannot assure when the API call returns with the data. Hence let us think about the following scenario:
getItemsByLabel called when you clicked an item in the navigation drawer.onScrolled function got called somehow and the API for getData triggered immediately. getItemsByLabel function returned with data, cleared the items list and started inserting new data to the items.getData API returned at the same time just after the data in the items list was cleared by getItemsByLabel and start adding elements in items.items list has elements from both getData and getItemsByLabel. ArrayList implementation is not thread-safe, hence it is prone to have data mix from both of the API response.I hope that explains the problem that you are having. To avoid this problem, you might consider having a boolean variable like getItemsByLabelCalled which might have value false by default. When an item is being clicked in the navigation drawer, set the value of getItemsByLabelCalled = true. Then check this value before adding elements into items list in the following to cases.
getData function. if(getItemsByLabelCalled) return
getItemsByLabel function, set and reset the value of getItemsByLabelCalled. Please check the following code in the UPDATE section.Hope that helps!
UPDATE
To give you a sample demonstration of what changes you should make:
Take the boolean for keeping track when the function is called.
private boolean getItemsByLabelCalled = false;
Now, modify the getData function like the following.
private void getData() {
if(getItemsByLabelCalled) return;
// Other statements are the same as before
}
Now modify the getItemsByLabel function to set the getItemsByLabelCalled variable.
public void getItemsByLabel(String label) {
// Here is the change
if(getItemsByLabelCalled) return;
else getItemsByLabelCalled = true;
progressBar.setVisibility(View.VISIBLE);
String url = BloggerAPI.BASE_URL + "search?q=label:" + label + "&key=" + BloggerAPI.KEY;
Log.e("Label :", url);
if (token != "") {
url = url + "&pageToken=" + token;
}
if (token == null) {
return;
}
final Call<PostList> postList = BloggerAPI.getService().getPostList(url);
postList.enqueue(new Callback<PostList>() {
@Override
public void onResponse(@NonNull Call<PostList> call, @NonNull Response<PostList> response) {
if (response.isSuccessful()) {
progressBar.setVisibility(View.GONE);
items.clear();
recyclerView.swapAdapter(adapter, false);
PostList list = response.body();
if (list != null) {
token = list.getNextPageToken();
items.addAll(list.getItems());
adapter.notifyDataSetChanged();
}
// Reset again here
getItemsByLabelCalled = false;
} else {
// Reset again here
getItemsByLabelCalled = false;
progressBar.setVisibility(View.GONE);
recyclerView.setVisibility(View.GONE);
emptyView.setVisibility(View.VISIBLE);
int sc = response.code();
switch (sc) {
case 400:
Log.e("Error 400", "Bad Request");
break;
case 404:
Log.e("Error 404", "Not Found");
break;
default:
Log.e("Error", "Generic Error");
}
}
}
@Override
public void onFailure(@NonNull Call<PostList> call, @NonNull Throwable t) {
Toast.makeText(MainActivity.this, "Error occured", Toast.LENGTH_LONG).show();
Log.e(TAG, "onFailure: " + t.toString());
Log.e(TAG, "onFailure: " + t.getCause());
progressBar.setVisibility(View.GONE);
recyclerView.setVisibility(View.GONE);
emptyView.setVisibility(View.VISIBLE);
// Reset again here
getItemsByLabelCalled = false;
}
});
}
I think no more changes are required, though I am not sure. Let me know if that works.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With