I am trying to make a layout with recyclerview something like the video. I made a recyclerview which update list after certain interval but the problem is after data update it scroll to top position automatically. I want to make something like the video. https://youtu.be/omcS-6LeKoo
I have tried with link from SO
RecyclerView scrolls to top position when change the adapter data RecyclerView notifyDataSetChanged scrolls to top position but unable to solve. below is my attempt
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.recyclerViewId);
handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
// Toast.makeText(getApplicationContext(),"Updating",Toast.LENGTH_LONG).show();
listShow();
handler.postDelayed(this,1000);
}
},1000);
}
void listShow(){
retrofitApiCall = RetrofitInstance.getRetrofitInstance().create(RetrofitApiCall.class);
Call<ModelClass_JSONParse> getDetails = retrofitApiCall;
anime = ExtendedAnime.getAll();
getDetails.enqueue(new Callback<ModelClass_JSONParse>() {
#Override
public void onResponse(Call<ModelClass_JSONParse> call,
Response<ModelClass_JSONParse> response) {
Log.v("Res",response.toString());
getWithValues = new HashMap<>();
if (response.isSuccessful()){
list.add(mModelClass_adapter);
}
adapter = new Adapter(getApplicationContext(),list);
StaggeredGridLayoutManager layoutManager = new
StaggeredGridLayoutManager(1,StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
adapter.notifyDataSetChanged();
}
}
#Override
public void onFailure(Call<ModelClass_JSONParse> call, Throwable t) {
Log.v("Res",call.toString());
}
});
}
These lines of code are causing the problem for you. You're setting a new adapter reference and linear layout manager reference every time of your API calling.
adapter = new Adapter(getApplicationContext(),list);
StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(1,StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
To Do your desired task you need to do following steps -
Just set your LayoutManager and adapter for the first time.
Make a setDataList method in your adapter class. And set your updated list to adapter list.
And then every time of calling API set that list to setDataList and call adapter.notifyDataSetChanged() method of your adapter class.
The above steps will solve your problem. Just give it a try.
The problem is probably because of you are setting new adapter reference in network callback method onResponse(). Try setting adapter in onCreate and then update dataset in callback.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.recyclerViewId);
recyclerView.setAdapter(yourAdapter);
}
In network callback,
#Override
public void onResponse(Call<ModelClass_JSONParse> call,
Response<ModelClass_JSONParse> response) {
Log.v("Res",response.toString());
getWithValues = new HashMap<>();
if (response.isSuccessful()){
adapter.setDataSet(newDataList) //not change adapter reference,only update data set
}
}
Implement setDataSet() method in your adapter to update list like below.
class YourAdapter extends RecyclerView.Adapter<>{
priavate List<> list = new ArrayList();
public void setDataSet(newList:List<>){
list.clear();
list.addAll(newList);
notifyDataSetChanged();
}
}
Don't use adapter.notifyDataSetChanged(); method because I think your main view must be wrap content so either set a fixed height like 150dp.
Try different methods like notifyItemChanged(), notifyItemRangeChanged(), notifyItemInserted()
You are setting adapter again and again when the response is changing, so you should change list and set adapter in onCreate.
Arraylist<ModelClass_adapter> list = new Arraylist<ModelClass_adapter>;
Adapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.recyclerViewId);
//set adapter here
adapter = new Adapter(getApplicationContext(),list);
StaggeredGridLayoutManager layoutManager = new
StaggeredGridLayoutManager(1,StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
// Toast.makeText(getApplicationContext(),"Updating",Toast.LENGTH_LONG).show();
listShow();
handler.postDelayed(this,1000);
}
},1000);
}
void listShow(){
retrofitApiCall = RetrofitInstance.getRetrofitInstance().create(RetrofitApiCall.class);
Call<ModelClass_JSONParse> getDetails = retrofitApiCall;
anime = ExtendedAnime.getAll();
getDetails.enqueue(new Callback<ModelClass_JSONParse>() {
#Override
public void onResponse(Call<ModelClass_JSONParse> call,
Response<ModelClass_JSONParse> response) {
Log.v("Res",response.toString());
getWithValues = new HashMap<>();
if (response.isSuccessful()){
list.clear();
list.add(mModelClass_adapter);
}
adapter.notifyDataSetChanged();
}
}
#Override
public void onFailure(Call<CurrencyModelClass_JSONParse> call, Throwable t) {
Log.v("Res",call.toString());
}
});
}
You are setting a new adapter every time and a new layout manager response comes.
which may cause this type of problem. you need to set adapter and layout manager in onCreate. just update adapter list in response of the api.
according to this answer you need linear layout manager only.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.recyclerViewId);
list= ArrayList<>();
adapter = new Adapter(getApplicationContext(),list);
LinearLayoutManager linearLayoutManager = new
LinearLayoutManager(context, OrientationHelper.VERTICAL, false);
recycleView.setLayoutManager(linearLayoutManager);
recyclerView.setAdapter(adapter);
handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
// Toast.makeText(getApplicationContext(),"Updating",Toast.LENGTH_LONG).show();
listShow();
handler.postDelayed(this,1000);
}
},1000);
}
void listShow(){
retrofitApiCall = RetrofitInstance.getRetrofitInstance().create(RetrofitApiCall.class);
Call<ModelClass_JSONParse> getDetails = retrofitApiCall;
anime = ExtendedAnime.getAll();
getDetails.enqueue(new Callback<ModelClass_JSONParse>() {
#Override
public void onResponse(Call<ModelClass_JSONParse> call,
Response<ModelClass_JSONParse> response) {
Log.v("Res",response.toString());
getWithValues = new HashMap<>();
if (response.isSuccessful()){
adapter.getList().add(mModelClass_adapter);
}
adapter.notifyDataSetChanged();
}
}
#Override
public void onFailure(Call<CurrencyModelClass_JSONParse> call, Throwable t) {
Log.v("Res",call.toString());
}
});
}
you can do by following way
First get the count of your current datalist
int position = datalist.size();
after adding data into datalist
call DataAdapter.notifyDataSetChanged();
then move cursor to position in recyclerview
recyclerView.scrollToPosition(position);
Happy coding...!
Related
I've tried a few different ways of fixing this, but it just doesn't seem to want to work. I have attached the fragment involved. The recyclerview works when I use the search function; However, when I first load the page, I get the error that
E/RecyclerView: No adapter attached; skipping layout
I think it might be an issue with the onCreate vs onCreateView, but I'm not exactly sure what to put where.
NewsFragment.java
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
v = inflater.inflate(R.layout.fragment_news, container, false);
swipeRefreshLayout = v.findViewById(R.id.swipeRefresh);
recyclerView = v.findViewById(R.id.news_recyclerView);
etQuery = v.findViewById(R.id.etQuery);
btnSearch = v.findViewById(R.id.btnSearch);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
final String country = getCountry();
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
retrieveJson("", country, API_KEY);
}
});
retrieveJson("", country, API_KEY);
btnSearch.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (!etQuery.getText().toString().equals("")) {
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
retrieveJson(etQuery.getText().toString(), country, API_KEY);
}
});
retrieveJson(etQuery.getText().toString(), country, API_KEY);
} else {
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
retrieveJson("", country, API_KEY);
}
});
retrieveJson("", country, API_KEY);
}
}
});
return v;
}
public void retrieveJson(String query ,String country, String apiKey){
swipeRefreshLayout.setRefreshing(true);
Call<Headlines> call;
if (!etQuery.getText().toString().equals("")){
call=NewsApiClient.getInstance().getApi().getSpecificData(query,apiKey);
}else{
call=NewsApiClient.getInstance().getApi().getHeadlines(country,apiKey);
}
call.enqueue(new Callback<Headlines>() {
#Override
public void onResponse(Call<Headlines> call, Response<Headlines> response) {
if (response.isSuccessful() && response.body().getArticles() != null ){
swipeRefreshLayout.setRefreshing(false);
articles.clear();
articles = response.body().getArticles();
newsAdapter = new NewsAdapter(getContext(), articles);
recyclerView.setAdapter(newsAdapter);
}
}
#Override
public void onFailure(Call<Headlines> call, Throwable t) {
swipeRefreshLayout.setRefreshing(false);
Toast.makeText(getContext(), t.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
});
}
public String getCountry(){
Locale locale = Locale.getDefault();
String country = locale.getCountry();
return country.toLowerCase();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
On the onCreateView you are in fact not setting any adapter. You instead set it when retrieving the json data inside the call.enqueue callbacks, this callbacks are called asynchronously and thus the first time the onCreateView is called no adapter is set to the RecyclerView. To avoid that, set an adapter with empty data empty adapter right at the start of the onCreateView function like this (make newsAdapter a variable of the current fragment)
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
newsAdapter = new NewsAdapter(getContext(), articles);
recyclerView.setAdapter(newsAdapter);
make articles be a variable of the current fragment
and insted of setting and adapter on the callbacks of you retriveJson function simply update the articles array with the data and call the notifydatasetchanged function of the RecycleView like this
public void onResponse(Call<Headlines> call, Response<Headlines> response) {
if (response.isSuccessful() && response.body().getArticles() != null ){
swipeRefreshLayout.setRefreshing(false);
articles.clear();
articles = response.body().getArticles();
newsAdapter.setData(articles);
newsAdapter.notifyDataSetChanged()
}
}
setData is a function that you have to create in your NewsAdapter class.
Hope it helped!
And take a look a this brief guide on how to use RecycleViews
This question already has answers here:
recyclerview No adapter attached; skipping layout
(38 answers)
Closed 4 years ago.
I have a simple program that connect to an API and get some data from it.I use Retrofit library.There are two activity. MainActivity includes a RecyclerView that show data received from the server. If press any item,application go to UserInfoActivity and show detailed information of that item.My app when go UserInfoActivity crashed after a few seconds and don't show information. However response correctly received but the program gives an error
on txtId.setText(user.getId) line. The error is E/RecyclerView: No adapter attached; skipping layout
In fact my problem starts when call Call<User> getUserInfo(#Path("id") int id); in second Activity.
This is MainActivity code.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
userRequest();
}
private void userRequest(){
APIInterface apiInterface= APIClient.getClient().create(APIInterface.class);
retrofit2.Call<ArrayList<User>> call= apiInterface.getUsers();
call.enqueue(new Callback<ArrayList<User>>() {
#Override
public void onResponse(retrofit2.Call<ArrayList<User>> call, Response<ArrayList<User>> response) {
if(response.isSuccessful()){
ArrayList<User> users = response.body();
setupRecycelerView(users);
}
}
#Override
public void onFailure(retrofit2.Call<ArrayList<User>> call, Throwable t) {
Log.i("RETROFIT","response not successful");
}
});
}
private void setupRecycelerView(ArrayList<User> users){
recyclerView=findViewById(R.id.RcyView);
adapter=new RecycelerAdapter(this,users);
linearLayoutManager=new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false);
recyclerView.setAdapter(adapter);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setItemAnimator(new DefaultItemAnimator());
}
This is onBindViewHolder function of RecycelerAdapter.
public void onBindViewHolder(#NonNull UserViewHolder holder, int position) {
final User user=values.get(position);
holder.txtName.setText(user.getName());
holder.txtPhone.setText(user.getPhone());
final Intent intent=new Intent(context, UserInfoActivity.class);
intent.putExtra("user",user);
holder.root.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
context.startActivity(intent);
}
});
}
This is UserInfoActivity code.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user_info);
txtId=findViewById(R.id.txtId);
txtName=findViewById(R.id.txtName);
txtPhone=findViewById(R.id.txtPhone);
Intent intent=getIntent();
User user=(User)intent.getSerializableExtra("user");
id=user.getId();
userInfoRequest();
}
private void userInfoRequest(){
APIInterface apiInterface= APIClient.getClient().create(APIInterface.class);
retrofit2.Call<User> call= apiInterface.getUserInfo(id);
call.enqueue(new Callback<User>() {
#Override
public void onResponse(Call<User> call, Response<User> response) {
if(response.isSuccessful()){
User user=response.body();
Log.e("MF","ON Response");
Log.e("MF",user.getName());
Log.e("MF",user.getPhone());
txtId.setText(user.getId());
txtName.setText(user.getName());
txtPhone.setText(user.getPhone());
}
}
#Override
public void onFailure(Call<User> call, Throwable t) {
Log.e("MF","on failure");
}
});
}
Try this:
linearLayoutManager=new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false);
recyclerView.setLayoutManager(linearLayoutManager);
adapter=new RecycelerAdapter(this,users);
recyclerView.setAdapter(adapter);
adapter.notifyDataSetChanged();
recyclerView.setHasFixedSize(true);
recyclerView.setItemAnimator(new DefaultItemAnimator());
Used notifyDataSetChanged() to tell the RecyclerView that data changed-added and set the Adapter after setLayoutManager.
If this didn't help, initialize the RecyclerView inside onCreate method and set the Adapter in onResponse with runOnUiThread like following:
runOnUiThread(new Runnable() {
#Override
public void run() {
//Do something on UiThread
ArrayList<User> users = response.body();
adapter=new RecycelerAdapter(this,users);
recyclerView.setAdapter(users);
}
});
Then call notifyDataSetChanged() and it should work then.
You're calling the setupRecycelerView on the response of the Retrofit api call, the problem is that till the response comes the RecyclerView doesnt have an adapter or a layoutManager attached to itself, hence the error.
Instead, you can call the setupRecycelerView inside onCreate itself and then create a global object of the Users list. Something like this:
private final ArrayList<Users> users = new ArrayList();
This list would be empty and the adapter would be initialised with the same empty list, then when you get the response from the retrofit api, use:
users.addAll(response.getBody());
this will fill the contents and then call adapter.notifyDataSetChanged(); to make the adapter reflect the updated contents.
This happens because you initialize RecyclerView when/if the response from server is successful.
Initialize it (call setupRecycelerView() ) in onCreate(). And change adapter=new RecycelerAdapter(this,users); to adapter=new RecycelerAdapter(this);
Make your RecycelerAdapter's constructor take one argument only (probably Context)
In your adapter, make a public method that sets the users.
public setUserData(ArrayList<User> users) {
this.values = users;
notifyDataSetChanged();
}
Call RecycelerAdapter.setUserData(users); from Retrofit's response is successful.
Just call setAdapter() after setLayoutManager() in your setupRecycelerView()
I am going to make website app and load feed on my first page but the problem is the scrolling is endless, how to stop this? The scrolling is endless so I want to stop it.
public class FeedActivity extends AppCompatActivity
{
private SwipeRefreshLayout swipeLayout;
private RecyclerView listView;
private LinearLayoutManager layoutManager;
private FeedAdapter adapter;
private FeedService service;
private FeedModel model;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_feed);
ActionBar actionBar = getSupportActionBar();
if(null != actionBar)
{
actionBar.setDisplayShowHomeEnabled(true);
actionBar.setIcon(R.mipmap.ic_launcher);
}
model = new FeedModel();
adapter = new FeedAdapter(model);
service = FeedService.create(model);
layoutManager = new LinearLayoutManager(this);
swipeLayout = (SwipeRefreshLayout) findViewById(R.id.swipeLayout);
listView = (RecyclerView) findViewById(R.id.listView);
listView.setLayoutManager(layoutManager);
listView.setAdapter(adapter);
}
#Override
protected void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
}
#Override
protected void onStart()
{
super.onStart();
model.subscribe(onDataChangedListener);
adapter.setOnItemClickListener(onItemClickListener);
swipeLayout.setOnRefreshListener(onRefreshListener);
service.fetchOlder();
}
#Override
protected void onStop()
{
super.onStop();
model.unSubscribe(onDataChangedListener);
adapter.setOnItemClickListener(null);
swipeLayout.setOnRefreshListener(null);
}
FeedModel.OnDataChangedListener onDataChangedListener = new FeedModel.OnDataChangedListener()
{
#Override
public void onFetchedAndAddedToTop(int count)
{
swipeLayout.setRefreshing(false);
adapter.notifyDataSetChanged();
}
#Override
public void onFetchedAndAddedToBottom(int count)
{
if(count == 0)
{
adapter.setFooterEnabled(false);
}
adapter.notifyDataSetChanged();
}
};
SwipeRefreshLayout.OnRefreshListener onRefreshListener = new SwipeRefreshLayout.OnRefreshListener()
{
#Override
public void onRefresh()
{
service.fetchNewer();
}
};
FeedAdapter.OnItemClickListener onItemClickListener = new FeedAdapter.OnItemClickListener()
{
#Override
public void onItemClick(View view)
{
int position = listView.getChildAdapterPosition(view);
FeedItem item = model.get(position);
Intent intent = new Intent(getApplicationContext(), PostActivity.class);
intent.putExtra("id", item.getId());
startActivity(intent);
// System.out.println("Click: "+item.getTitle());
}
};
}
You can stop scrolling in two ways
If you know how many items are refreshing for every refresh you done then make a condition to stop calling refresh when you get less items than actual.
You can simply make a condition to stop refreshing list when you don't get data from earlier refresh.
Hope this Helpful..
#Rajesh
To stop endless scrolling apply a condition.
For example for every scroll 20 results are fetched and added to the list. Then you can apply a condition
if(lastFetchedItemCount!=0&&lastFetchedItemCount%20==0){
//enable scrolling
}
else{
//disable scrolling
}
I am trying to set the adapter of a RecyclerView within the onCreate(). I read that the notifyDatasetChanged() can be invoked on the main thread only. But how can this be achieved? Here is my code so far:
RecyclerView recyclerViewSt;
List<GitHubstarredRepos> myDataSource = new ArrayList<>();
RecyclerView.Adapter myAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
recyclerViewSt = (RecyclerView) findViewById(R.id.starred_repos_recycler_view);
recyclerViewSt.setLayoutManager(new LinearLayoutManager(this));
myAdapter = new StarredReposAdapter(myDataSource, R.layout.list_item_starred_repo,
getApplicationContext());
recyclerViewSt.setAdapter(myAdapter);
}
public void loadStarredRepos (View view){
GitHubStarredRepoAPI apiService =
ApiClient.getClient().create(GitHubStarredRepoAPI.class);
Call<List<GitHubstarredRepos>> call = apiService.getStarredRepoName(newString);
call.enqueue(new Callback<List<GitHubstarredRepos>>() {
#Override
public void onResponse(Call<List<GitHubstarredRepos>> call, Response<List<GitHubstarredRepos>>
response) {
myDataSource.clear();
myDataSource.addAll(response.body());
recyclerViewSt.notify();
}
Thank you!
instead of notifying RecyclerView, Notify your Adapter.
I am currently trying to update a RecyclerView once an AsyncTask has completed its operation.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Initializing RecycleView
mRecyclerView = (RecyclerView)findViewById(R.id.list);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
mAdapter = new AdvertAdapter(this.results, R.layout.card_advert, this);
mRecyclerView.setAdapter(mAdapter);
Here is a code snippet from my AsyncTask:
#Override
protected void onPostExecute(String result){
// your stuff
listener.onTaskCompleted(data);
}
In the listener Activity class, I am updating the data set and attempting to notify my custom adapter that the data set has been changed:
#Override
public void onTaskCompleted(ArrayList<Data>results) {
this.results = results;
this.mAdapter.notifyDataSetChanged();
System.out.println("Done.");
}
It seems that notifyDataSetChanged() is not actually doing anything, as the list remains empty even after the final line. From the debugger, I can verify that results does contain the correct results, and the onTaskCompleted is being run on the main thread. How can I update my RecyclerView with the new results?
I think you should add the ArrayList<Data>results to your adapter data.
Something like this:
#Override
public void onTaskCompleted(ArrayList<Data>results) {
//this.results = results;
this.mAdapter.clear();
this.mAdapter.add(results);
//or this.mAdapter.results = results;
this.mAdapter.notifyDataSetChanged();
System.out.println("Done.");
}