FirebaseRecyclerAdapter with empty view - android

I know there are lot of ways to have an empty view for a RecyclerView. But my question is for FirebaseRecyclerView.
My layout is:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/feed_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical" />
<ProgressBar
android:id="#+id/feed_loading"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone"/>
</RelativeLayout>
So I am showing Loading ProgressBar before the RecyclerView fills its items. Now what if server doesn't have any item. In this situation my RecyclerView is empty always and my Loading ProgressBar always visible to user.
So instead of showing the ProgressBar for indefinite period, I want to show some empty layout. E.g. "No Data Found" or something similar message.
The final result should be: ProgressBar should be shown until data is loaded to RecyclerView and once data is loaded ProgressBar should be invisible. But if no data is present in server, some empty layout should be shown instead of ProgressBar.
In normal RecyclerView, we have a dataset (some ArrayList, etc) and if it is empty then we can show that empty layout. But in case of FirebaseRecyclerAdapter, I dont have the reference of Snapshot in my Activity or Context. Nor I have any callback which tells me that no data is present in server.
Any workaround will help a lot.

Here is what I would try. First check out the accepted answer to the question linked below. It provides some very good insight into how Firebase queries work. I'd consider the info trusted since the answer is by someone on the Firebase team:
How to separate initial data load from incremental children with Firebase?
So, based on the answer to the question linked above and the fact that the FirebaseRecyclerAdapter is backed by a FirebaseArray which is populated using a ChildEventListener I would add a Single value event listener on the same database reference used to populate your FirebaseRecyclerAdapter. Something like this:
//create database reference that will be used for both the
//FirebaseRecyclerAdapter and the single value event listener
dbRef = FirebaseDatabase.getInstance().getReference();
//setup FirebaseRecyclerAdapter
mAdapter = new FirebaseRecyclerAdapter<Model, YourViewHolder>(
Model.class, R.layout.your_layout, YourViewHolder.class, dbRef) {
#Override
public void populateViewHolder(YourViewHolder holder, Model model, int position){
//your code for populating each recycler view item
};
mRecyclerView.setAdapter(mAdapter);
//add the listener for the single value event that will function
//like a completion listener for initial data load of the FirebaseRecyclerAdapter
dbRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
//onDataChange called so remove progress bar
//make a call to dataSnapshot.hasChildren() and based
//on returned value show/hide empty view
//use helper method to add an Observer to RecyclerView
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
That would handle the initial setup of the RecyclerView. When onDataChange is called on the single value event listener use a helper method to add an observer to the FirebaseRecyclerAdapter to handle any subsequent additions/deletions to database location.
mObserver = new RecyclerView.AdapterDataObserver() {
#Override
public void onItemRangeInserted(int positionStart, int itemCount) {
//perform check and show/hide empty view
}
#Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
//perform check and show/hide empty view
}
};
mAdapter.registerAdapterDataObserver(mObserver);

Answer from: https://stackoverflow.com/a/40204298/2170278
the FirebaseUI adapters nowadays have an onDataChanged() method that you can override to detect when they're done loading a set of data.
See the source code on github. From there:
This method will be triggered each time updates from the database have been completely processed. So the first time this method is called, the initial data has been loaded - including the case when no data at all is available. Each next time the method is called, a complete update (potentially consisting of updates to multiple child items) has been completed.
You would typically override this method to hide a loading indicator (after the initial load) or to complete a batch update to a UI element.
The FirebaseUI sample app overrides onDataChanged() to hide its "loading" indicator:
public void onDataChanged() {
// If there are no chat messages, show a view that invites the user to add a message.
mEmptyListMessage.setVisibility(getItemCount() == 0 ? View.VISIBLE : View.GONE);
}

Related

FirebaseRecyclerAdapter firing twice

