I'm trying to update the data within the recyclerview every 5 seconds but every time the data is updated the scroll position is not maintained and in automatic it goes up.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_units_impl, container, false);
initUnitsViewImpl(view);
return view;
}
private void initUnitsViewImpl(View view) {
initViewID(view);
initPresenter();
initOnClickListeners();
}
private void initPresenter() {
final UnitsPresenter presenter = new UnitsPresenterImpl(getContext());
presenter.setView(this);
presenter.getFullVehicles();
handler.postDelayed(new Runnable() {
#Override
public void run() {
presenter.getFullVehicles();
presenter.hideProgressDialog();
handler.postDelayed(this,5000);
}
},5000);
}
#Override
public void setUnitList(final List<Unit> unitList) {
this.vehicles = unitList;
final LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext());
rvUnits.setLayoutManager(linearLayoutManager);
unitAdapter = new UnitAdapter(vehicles, getContext());
rvUnits.setAdapter(unitAdapter);
}
What am i doing wrong?
Thanks for your help.
I'm assuming the issue here is that you're trying to update your recycler view by updating the adapter. You actually do a lot of things element by element, no need to change the adapter.
Check out this answer and let me know if this is what you're looking for.
https://stackoverflow.com/a/48959184/13150066
Related
RecyclerView position not saved after screen rotation and moves to the beginning of the list.
RecyclerView находиться в Fragment, added the following code to my project, after which, in theory, the position should be saved, but this does not happen
#Override
public void onPause() {
super.onPause();
state = RLM_tasks.onSaveInstanceState();
}
Help to figure out why the list does not save position and solve this problem
Fragment completely
public class FragmentList extends Fragment {
CardView cv_list;
private FirebaseFirestore firebaseFirestore = FirebaseFirestore.getInstance();
private CollectionReference collectionReference = firebaseFirestore.collection("Tasks");
private AdapterTasksList adapterTasksList;
private RecyclerView rv_tasks;
private RecyclerView.LayoutManager RLM_tasks;
Parcelable state;
public static FragmentList newInstance() {
return new FragmentList();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_list, container, false);
}
#Override
public void onViewCreated(#NonNull final View view, #Nullable Bundle savedInstanceState)
{
cv_list = requireView().findViewById(R.id.cv_list);
cv_list.setOnClickListener(v -> requireActivity().onBackPressed());
Query query = collectionReference.orderBy("TurnTasks", Query.Direction.DESCENDING);
FirestoreRecyclerOptions<ItemTasks> firestoreRecyclerOptions = new FirestoreRecyclerOptions.Builder<ItemTasks>()
.setQuery(query, ItemTasks.class)
.build();
adapterTasksList = new AdapterTasksList((ClickTasksBlanc) getContext(), firestoreRecyclerOptions);
adapterTasksList.startListening();
rv_tasks = requireView().findViewById(R.id.rv_list);
RLM_tasks = new LinearLayoutManager(getActivity());
RLM_tasks.onRestoreInstanceState(state);
rv_tasks.setHasFixedSize(false);
rv_tasks.setLayoutManager(RLM_tasks);
rv_tasks.setNestedScrollingEnabled(true);
rv_tasks.setAdapter(adapterTasksList);
}
#Override
public void onPause() {
super.onPause();
state = RLM_tasks.onSaveInstanceState();
}
}
I have suspicions that the whole problem is that the list is re-created when the screen is rotated, and therefore it cannot re-revert to the previous position.
For these kind of situation you have to use model view- view model called mvvm you can get more detail & tutorial from official website and also see the some example of tutorial
You can save layoutManager state in ViewModel then get the state value when the fragment re-created
You can check the sample code here https://code.luasoftware.com/tutorials/android/android-jetpack-navigation-lost-state-after-navigation/#maintain-state-via-viewmodel
the ProgressBar doesn't disappear in the fragment also items aren't loaded in the RecyclerView, When the code was in the body of onCreate of the main activity ,it was working
I am making fragment that contains ProgressBar and RecyclerView with retrieving data from firebase database , I added setVisibility at the end of onDataChange method so that after getting all the data and store it in the array list , the ProgressBar disappear
RecyclerView offersRecycler;
ArrayList<offer> offers;
OffersAdapter offersAdapter;
View fragment_layout;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
fragment_layout = inflater.inflate(R.layout.fragment_offers, container, false);
// Offers RecyclerView
offersRecycler = fragment_layout.findViewById(R.id.offersRecycler);
offers = new ArrayList();
offers_init();
offersAdapter = new OffersAdapter(offers,getContext());
offersRecycler.setAdapter(offersAdapter);
offersRecycler.setLayoutManager(new LinearLayoutManager(getContext()));
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_offers, container, false);
}
private void offers_init() {
DatabaseReference db_offers = db_ref().child("offers");
db_offers.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
try {
ProgressBar loadingOffers = fragment_layout.findViewById(R.id.loadingOffers);
loadingOffers.setProgress(10);
for (DataSnapshot offer_item : dataSnapshot.getChildren()) {
String id = offer_item.getKey();
String title = offer_item.child("title").getValue().toString();
String rating = offer_item.child("rating").getValue().toString();
String orders_number = offer_item.child("orders_number").getValue().toString();
offer offer_object = new offer(id,title, rating, orders_number);
offers.add(offer_object);
offersAdapter.notifyDataSetChanged();
}
Toast.makeText(getContext(),"title",Toast.LENGTH_LONG).show();
loadingOffers.setVisibility(View.GONE);
}catch (Exception ex){
Toast.makeText(getContext(),ex.getMessage(),Toast.LENGTH_LONG).show();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
}
I expect the ProgressBar to hide and the array loaded into the RecyclerView
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
fragment_layout = inflater.inflate(R.layout.fragment_offers, container, false);
// Offers RecyclerView
offersRecycler = fragment_layout.findViewById(R.id.offersRecycler);
offers = new ArrayList();
offers_init();
offersAdapter = new OffersAdapter(offers,getContext());
offersRecycler.setAdapter(offersAdapter);
offersRecycler.setLayoutManager(new LinearLayoutManager(getContext()));
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_offers, container, false);
}
^ This is your code snippet. What you are doing is - inflating a layout fragment_layout = inflater.inflate(R.layout.fragment_offers, container, false) and using fragment_layout to get the instance of RecyclerView. This is fine.
But at the end of the method, you are inflating a new layout and returning that. So the Android framework uses that View instead of fragment_layout to set the Fragment view. All the views in fragment_layout won't be shown since it's never added to the fragment and hence you don't see any changes in the RecyclerView.
At the end of the onCreateView method, you should just return fragment_layout.
in your CreateView, change
return inflater.inflate(R.layout.fragment_offers, container, false);
by
return fragment_layout;
You call the layout, init recyclerview, but recall the "blank" layout
Also, avoid underscore for method or variable name, it's not the java convention
What I'm using:
MVP
Navigation Component
Single Activity
Fragments
RecyclerView
Realm
Problem:
I have a MainActivity that hosts all the fragments for the application flow.
There's one fragment that has a recyclerview, the more I scroll the more API requests are made. Whenever I press one item to navigate to a different fragment and press back to return to the list, the list only has a small piece of the previous loaded recyclerview, I want everything.
Is it possible to do this with the navigation component?
Can I prevent onCreateView being called whenever I press back?
I've tried to save the View in a static variable, have a onSaveInstance (but it never gets called)
I'm using navigation this way:
#Override
public void onClick(View view) {
Navigation.findNavController(view)
.navigate(R.id.action_tripsFragment_to_tripDetailsFragment, bundle);
}
MainFragment:
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater,
#Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.trips_fragment, container, false);
mPresenter = new TripsFragmentPresenterImpl(this);
ButterKnife.bind(this, v);
navController = Navigation.findNavController(getActivity(), R.id.nav_host_fragment);
mLinearLayoutManager = new LinearLayoutManager(getContext(), RecyclerView.VERTICAL, false);
mAdapter = new PaginationAdapter(getContext());
mAdapter.setOnItemClickListener(onItemClickListener);
mPresenter.setLoadingTripsProgressBar(true);
mRecyclerView.setLayoutManager(mLinearLayoutManager);
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.addOnScrollListener(new PaginationScrollListener(mLinearLayoutManager) {
#Override
protected void loadMoreItems() {
isLoading = true;
currentPage += 1;
loadNextPage();
}
#Override
public int getTotalPageCount() {
return currentPage;
}
#Override
public boolean isLoading() {
return isLoading;
}
});
loadFirstPage();
return v;
}
What I expect:
Whenever I press back from TripDetails to Trips I want to get my recyclerview as it was, with the previous loaded items at the correct position.
Current results:
Whenever I press back from TripDetails to Trips the recyclerview cuts the items to only the items that are visible, meaning that we'll lose items previous loaded in previous pages.
For future reference I've managed to solve the issue by creating a ModelView to keep the data independent of the lifecycle of the fragment
I have some trouble to understand why my recyclerView get update while I add the data only after setAdapter() and never call notifyDataSetChanged() !
In the same type:
-> I add 3 String to my list
-> Notify the adapter only for the first added (notifyItemInserted)
Result: 3 String displayed.
If anyone can help me understand I will be very grateful to him :)
https://www.noelshack.com/2018-04-5-1516969446-recycler.png
my code:
public class MainActivity extends AppCompatActivity {
List<String> mList = new ArrayList<>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Call before set adapter so it's normal if show when the adapter is set
mList.add("Before set the adapter");
RecyclerView recyclerView = findViewById(R.id.rcv);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
final Adapter mAdapter = new Adapter(mList);
recyclerView.setAdapter(mAdapter);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mList.add("Button add " +0);
mAdapter.notifyItemInserted(mList.size()-1);
//Again it's showing but never notify
mList.add("Button add " +1);
mList.add("Button add " +2);
}
});
//Called after setAdapter and NEVER notify the adapter but it's still show the data
addMoreData();
}
private void addMoreData() {
for (int i = 0; i < 10; i++) {
mList.add("addMoreData: " + i);
}
}
class Adapter extends RecyclerView.Adapter<Adapter.ViewHolder> {
class ViewHolder extends RecyclerView.ViewHolder {
TextView nameTextView;
ViewHolder(View itemView) {
super(itemView);
nameTextView = (TextView) itemView.findViewById(R.id.textview);
}
}
#Override
public Adapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.raw, parent, false);
return new Adapter.ViewHolder(view);
}
private List<String> mList;
Adapter(List<String> list) {
mList = list;
}
#Override
public void onBindViewHolder(Adapter.ViewHolder holder, int position) {
holder.nameTextView.setText(mList.get(position));
}
#Override
public int getItemCount() {
return mList.size();
}
}
}
I think, because of notifyDataSetChanged's working principle, this is the result.
A Google engineer said ;
When you call notifyDataSetChanged, RecyclerView invalidates the data
but does not update the UI until the next animation frame. This is how
android view system works. When a widget is invalidated (e.g. changing
its data) it requests a layout which means it will be re-measured and
re-laid out in the next view traversal. This is done so that we can
batch all changes until the next time screen will be updated.
That means; nofiyDataSetChanged waits the UI Thread. So, doesn't matter the below lines of the notifyItemInserted from your code.
mList.add("Button add " +0);
mAdapter.notifyItemInserted(mList.size()-1);
//Again it's showing but never notify
mList.add("Button add " +1);
mList.add("Button add " +2);
We mostly use adapter notify calls to keep views that are currently shown in their proper state, it does not prevent adapter from serving the data to RecyclerView component.
You can see it very clearly, if your RecyclerView has scroll - update the list data without notifying it and then scroll to the bottom you will get your new items laid out.
In your case you add item on the end and notify only for it - but LayoutManager determines it can still lay out more views so it does.
I am using RecyclerView (and GridLayout) to place dynamic buttons in a grid. What is the best way to set up a DIFFERENT onClickListener for each dynamic button as it is created using RecyclerView and placed in the Gridlayout?
My buttons are created randomly depending on a user action by passing a drawable to my RecyclerView in the method "createButton" below. Only one drawable gets passed to my gridLayout at a time, and each time a new onClickListener must be created. What is the best way to go about this?
private GridLayoutManager lLayout;
RecyclerViewAdapter rcAdapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
List<ItemObject> myList = new ArrayList<>();
rcAdapter = new RecyclerViewAdapter(getActivity(),myList);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.home_fragment, container, false);
lLayout = new GridLayoutManager(getActivity(), 3,
GridLayoutManager.HORIZONTAL, false);
RecyclerView rView = (RecyclerView)view.findViewById(R.id.recycler_view);
rView.setHasFixedSize(true);
rView.setLayoutManager(lLayout);
rView.setAdapter(rcAdapter);
return view;
}
public void createButton (Drawable d, String appName){
rcAdapter.addItem(new ItemObject(appName, d));
}
I don't think you need a new click listener every time you just need a click listener that is aware of the ItemObject. For that, I'll give you my usual approach for such a pattern:
Somewhere in your code you have an RecyclerView.ViewHolder, you should make that view holder implement OnClickListener and give pass the reference of ItemObject to the holder during OnBind, like following:
class MyHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
ItemObject itemObject;
public MyHolder(View itemView) {
super(itemView);
itemView.findViewById(your button Id).setOnClickListener(this); // make this holder receives the clicks
}
#Override
public void onClick(View view) {
// here you add logic that depending on the data from itemObject
}
}
and then during onBind you must properly set the ItemObject
#Override
public void onBindViewHolder (MyHolder holder, int position) {
ItemObject itemObject = list.get(position);
holder.itemObject = itemObject;
// the rest of your bind code....
}