Hiy guys,
I have a fragment with a RecyclerViewin it. That RecyclerView is populated by a Firebase 'DatabaseReference' object and on that reference I have added a ValueListener as follows:
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
savedInstanceState) {
Log.d(TAG, "onCreateView");
View view = inflater.inflate(R.layout.fragment_home, container, false);
databaseReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
//Do something
}
#Override
public void onCancelled(DatabaseError databaseError) {
// Getting Post failed, log a message
Log.w(TAG, "onCancelled", databaseError.toException());
}
});
ItemRecyclerViewAdapter adapter = new ItemRecyclerViewAdapter(activity, itemsDatabaseReference);
adapter.setHasStableIds(true);
// use a linear layout manager
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.recyclerview);
recyclerView.setHasFixedSize(true);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
DividerItemDecoration itemDecoration = new DividerItemDecoration(getContext(),
DividerItemDecoration.VERTICAL_LIST);
recyclerView.addItemDecoration(itemDecoration);
recyclerView.setAdapter(adapter);
return view;
}
ItemRecyclerViewAdapter extends FirebaseRecyclerAdapter. The problem is that onDataChange happens after onResume so that at times the recycler is empty. What I can't understand is the fact that the recycler view is not always empty. Sometimes data are shown and sometimes not.
Thanks
I think if you are using FirebaseRecyclerAdapter (or any class extended from that) as your RecyclerView adapter, you should set recyclerView.setHasFixedSize(false). So the recyclerView knew that data is constantly updated.
Actually onResume(); is strictly bound with activity onResume();
If you are using viewpager then there is a method in fragment
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
//do your logic . updating recycler view
}
if you are infalting in framelayout
then you can update in onViewCreated();
Related
I have an activity HomeActivity where I navigate between two fragments Fragment1 and Fragment2. The Fragment1 fragment contains a recyclerview. When I move to Fragment2, Fragment1 is paused and stopped. When I come back to Fragment1, the list loads again. How to return to the scroll position from which I left? One way which I have tried is to save the state of recyclerview in a bundle before the fragment is paused in onPause() method. Now, how do I use this save state when view is created?
The navigation code is:
final NavController navController = Navigation.findNavController(this, R.id.nav_controller);
binding.go_to_fragment2.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
navController.navigate(R.id.fragment2);
}
});
binding.go_to_fragment1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
navController.navigate(R.id.fragment1);
}
});
The code for Fragment1 is:
public class Friends extends Fragment1 {
private FriendsFragmentBinding binding;
private MyAdapter myAdapter;
private static Bundle RecyclerState;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
binding = FriendsFragmentBinding.inflate(inflater, container, false);
binding.mRecycler.setHasFixedSize(true);
binding.mRecycler.setLayoutManager(new LinearLayoutManager(getContext()));
PagedList.Config config = new PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setPrefetchDistance(2)
.setPageSize(5)
.build();
// Init Adapter Configuration
Query mQuery = FirebaseFirestore.getInstance().collection("posts");
FirestorePagingOptions<Post> options = new FirestorePagingOptions.Builder<Post>()
.setLifecycleOwner(this)
.setQuery(mQuery, config, Post.class)
.build();
binding.swipe.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
myAdapter.refresh();
}
});
myAdapter = new MyAdapter(options, new MyAdapter.GetState() {
#Override
public void thisState(boolean state) {
if (state) {
binding.swipe.setRefreshing(true);
} else {
binding.swipe.setRefreshing(false);
}
}
});
binding.mRecycler.setAdapter(myAdapter);
return binding.getRoot();
}
#Override
public void onPause() {
super.onPause();
Parcelable listState = Objects.requireNonNull(binding.mRecycler.getLayoutManager()).onSaveInstanceState();
RecyclerState = new Bundle();
RecyclerState.putParcelable("key", listState);
}
}
You're calling binding.mRecycler.setAdapter(myAdapter); before your adapter has any data set. It is when your adapter is set that the saved scroll position is restored. Since there's no items in your adapter, there's nothing to scroll back to, so your scroll position is lost.
As per the Restore RecyclerView scroll position blog post, you have two options:
Don't set your adapter until you get data for the first time.
Upgrade to RecyclerView 1.2.0-alpha02 or higher and switch the StateRestorationPolicy to PREVENT_WHEN_EMPTY:
adapter.stateRestorationPolicy = PREVENT_WHEN_EMPTY
Which will mean that the RecyclerView will wait until your adapter is populated with data before restoring its state, thereby restoring your scroll position correctly.
Can someone tell me why the submitList() method can't be accessed in the following code? Similar code is working fine in another test app, although that's in an Activity.
public class MyFFBooksFragment extends Fragment {
private int currentBook;
private RecyclerView mRecyclerView;
private RecyclerView.LayoutManager mLayoutManager;
private GamebookViewModel gamebookViewModel;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable
ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.myffbooks_fragment, container, false);
mLayoutManager = new LinearLayoutManager(getActivity());
final ArrayList<BookItem> bookList = new ArrayList<>();
final BookItemAdapter maAdapter = new BookItemAdapter(bookList);
mRecyclerView = view.findViewById(R.id.recyclerView);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setHasFixedSize(true);
mRecyclerView.setAdapter(maAdapter);
gamebookViewModel = ViewModelProviders.of(this).get(GamebookViewModel.class);
gamebookViewModel.getAllGamebooks().observe(this, new Observer<List<Gamebook>>() {
#Override
public void onChanged(#Nullable List<Gamebook> gamebooks) {
maAdapter.submitList(bookList);
}
});
return view;
}
}
Perhaps you should not use it in onCreateView(). Try use call method that triggers observation of VM after an Activity had been created, near onStart(). Maybe it will help.
When a new page is available, we call submitList() method of the PagedListAdapter class
override fun onStart() {
super.onStart()
gamebookViewModel.callFunc()
}
Also as I understand, you call getAllGamebooks() which triggers some actions and return you LiveData<*> and you subscribe on it with observe(). I recommend you to separate this on 2 methods:
subscribe
action
Use PagedList instead of list like below:
gamebookViewModel.getAllGamebooks().observe(this, new Observer<PagedList<Gamebook>>() {
#Override
public void onChanged(#Nullable PagedList<Gamebook> gamebooks) {
maAdapter.submitList(bookList);
}
});
And in your viewmodel too.
Found the problem. My adapter wasn't extending ListAdapter, so no wonder it couldn't find the submitList() method. Rookie error.
I'm updating my app to use Firebase's Firestore database. I'm struggling to get the app to show the data that's been retrieved from the database. The data is retrieved ok, but doesn't show up. By setting breakpoints, I've established that the ViewHolder isn't being bound to the adapter at any point.
The data is being shown in a Fragment. The Fragment layout is (I've taken out irrelevant stuff like padding, sizes etc):
<android.support.constraint.ConstraintLayout
android:id="#+id/layout_charts_list"
tools:context="apppath.fragments.ChartListFragment">
<TextView
android:id="#+id/loading_list"
android:text="#string/loading_my_charts" />
<TextView
android:id="#+id/empty_list"
android:text="#string/my_charts_empty"
android:visibility="gone"/>
<android.support.v7.widget.RecyclerView
android:id="#+id/charts_list" />
</android.support.constraint.ConstraintLayout>
The Fragment code itself is:
public abstract class ChartListFragment extends Fragment {
private FirebaseFirestore mDatabaseRef;
private FirestoreRecyclerAdapter<Chart, ChartViewHolder> mAdapter;
private Query mChartsQuery;
private RecyclerView mRecycler;
public ChartListFragment() {}
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View rootView = inflater.inflate(
R.layout.fragment_charts_list, container, false);
mRecycler = rootView.findViewById(R.id.charts_list);
mRecycler.setHasFixedSize(true);
// Set up Layout Manager, and set Recycler View to use it
LinearLayoutManager mManager = new LinearLayoutManager(getActivity());
mManager.setReverseLayout(true);
mManager.setStackFromEnd(true);
mRecycler.setLayoutManager(mManager);
// Connect to the database, and get the appropriate query (as set in the actual fragment)
mDatabaseRef = FirebaseFirestore.getInstance();
mChartsQuery = getQuery(mDatabaseRef);
// Set up Recycler Adapter
FirestoreRecyclerOptions<Chart> recyclerOptions = new FirestoreRecyclerOptions.Builder<Chart>()
.setQuery(mChartsQuery, Chart.class)
.build();
mAdapter = new ChartListAdapter(recyclerOptions);
// Use Recycler Adapter in RecyclerView
mRecycler.setAdapter(mAdapter);
return rootView;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Add listener to charts collection, and deal with any changes by re-showing the list
mChartsQuery.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot queryDocumentSnapshots,
#Nullable FirebaseFirestoreException e) {
if (queryDocumentSnapshots != null && queryDocumentSnapshots.isEmpty()) {
((MainActivity) getActivity()).setPage(1);
mRecycler.setVisibility(View.GONE);
}else {
mRecycler.setVisibility(View.VISIBLE);
}
}
});
}
// HELPER FUNCTIONS
public abstract Query getQuery(FirebaseFirestore databaseReference);
}
ChartListAdapter is as follows:
public class ChartListAdapter
extends FirestoreRecyclerAdapter<Chart, ChartViewHolder> {
public ChartListAdapter(FirestoreRecyclerOptions recyclerOptions) {
super(recyclerOptions);
}
#Override
protected void onBindViewHolder(ChartViewHolder holder, int position, Chart model) {
holder.setChartName(model.getName());
// Bind Chart to ViewHolder
holder.bindToChart(model);
}
#Override
public ChartViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_chart, parent, false);
return new ChartViewHolder(view);
}
}
ChartViewHolder:
public class ChartViewHolder extends RecyclerView.ViewHolder {
private TextView chartNameView;
private String chartKey;
public ChartViewHolder(View itemView) {
super(itemView);
chartNameView = itemView.findViewById(R.id.chart_name);
}
public void setChartName(String chartName) {
chartNameView.setText(chartName);
}
public void bindToChart(Chart chart) {
chartKey = chart.getKey();
chartNameView.setText(chart.getName());
}
public String getChartKey() {
return chartKey;
}
}
The ChartListAdapter constructor is called, but onBindViewHolder and onCreateViewHolder are never called, and ChartViewHolder is never accessed at all. Am I missing a line of code? Or doing this completely wrong? I'm not all that familiar with Adapters and RecyclerViews, so I've found it quite hard to get to grips with putting it all together.
For those who came here from Google, set the lifecycle owner on the options so that start/stop listening is called automatically.
FirestoreRecyclerOptions<Chart> recyclerOptions = FirestoreRecyclerOptions.Builder<Chart>()
.setQuery(mChartsQuery, Chart.class)
.setLifecycleOwner(this)
.build();
You need to add the following, to begin listening for data, call the startListening() method. You may want to call this in your onStart() method. Make sure you have finished any authentication necessary to read the data before calling startListening() or your query will fail:
#Override
protected void onStart() {
super.onStart();
adapter.startListening();
}
more info here:
https://github.com/firebase/FirebaseUI-Android/tree/master/firestore
I want to display my data inside a fragment but it seems like some methods are not applicable in fragments only in Activity. The text with * are causing me errors. How i can execute it on a fragment?
String urlAddress = "http://capstonproject.xyz/project/tourist/retrieve/adventure";
RecyclerView recyclerView;
public Fadventure() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
//RecyclerView recyclerView = (RecyclerView) inflater.inflate(R.layout.fragment_fadventure, container, false);
return inflater.inflate(R.layout.fragment_fadventure, container, false);
recyclerView = view.findViewById(R.id.adventure_list);
recyclerView.setLayoutManager(new LinearLayoutManager(***this***));
recyclerView.setItemAnimator(new DefaultItemAnimator());
new DBFetch(***Fadventure.this***,recyclerView).execute();
//return recyclerView;
}
instead of this try with getActivity().getApplication().
hope it helps.
I am creating an app with many fragments but It throws an error when I try to access my ListView inside Fragment. I tested this code piece before in MainActivity and it worked fine.
Here is the fragment code :
public class Theme extends Fragment {
CustomAdapter adapter;
ListView lv;
FirebaseDatabase database;
DatabaseReference myRef;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.theme_layout, container, false);
database = FirebaseDatabase.getInstance();
myRef = database.getReference();
lv = (ListView) v.findViewById(R.id.list);
adapter = new CustomAdapter(getActivity(), getData());
lv.setAdapter(adapter);
return v;
}
private ArrayList getData() {
final ArrayList<Teemad> teemadelist = new ArrayList<>();
myRef.child("teemad").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Iterable<DataSnapshot> children = dataSnapshot.getChildren();
for(DataSnapshot child: children){
Teemad teemad = child.getValue(Teemad.class);
teemadelist.add(teemad);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
return teemadelist;
}
}
This is my layout file:
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:id="#+id/container">
<ListView
android:id="#+id/list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:divider="#color/material_blue_grey_800"
android:dividerHeight="1dp"
android:footerDividersEnabled="false"
/>
</FrameLayout>
And these are two of the main errors (at least I assume so) it throws:
03-23 01:14:50.296 9127-9127/com.example.richard.kodutoo_messenger W/View: requestLayout() improperly called by android.support.v7.widget.AppCompatTextView{2ffc0764 V.ED.... ......ID 16,123-227,180 #7f0d0087 app:id/bb_bottom_bar_title} during layout: running second layout pass
03-23 01:14:50.455 9127-9127/com.example.richard.kodutoo_messenger W/art: Before Android 4.1, method int android.support.v7.widget.ListViewCompat.lookForSelectablePosition(int, boolean) would have incorrectly overridden the package-private method in android.widget.ListView
Any kind of help or feedback would come in handy since I have been stuck at this place for a very long time. If additional information is needed to answer, then I will gladly provide it.
EDIT!!
I tried to use ListFragment instead of Fragment and after onCreateView I used onActivityCreated but it came out even worse.. Before it at least started the app, now the app crashes as soon as it starts.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.theme_layout, container, false);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
database = FirebaseDatabase.getInstance();
myRef = database.getReference();
lv = (ListView) getListView().findViewById(R.id.list);
adapter = new CustomAdapter(getActivity(), getData());
lv.setAdapter(adapter);
}
The problem is how you are using your adapter in combination with firebase events.
The right order would be something like this:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.theme_layout, container, false);
database = FirebaseDatabase.getInstance();
myRef = database.getReference();
lv = (ListView) v.findViewById(R.id.list);
adapter = new CustomAdapter(getActivity()); // Note : Here no passing of data
lv.setAdapter(adapter);
startListener();
return v;
}
public void startListener() {
myRef.child("teemad").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
ArrayList<Teemad> teemadelist = new ArrayList<>();
Iterable<DataSnapshot> children = dataSnapshot.getChildren();
for(DataSnapshot child: children){
Teemad teemad = child.getValue(Teemad.class);
teemadelist.add(teemad);
}
adapter.items = teemadelist; // Here items is a public field in adapter holding list of items
adapter.notifyDataSetChanged();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
The RequestLayout error was happening to me too. It would crash my App or throw an Exception but it would show up in the Log as if to warm me.
I solved it by putting the code where I was setting different margins for the ListView into a Runnable and call the Runnable with a Handler and no more warning/error.
I have come to believe that specific error most likely has to do with trying to adjust/change/redraw the ListView or some child Views belonging to the ListView
without using a Runnable.
I prefer to post the Runnable to a Handler anytime now when I change a View's or ViewGroup's attributes at runtime.