RecyclerView position not saved after screen rotation - java

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

Related

RecyclerView scroll position goes to top after update

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

why setVisibility in fragment doesn't work?

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

'startActivity(android.content.Intent, android.os.Bundle)' in 'android.support.v4.app.Fragment' cannot be applied to '(android.content.Intent, int)'

I'm developing my final year project about trip planner, and my app has fragments that contains other features. In FragmentPhoto, which I created to upload photo into the app has error regarding the startActivity(). The error said 'startActivity(android.content.Intent, android.os.Bundle)' in 'android.support.v4.app.Fragment' cannot be applied to '(android.content.Intent, int)'. Here is my code for the fragment.
public class FragmentPhoto extends Fragment {
private Button mSelectImage;
private StorageReference mStorage;
private static final int GALLERY_INTENT = 2;
public FragmentPhoto() {
// Required empty public constructor
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mStorage = FirebaseStorage.getInstance().getReference();
mSelectImage = (Button) getView().findViewById(R.id.selectImage);
mSelectImage.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/*");
startActivity(intent,GALLERY_INTENT);
}
});
}
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.activity_fragment_photo, container, false);
}
}
The error kinda related to the private static final int GALLERY_INTENT = 2, since the extends is not AppCompatActivity. Is there any way to resolve this? Thank you in advance.
ACTION_PICK send the request to installed apps(registered with this action to supply images) to allow user to pick images so to identify the request uniquely, a unique int parameter is passed which will be returned in onActivityResult so you have to use startActivityForResult
so use startActivityForResult(intent,GALLERY_INTENT);

Refreshing fragment in Android

I have a fragment which doesn't contain anything at the application start, but after the data is loaded it should show the loaded data. I do all my data loading in MainActivity in function onDataLoaded which implements DataLoadedListener. After the data is loaded the function sends the data (ArrayList) as a parameter through the function call (initializeAdapter()). Then, the function initializeAdapter() gets it and initialize custom made adapter (adapter is a global object, initialized in initializeAdapter() and used in onCreateView()).
I guess that I should refresh fragment view after the data is loaded and the adapter is initialized, but I don't know how to do it. I tried in a lot of ways but didn't make it.
onDataLoaded function in MainActivity which calls initializeAdapter()
#Override
public void onDataLoaded(List<Grad> gradovi, List<Ponuda> ponude) {
Spinner spinnerGradovi = (Spinner) findViewById(R.id.gradovi_spinner);
ArrayAdapter<String> adapterGradovi;
List<String> listaGradova = new ArrayList<>();
ArrayList<Ponuda> ponudaArrayList = new ArrayList<Ponuda>();
ponudaLista = ponude;
gradLista = gradovi;
for(Grad grad : gradovi ){
listaGradova.add(grad.getNaziv());
}
for(Ponuda ponuda : ponude){
ponudaArrayList.add(ponuda);
}
adapterGradovi = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, listaGradova);
spinnerGradovi.setAdapter(adapterGradovi);
Fragment fragmentGet = svePonudeFragment;
((SvePonudeFragment)fragmentGet).initializeAdapter(ponudaArrayList);
}
and this is a Fragment
public class SvePonudeFragment extends Fragment {
private RecyclerView rv;
RVAdapter adapter;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.sve_ponude_fragment, container, false);
rv = (RecyclerView) rootView.findViewById(R.id.rv);
LinearLayoutManager llm = new LinearLayoutManager(getActivity());
rv.setLayoutManager(llm);
rv.setAdapter(adapter);
return rootView;
}
public void initializeAdapter(List<Ponuda> preuzetePonude){
adapter = new RVAdapter(preuzetePonude);
//this isn't working
/*FragmentManager manager = getActivity().getSupportFragmentManager();
android.support.v4.app.FragmentTransaction ft = manager.beginTransaction();
ft.detach(this).attach(this).commit();*/
//this is not working, neither
Fragment frg = getFragmentManager().findFragmentByTag("sve_ponude_fragment_tag");
final android.support.v4.app.FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.detach(frg);
ft.attach(frg);
ft.commit();
}
}
I am also interested in how to make an empty adapter which loads if data is not loaded yet? And when the data is loaded to fill out another adapter and use it.
Initialize adapter before you set. If you interested to change you approach I can give suggestions: Create a constructor for you fragment which you can pass the ArrayList. OnCreate or OnCreateView you can initialize the adapter and pass the list into. After setting adapter notifyDataChanges and to check if there is data please use some Logs when you pass the data and before you pass the data. Do not directly blame the adapter or fragment control your data. Besides I suggest you to study more using fragments + RecyclerView. I am posting one example that you can look at but the data load in the fragment.
public class CategoryFragment extends Fragment {
//Class Tag
private static final String TAG = CategoryFragment.class.getSimpleName();
//Fragments base context
private Context mContext;
//RecyclerView widget
private RecyclerView mRecyclerView;
//RecyclerView's Layout Manager
private RecyclerView.LayoutManager mLayourManager;
//RecyclerView's Adapter
private CategoryFragmentAdapter mCategoryFragmentAdapter;
//Category Data List
private ArrayList<Category> mCategoryList;
//Interface object
onCategoryItemClick mCallBackCategory;
#Override
public void onAttach(Context context) {
super.onAttach(context);
//Instantiate base context
mContext = context;
//Instantiate interface
mCallBackCategory = (onCategoryItemClick)getActivity();
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Instantiate Category List
mCategoryList = new ArrayList<Category>();
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
//Custom fragment view instantiating
View view_fragment_category = inflater.inflate(R.layout.fragment_items, container, false);
//Instantiating Fragment's recycler view
mRecyclerView = (RecyclerView) view_fragment_category.findViewById(R.id.recycle_categories);
//Instantiate RecycleView's Layout Manager: LinearLayoutManager is one of the default
mLayourManager = new LinearLayoutManager(mContext);
//Setting layout manager for recycler view
mRecyclerView.setLayoutManager(mLayourManager);
//Retrieve data from server, populate the category data list and set the RecyclerView's adapter
getCategories();
//Instantiate the adapter and push the data
mCategoryFragmentAdapter = new CategoryFragmentAdapter(mContext, mCategoryList);
//RecyclerView item click listener
mCategoryFragmentAdapter.SetOnItemClickListener(new CategoryFragmentAdapter.OnItemClickListener() {
#Override
public void onItemClick(View v , int position) {
//Test display
//Toast.makeText(mContext, mCategoryList.get(position).getId(),Toast.LENGTH_LONG).show();
//Push to Activity method with clicked item ID
mCallBackCategory.proceedToProducts(mCategoryList.get(position).getId());
}
});
return view_fragment_category;
}

How to update custom RecyclerView from FragmentDialog?

I have an Activity A with a fragment frag2. Inside the fragment I have a RecyclerView and Adapter to show a list of custom class objects. Adding objects to the adapter is handled programmatically. I have a button inside TwoFragment that opens a FragmentDialog. I'd like to add an object to my Adapter by confirming this dialog, but it seems that the adapter is null when called from the FragmentDialog.
The same adapter is not null, and works if I call it from the fragment OnClick.
Moreover the adapter is null only after screen rotation, it works fine before rotating.
To communicate between the two Fragments I implement a communicator class in activity A.
Activity A
public void respond(String type) {
frag2.addSupport(type);
}
frag2
public RecyclerView rv;
public ArrayList<support> supports;
public myAdapter adapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supports = new ArrayList<>();
adapter = new myAdapter(supports);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View layout = inflater.inflate( R.layout.fragment_two, container, false);
layout.setId(R.id.frag2);
if (savedInstanceState!=null)
{
supports = savedInstanceState.getParcelableArrayList("supports");
}
rv = (RecyclerView) layout.findViewById(R.id.rv);
adapter = new myAdapter(supports);
rv.setAdapter(myAdapter);
rv.setLayoutManager(new LinearLayoutManager(getActivity()));
rv.setItemAnimator(new DefaultItemAnimator());
#Override
public void onClick(View v) {
int id = v.getId();
switch (id){
case R.id.button1:
addSupport(type); // THIS WORKS ALWAYS, even after screen rotate
break;
case R.id.button2:
showDialog();
break;
}
}
public void showDialog(){
FragmentManager manager = getFragmentManager();
myDialog dialog = new myDialog();
dialog.show(manager, "dialog");
}
public void addSupport(String type){
adapter.addItem(new support(type)); // this line gives null pointer on adapter, but only if called after screen rotate and only if called from the dialog
}
dialog
communicator comm;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.dialog, null);
comm = (myCommunicator) getActivity();
create = (Button) view.findViewById(R.id.button_ok);
create.setOnClickListener(this);
return view;
}
#Override
public void onClick(View v) {
if(v.getId()==R.id.button_ok)
{
// some controls to set type
comm.respond(type)
dismiss();
}
else {
dismiss();
}
myAdapter
public class myAdapter extends RecyclerView.Adapter<myAdapter.VH> {
private LayoutInflater inflater;
private ArrayList<support> data = new ArrayList<>();
// settings for viewholder
public myAdapter (ArrayList<support> data)
{
this.data=data;
}
public void addItem(support dataObj) {
data.add(dataObj);
notifyItemInserted(data.size());
}
}
logcat
FATAL EXCEPTION: main
java.lang.NullPointerException: Attempt to invoke virtual method 'myAdapter.addItem(myObject)' on a null object reference
I hope there are no mistakes, I shortened the code for better understanding. Keep in mind that everything works if I never rotate the screen.
I'm a beginner with android and I'm stuck with this for several days now. Please, help.
To understand the problem, it's as you say:
.. everything works if I never rotate the screen
So firstly to understand what happens on rotation, this is a quote from the Android Developer website:
Caution: Your activity will be destroyed and recreated each time the user rotates the screen. When the screen changes orientation, the system destroys and recreates the foreground activity because the screen configuration has changed and your activity might need to load alternative resources (such as the layout).
Ok, now to understand the error:
FATAL EXCEPTION: main
java.lang.NullPointerException: Attempt to invoke virtual method 'myAdapter.addItem(myObject)' on a null object reference
Essentially, in your dialog class, you have created a strong dependency by declaring :
comm = (myCommunicator) getActivity();
because comm references objects which would have been destroyed on rotation, hence the NullPointerException.
To further understand runtime changes, such as orientation changes, I'd recommend going through Handling Runtime Changes.
Update
Thank you for your answer, what would you recommend instead of comm = (myCommunicator) getActivity(); ?
The solution comes in 3 parts:
Make sure the onCreate of Activity A has the following:
#Override
public void onCreate(Bundle savedInstanceState) {
......
// find the retained fragment on activity restarts
FragmentManager fm = getFragmentManager();
frag2 = (Frag2) fm.findFragmentByTag(“frag2”);
// create frag2 only for the first time
if (frag2 == null) {
// add the fragment
frag2 = new Frag2();
fm.beginTransaction().add(frag2 , “frag2”).commit();
}
......
}
Add setRetainInstance(true) to the onCreate of frag2.
Remove the implicit referencing i.e. comm = (myCommunicator) getActivity();, and implement something more loosely coupled for dialog.
dialog
public interface Communicator {
void respond(String type);
}
Communicator comm;
....
public void addCommunicator(Communicator communicator) {
comm = communicator;
}
public void removeCommunicator() {
comm = null;
}
#Override
public void onClick(View v) {
if((v.getId()==R.id.button_ok) && (comm!=null))
{
// some controls to set type
comm.respond(type);
}
// Regardless of what button is pressed, the dialog will dismiss
dismiss();
}
This allows you do the following in frag2 (or any other class for that matter):
frag2
<pre><code>
public class Frag2 extends Fragment implements dialog.Communicator {
........
public void showDialog() {
FragmentManager manager = getFragmentManager();
myDialog dialog = new myDialog();
dialog.addCommunicator(this);
dialog.show(manager, "dialog");
}
#Override
public void respond(String type){
adapter.addItem(new support(type));
}
}

Categories