I have 2 activities: RoutineViewer and ExerciseViewer. Both activities contain fragments that are sub-classes of ParentFragment.java.
RoutineViewer's fragment (RoutineViewerFragment) is displaying a recyclerview. The recyclerview's data is from a static ArrayList in ParentFragment.java.
From RoutineViewerFragment, I can start the ExerciseViewer activity, and the fragment inside it (ExerciseViewerFragment) is used to make changes to customRoutineExercise in ParentFragment.java. After making the changes in ExerciseViewerFragment, I can call requireActivity().finish() on ExerciseViewer.java, which will take me back to RoutineViewerFragment. However, my problem is that the data in RoutineViewerFragment is not reflecting the changes I made in ExerciseViewerFragment.
Some solutions I have tried (but didn't work in this case) from looking at other similar problems:
// In RoutineViewerFragment onResume() with if else conditions
mAdapter.notifyItemChanged(// insert relevant position);
// In RoutineViewer.java onResume() with if else
getSupportFragmentManager().beginTransaction.detach(currentfragment).attach(currentfragment).commit()
I'm guessing that the reason why mAdapter.notifyItemChanged(position) isn't working is because I'm not directly passing the static customRoutineExercise in the constructor of mAdapter, but rather a value of the HashMap instead.
As for the .detach().attach(), I've read that it forces the onCreateView() to run again, which theoretically should refresh the recyclerview with the updated data, but it isn't.
I've left the important code segments below. Any help is appreciated, thanks!
ParentFragment.java
protected static int selectedCustomRoutine;
protected static int selectedCustomExercise;
protected static ArrayList<customRoutine> customRoutineInfo = new ArrayList<>();
protected static HashMap <Integer, ArrayList<customExercise>> customRoutineExercise = new HashMap<>();
RoutineViewer.java
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_routine_viewer);
Intent intent = getIntent();
routineType = intent.getStringExtra("routineType");
routinePosition = intent.getIntExtra("routinePosition", 0);
selectedFragment = new RoutineViewerCustomFragment(routinePosition);
getSupportFragmentManager().beginTransaction().replace(R.id.routineV_fragment_container, selectedFragment).commit();
break;
}
RoutineViewerFragment.java
private int routinePosition;
public RoutineViewerCustomFragment(int routinePosition) {
// constructor
this.routinePosition = routinePosition;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_routine_viewer_custom, container, false);
buildRecyclerView(rootView);
return rootView;
}
// other code
private void buildRecyclerView(View rootView) {
mRecyclerView = rootView.findViewById(R.id.recyclerView);
mRecyclerView.setHasFixedSize(true);
mLayoutManager = new LinearLayoutManager(getActivity());
mAdapter = new RoutineViewerCustomAdapter(customRoutineExercise.get(routinePosition));
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setAdapter(mAdapter);
}
// Other code
private void goExerciseViewer(int position) {
selectedCustomExercise = //number;
selectedCustomRoutine = //number;
}
ExerciseViewerFragment.java
private void saveChanges() {
customRoutineExercise.get(selectedCustomRoutine).get(selectedCustomExercise).setSetNumber(Integer.parseInt(editText1.getText().toString()));
customRoutineExercise.get(selectedCustomRoutine).get(selectedCustomExercise).setRepNumber(Integer.parseInt(editText2.getText().toString()));
}
Code Smell: The recyclerview's data is from a static ArrayList in ParentFragment.java.
Don't do this.
Have a repository where you store your "recycler view list" (exercises?), and have the recyclerView request (or observe if you use a ViewModel) a list.
On the fragment where you modify this list, have said fragment talk to the same repository either via its own viewModel or a shared one or whatever you use (search for Android ViewModels jetPack if unfamiliar).
This way, when the recyclerView comes back, it will ask for a new list (and will get the updated copy) or if observing, it will be notified that a new list is available.
The list you pass to the adapter is/should be a copy/immutable.
This is not the same but it's an Activity that has a RecyclerView and "asks" directly for the data to a repo. (This example has nothing to do with your problem, but notice how the adapter is dummy, it's just told to display the list).
https://github.com/Gryzor/CheckBoxCounter/blob/master/app/src/main/java/com/androidonlinux/checkboxcounter/MainActivity.kt
Related
I am following this question
how to store recyclerview data on onSaveInstanceState
I also found
How to save state of view class? and Fragment save view state.
Context
I give data in form of DataModel (implements Parcelable) to recyclerview in one of my Fragments.
using Bottom navifation and ROOM DB (to get and save Data).
what I have done yet
I used the code in first link and in my code. But I couldn't understand the fourth peace of code, which was used in there (I don't have a response.boddy(), Error).
Anyway every time changing the view savedInstanceState = null so the code is being redone.
what I want or question
I would like to not redo the work every time changing the view via bottom navigation?
what am I doing wrong, that data are not being saved in savedInstanceState?
my Fragment view
private ArrayList<DataModel> data;
public View onCreateView(#NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
root = inflater.inflate(R.layout.fragment_home, container, false);
currentContext = getContext();
recyclerView = (RecyclerView) root.findViewById(R.id.recyclerViewHome);
recyclerView.setHasFixedSize(true);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getContext());
recyclerView.setLayoutManager(layoutManager);
recyclerView.setItemAnimator(new DefaultItemAnimator());
this.data = new ArrayList<>();
adapter = new CustomAdapter(data, currentContext, 1);
recyclerView.setAdapter(adapter);
if (savedInstanceState != null) {
// Retrieve the data you saved
data = savedInstanceState.getParcelableArrayList("saved_data");
//Call method to reload adapter record
recyclerViewsaveInstance(data);
}
else {
//No data to retrieve
dataAsynTask; //deleted, Basicly I get the data from DB, convert it to DataModel and give them to recyclerview.
}
return root;
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
Log.i("savedInstanceState", "loading");
savedInstanceState.putParcelableArrayList("saved_data", this.data);
super.onSaveInstanceState(savedInstanceState);
}
public void recyclerViewsaveInstance(ArrayList<DataModel> dataset)
{
this.data = dataset;
adapter = new CustomAdapter(dataset, getContext(), 1);
recyclerView.setAdapter(adapter);//notify adapter about the new record
adapter.notifyDataSetChanged();
}
I think what you want to do here is to move your AsyncTask out to the Activity level, and have your Fragment ask the Activity for the List<DataModel>.
In your Fragment's onCreateView() method, instead of writing
this.data = new ArrayList<>();
and then checking savedInstanceState and conditionally executing an AsyncTask, you could write this:
this.data = ((MyActivity) getActivity()).getData();
Then, in your Activity, you'd implement this getData() method:
public List<DataModel> getData() {
if (data != null) {
return data;
} else {
// kick off AsyncTask here
return Collections.emptyList();
}
}
When the AsyncTask finishes, have it update the Activity's data field and also have it notify the Fragment that new data is available. In the Fragment, you'd have a method like this:
public void onDataFetched(List<DataModel> data) {
this.data = data;
adapter.notifyDataSetChanged();
}
The end result of all of the above is that the Activity is now responsible for loading the data and for saving it. The Fragment doesn't know anything about the database or caching; it just knows to ask the Activity for the data whenever it is shown.
Since the Activity stays alive while you switch between Fragments with your bottom nav, it will successfully keep the data alive when you switch tabs.
I'm using a RecyclerView in my Android project. I have a function, getPosts(int page) that adds new items to the relevant ArrayList. This is called in onLoadMore().
Now, when the activity starts, nothing happens. So I decided to call getPosts(1) manually from onCreate(). The problem with this is now page 1 is being loaded twice. Once by me in onCreate(), and for some reason again in onLoadMore(). Subsequent pages load perfectly.
So is there some way to tell the RecyclerView to start loading? If not, what should I do here?
Edit: As requested, here is a summary of my code:
private ArrayList<Post> postArrayList;
private RecyclerView recycler;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_diary);
//...
postArrayList = new ArrayList<>();
recycler = ((RecyclerView) findViewById(R.id.recyclerDiary));
recycler.setHasFixedSize(true);
recycler.setLayoutManager(new LinearLayoutManager(this));
EndlessRecyclerViewScrollListener scrollListener = new EndlessRecyclerViewScrollListener(((LinearLayoutManager) recycler.getLayoutManager())){
#Override
public void onLoadMore(int page, int totalItemsCount, RecyclerView view) {
Log.d("devlog", "Requesting page "+page+" from onLoadMore()");
getPosts(page);
}
};
recycler.setAdapter(new PostAdapter(postArrayList, new RecyclerViewClickHandler()));
recycler.addOnScrollListener(scrollListener);
Log.d("devlog", "Requesting page 1 from onCreate()");
getPosts(1);
}
//...
private void getPosts(int page){
int insertStartPos = postArrayList.size();
Post[] posts;
//get posts from the backend
for (int i = 0; i < posts.length(); i++){
postArrayList.add(posts[i]);
}
DiaryAdapter adap = ((DiaryAdapter) recycler.getAdapter());
if (insertStartPos == 0){
adap.notifyDataSetChanged();
} else {
adap.notifyItemRangeInserted(insertStartPos, bundleSize);
}
}
One way to avoid calling getNewItems(1) twice that means that you should have a global variable on your class, for example, named currentPage that is initialised with value 1 and is incremented every time you call getNewItems().
From now on call
getNewItems(currentPage);
currentPage++;
I have a recyclerview inside a fragment and that fragment is inside a view pager.
notifydatasetchanged() is not working even though the same code works in other projects of mine. The only difference I can see is that it is inside a view pager now?.
This is how notify the change:
public void updateRecyler(){
// Clear the current array
alarmsFromSP.clear();
// Update the array (This is working the array size is changing)
alarmsFromSP.addAll(AlarmCollection.getAlarms(getActivity()));
// Notify the change
adapter.notifyDataSetChanged();
}
I create the recylerview in oncreate like this:
public class FragmentAllAlarms extends Fragment {
// Arraylist of the alarm data from the strings
ArrayList<Alarm> alarmsFromSP;
tabRefreshReceiver r;
RecyclerView rv;
AlarmsAdapter adapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_all_alarms, container, false);
// Recycler
rv = (RecyclerView) v.findViewById(R.id.mainAlarmRecyclerView);
rv.setLayoutManager(new LinearLayoutManager(getActivity()));
// Get saved data for alarms from the shared preferences string
alarmsFromSP = AlarmCollection.getAlarms(getActivity());
System.out.println("StringIs"+alarmsFromSP.size());
// Adapter
adapter = new AlarmsAdapter(getActivity(), alarmsFromSP);
rv.setAdapter(adapter);
Is it possible I am losing reference to the array or is it somthing to do with the viewpager?
I have been stuck with this for two days now any help is greatly appreciated.
Thanks in advance
make sure that updateRecyler() is called,
and try to change adapter.notifyDataSetChanged(); to rv.getAdapter().notifyDataSetChanged();
this worked for me once.
I have faced the same problem, then I found a temporary solution, instead of using notifyDataSetChanged() use
recyclerView.setAdapter(adapter);
recyclerView.scrollToPosition(postion);
I have got CardView with Views inside a RecyclerView. I have created adapter in which assets are attached to Views and everything works. Now, I would like to change these Views from my activity. Is it any simple way to do that?
public class MainActivity extends AppCompatActivity {
RecyclerView recyclerView;
private RecyclerView.LayoutManager layoutManager;
private List<Offer>offers;
TextView timer; //timer inside CardView
private void getViewReferences() {
recyclerView = (RecyclerView)findViewById(R.id.mainRecyclerView);
timer = (TextView)findViewById(R.id.timer);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getViewReferences();
layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
initializeData();
initializeAdapter();
timer.setText("13:16"); //NullPointerException here
}
private void initializeData(){
offers = new ArrayList<>();
offers.add(new Offer("godzina", R.drawable.zdj, 200));
offers.add(new Offer("godzina", R.drawable.zdj));
}
private void initializeAdapter() {
RecyclerViewAdapter adapter = new RecyclerViewAdapter(offers);
recyclerView.setAdapter(adapter);
}
}
So you don't want to change the Views but you want to update the text values of the views in your list.
You can't directly do that by trying to find the Views in the recyclerView and change the text. The adapter is responsible for giving values to your list. So you need to update the dataset in the adapter and call one of the notifyDataSetChanged methods on the adapter to update the recyclerView.
The big advantage of this design is that it places Views in Cache in order to reuse them multiple times without having to inflate and construct them again. This is a huge performance improvement.
You need to read the docs and some tutorials to understand the adapter design pattern better.
Here's my situation, I have 1 Activity, and 2 Fragments. When the Activity is started it loads a Fragment, let's call it list, into the view (in a FrameLayout filling the screen). List simply lists blogs posts which are retreived via an async HTTP call to out web api. When a blog is clicked in list is runs this method in the Activity
public void loadBlog(FragmentBlog.Blog theBlog) {
blog = theBlog;
FragmentBlogDetails d = new FragmentBlogDetails();
Bundle bundle = new Bundle();
bundle.putParcelable(KEY_BLOG, theBlog);
d.setArguments(bundle);
fragmentManager.beginTransaction().replace(R.id.container, d).addToBackStack("Detail").commit();
}
Then the blog loads up and is displayed fine. Now, once I press the back key, my list Fragment calls onCreateView again and I have to run the async call again to populate my list, which reset the scroll to the top too. How can I keep my data for needing to be fetched again, and keep the view so the scroll stays where it is?
Here's my onCreateView method in the list Fragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d("FragmentBlog", "onCreateView");
View view = inflater.inflate(R.layout.fragment_blog, container, false);
list = (ListView) view.findViewById(R.id.the_list);
list.setOnItemClickListener(this);
if (baa != null) {
list.setAdapter(baa);
}
text = (TextView) view.findViewById(R.id.my_text_view);
pb = (ProgressBar) view.findViewById(R.id.progressBar);
asyncForBlogs();
return view;
}
If you need any other bits of code let me know and I'll post them.
You need to save index of last item user clicked. Define a index variable in your fragment and when users clicked on an item store item index in it. Then save data in onSaveInstanceState Call back. You can save other data like this. But make sure the data you put inside bundle is not so big. Do not put any images or videos here!
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt("index",index);
super.onSaveInstanceState(outState);
}
after saving your data you can access it in onCreate method. like this
public void onCreate(Bundle savedInstanceState) {
if (savedInstanceState != null) {
index = savedInstanceState.getInt("index");
listView.smoothScrollToPosition(index);
}
}
smoothScrollToPosition scroll your list view to the last place you were been.