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