I have a problem using FirebaseRecyclerAdapater, at first it was working fine but now this adapter is firing twice. The database reference is only referring one child, but it is always firing twice. The Toast with text "counter" will appear twice
FirebaseRecyclerAdapter<RequestVisit, RequestViewHolder> requestAdapter =
new FirebaseRecyclerAdapter<RequestVisit, RequestViewHolder>(
RequestVisit.class,
R.layout.seekerrequests_layout,
RequestViewHolder.class,
requestDatabase.child("2DBwmhGplGMoAlLy6337HZEShi93")
) {
#Override
protected void populateViewHolder(final RequestViewHolder viewHolder, RequestVisit model, int position) {
Toast.makeText(getContext(), "counter" +
viewHolder.getAdapterPosition(), Toast.LENGTH_SHORT).show();
}
};
requestVisitList.setAdapter(requestAdapter);
A Firebase*Adapter shows a list of items, the child nodes under the location that you attach it to.
If populateViewHolder gets called with two different positions, that means there are two children under requestDatabase.child("2DBwmhGplGMoAlLy6337HZEShi93").
Keep in mind that if 2DBwmhGplGMoAlLy6337HZEShi93 is a child node with two properties, then your approach will call populateViewHolder for each of those properties.
If you want to show only a single item in the RecyclerView, you can create a simple query with:
FirebaseRecyclerAdapter<RequestVisit, RequestViewHolder> requestAdapter =
new FirebaseRecyclerAdapter<RequestVisit, RequestViewHolder>(
RequestVisit.class,
R.layout.seekerrequests_layout,
RequestViewHolder.class,
requestDatabase.orderByKey().equalTo("2DBwmhGplGMoAlLy6337HZEShi93")
)

Delete data from firebase recycler view adapter without deleting in the database

That is it, can i remove a view from firebase recycler view adapter, but not deleting in the database?How?
final FirebaseRecyclerAdapter<Game,GameViewHolder> firebaseRecyclerAdapter=new FirebaseRecyclerAdapter<Game, GameViewHolder>(
Game.class,
R.layout.row,
GameViewHolder.class,
mdatabase
) {
#Override
protected void populateViewHolder(GameViewHolder viewHolder, Game model, final int position) {
viewHolder.setTitle(model.getGame_name());
viewHolder.setJugado(model.getJugado());
viewHolder.setCreator(model.getCreator_name());
recyclerView.setAdapter(firebaseRecyclerAdapter);
}
FirebaseRecyclerAdapter doesn't support this. The whole point of FirebaseRecyclerAdapter is that it stays in sync with the contents of the location that you originally gave it. If something deletes data from the database, it will be automatically and unconditionally deleted from your adapter.
If you want this behavior, you will have to implement your own RecyclerView adapter, preferably one that does not use an active listeners to be notified of every change to the location of the data. You would have to listen to the entire contents just once, then send that to an adapter to populate a RecyclerView.
And how about this in ViewHolder?
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
itemView.setVisibility(View.INVISIBLE);
}
});
Yes you can. You have to add this in your GameViewHolder class:
Create a function and get reference of your cardview
Create a layout params and Create an instance of the parent of your cardview then set the height and the width equals to 0
public void Remove() {
cardView = mView.findViewById(R.id.cardview);
cardView.setLayoutParams(new FrameLayout.LayoutParams(0,0));
}
Use the function
viewHolder.remove();

Getting notified about item delete in RealmRecyclerView

I am using the RealmRecyclerView from this post:https://realm.io/news/android-realm-listview/
In this ToDo app, when item is swiped, it is automatically deleted from RecyclerView as well as Realm Database.
I want to get notified when an item is deleted, also which item is deleted so I can perform some action with that item.
I tried using the Realm Change Listener, but that is invoked every time when a Realm Transaction is committed. So it is invoked even when a new item is added.
How do I do this? Is it possible with normal RecyclerView?
At this moment (with version V1.1.0), Realm does not provide a callback when a RealmObject is deleted. This is true for all types of data/views (listView, RecyclerView, RealmObject, RealmResults etc), also true if you query data asObservable or use a change listener.
However it does make sense to send an empty object, when the queried object is deleted from Realm, but since its a breaking change in Realm, we will have to wait for V2.0.
More details - https://github.com/realm/realm-java/issues/3138
According to the answer, they have not yet added this feature. But if you want to achieve this, you can use normal ReacyclerView using the existing RealmAdapter and everything as it is.
Here's how to do it:-
Remove the RealmRecyclerView and add the normal RecyclerView:-
1. Add the normal RecyclerView from the support library.
2. Initialize the recyclerview with the existing adapter i.e the adapter class that extends RealmBasedRecyclerViewAdapter, no need to make a new adapter
recyclerView=(RecyclerView)findViewById(R.id.realm_recyeler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter = new FilterAdapter(this,results,true,true);
recyclerView.setAdapter(adapter);
3. Next, we will use the ItemTouchHelper class to implement the swipe to dismiss for the RecyclerView :-
ItemTouchHelper.SimpleCallback callback = new ItemTouchHelper.SimpleCallback(0,ItemTouchHelper.RIGHT) {
#Override
public boolean onMove(RecyclerView recyclerView,RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
adapter.remove(viewHolder.getAdapterPosition(),alarmintent);
}
};
I've made a method in my adapter to remove item(shown below), you can do it here as well
The viewHolder.getAdapterPositon() gives the position of item swiped, it is passed to delete the RealmObject from Realm DB at given position(shown below)
0 -> drag flag - since I am not implement drag to move items, I've kept it as zero
ItemTouchHelper.RIGHT - swipe flags - These say in which direction the swipe to dismiss is set
ItemTouchHelper.RIGHT - swipe in right direction to dismiss
ItemTouchHelper.LEFT - swipe in left direction to dismiss
To support both directions, pass- ItemTouchHelper.RIGHT | ItemTouchHelper.LEFT
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(callback);
itemTouchHelper.attachToRecyclerView(recyclerView);
Create a new ItemTouchHelper object with above callback
Attach the ItemTouchHelper to RecyclerView
4. Here's how to remove the item (below is the code of my remove method of adapter):-
public void remove(int position)
{
RealmConfiguration configuration = new RealmConfiguration.Builder(context).deleteRealmIfMigrationNeeded().build();
realm = Realm.getInstance(configuration);
realm.beginTransaction();
realmResults.deleteFromRealm(position);
realm.commitTransaction();
notifyItemRemoved(position);
}
deleteFromRealm method is used to delete item at given position
call the notifyItemRemoved(position) to indicate item is removed at specified position
5. That's it, very easy and no need to create new adapters etc.
While Realm may not provide a callback for when an item is deleted, there is a way to know when items are deleted when using the RealmRecyclerView from this post.
The adapter (RealmBasedRecyclerViewAdapter) will call onItemSwipedDismiss(int position) whenever an item is swiped for dismissal. In your subclass of this adapter, you can override this method to add some extra logic.
For example, in my Recycler View, I want to give users the option to Undo a deletion. So, I override onItemSwipedDismiss(int position) and access the fields of the object being deleted. (In my case, this object is fairly small -- only three fields -- so this isn't too unwieldy). Then I call the super method: super.onItemSwipedDismiss(position); which will animate the deletion and remove it from Realm.
Then, I create a Snackbar with an action that re-creates the Realm object from the saved fields. Once it's created, it immediately goes back into the recycler view.
Here's a skeleton of the implementation of this method override:
#Override
public void onItemSwipedDismiss(int position) {
// Gather the object's fields, if you want:
YourObject objectToDelete = realmResults.get(position);
final String title = objectToDelete.getTitle();
final long timestamp = objectToDelete.timestamp;
// Perform delete and animation:
super.onItemSwipedDismiss(position);
// Add code here depending on what you want to do
// (for example, you could add a Snackbar that undoes
// the deletion by "resurrecting" your deleted object)
}

RecyclerView Adapter onBindViewHolder never called unless I turn the screen off and on

My adapter works fine, it shows all the items I have given to it using a list etc. The only problem is that the items are never shown unless I turn the device screen on and off! Only then is the method onBindViewHolder called.
The code I use to create&set the adapter. It is ran from a different thread using the EventBus library hence runOnUiThread
#Subscribe
public void onMessageEvent(EchoListEvent event){
final EchoListEvent eventt = event;
runOnUiThread(new Runnable() {
#Override
public void run() {
final EchoListAdapter adapter = new EchoListAdapter(eventt.getList(), R.layout.location_item);
final RecyclerView listview = (RecyclerView) findViewById(R.id.main_recycler_view);
listview.setAdapter(adapter);
LinearLayoutManager layoutManager = new LinearLayoutManager(getApplicationContext());
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
listview.setLayoutManager(layoutManager);
}
});
}
from resource .xml the RecyclerView
<android.support.v7.widget.RecyclerView
android:id="#+id/main_recycler_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
I've also tried forcing the onBindViewHolder call using Adapter#notifyDataSetChanged but nothing happens.
I found the answer myself. Basically, the list I was passing to the event was getting populated by an callback which ran an in yet another thread. The API that the callback uses is kind of slow, so the list only got populated after the event had already been posted. The solution was to wait for the callback to finish before posting the event
TL;DR: threading is hard

How to categorize list items in a recyclerview?

I am building a notifications list for an application I'm working on and I'm having trouble finding a way to take my list of notifications from the server and displaying them in separate lists in a RecyclerView. The end product would display the list of notifications with headers for Recent notifications and Older notifications, a la:
<RECENT HEADER>
<NOTIF-1>
<NOTIF-2>
<OLDER HEADER>
<NOTIF-3>
<NOTIF-4>
<NOTIF-5>
<NOTIF-6>
except instead of angle-bracket text it's actual views representing those, complete with images, actual notification details and dividers.
I already have code that displays them in a RecyclerView:
XML:
<!-- Main layout -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="#layout/include_toolbar"/>
<RelativeLayout
android:id="#+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/notification_swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.mapjungle.mymoose.ui.widget.EmptyRecyclerView
android:id="#+id/notification_list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.v4.widget.SwipeRefreshLayout>
</RelativeLayout>
</LinearLayout>
Java:
#InjectView(R.id.notification_list) RecyclerView mRecyclerView;
#Inject Picasso mPicasso;
#Inject NotificationService mUserService;
private NotificationAdapter mAdatper;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_notifications);
ButterKnife.inject(this);
setTitle("Notifications");
mAdatper = new NotificationAdapter(mPicasso);
mRecyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(this)
.color(getResources().getColor(R.color.secondary_color))
.size(1)
.build());
final LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(layoutManager);
mRecyclerView.setAdapter(mAdatper);
updateList();
}
#Override
protected int getSelfNavDrawerItem() {
return NAVDRAWER_ITEM_PHOTO_POST;
}
public void updateList() {
mUserService.getNotifications(new Callback<List<Notification>>() {
#Override
public void success(List<Notification> notificationList, Response response) {
mAdatper.replaceWith(notificationList);
}
#Override
public void failure(RetrofitError error) {
Timber.e(error, "Failed to load notifications...");
}
});
}
This all works fine enough to display all of the notifications and they're all sorted in the order from newest to oldest descending. But each has a boolean property "acknowledged" that is set to false if the user hasn't seen them before. I want to put split the list into the two groups I've explained above using this flag, but I don't know how to throw in the headers. I've thought about subclassing Notification to create NotificationHeader views and inserting them into the list where appropriate but that just feels sloppy to me. I've also thought about doing two recycler views, one for the new and another for the old, but visually that didn't work the way I intended (I haven't confirmed it but it looked like each recycler view scrolled independently of the others, something that I do not want). Any suggestions?
I know that the first idea of creating special Notification Headers will probably work, I've done something like that before, but it just feels like bad practice.
RecyclerView.Adapter has a method called getItemViewType() that takes the position of an item in the adapter's list, and returns the view type it should use. In my case, the method looks like this:
#Override
public int getItemViewType(int position){
Notification n = mNotifications.get(position);
boolean useHeader = n.getType().equals(Notification.HEADER_OLDER) ||
n.getType().equals(Notification.HEADER_RECENT);
return useHeader ? this.USE_HEADER : this.DONT_USE_HEADER;
}
Which checks the items in the notification list and sees if they're a special static 'Header notification' object. This is used internally by the Adapter class and it passes the 'viewType' parameter to the onCreateViewHolder() method, which we also override:
#Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
int layout = viewType == USE_HEADER ?
R.layout.view_item_notification_header :
R.layout.view_item_notification;
NotificationItemView view = (NotificationItemView) LayoutInflater.from(viewGroup.getContext())
.inflate(layout, viewGroup, false);
return new ViewHolder(view);
}
Overriding this method allows us to use the viewType parameter to choose the appropriate layout to inflate for the ViewHolder.
There are some better style/good practice decisions things I should have done here, such as making my Notification adapter hold a list of NotificationListItems instead of Notifications, which would allow me to put in a new kind of NotificationHeader object on it's own instead of making Notification objects that weren't really Notifications and using a bunch of constant values. But the underlying principle is still there:
In your Model, have a method that returns the layout view to use for it
In your adapter override getItemViewType() to use the aforementioned method and return an int that corresponds to the layout that should be inflated
In your adapter also override onCreateViewHolder() to use the int from getItemViewType() and inflate the appropriate view accordingly

Categories

Resources