I have a view pager that has two different fragments.
switch (i) {
case 0:
return LineChartOdoFragment.newInstance(msg, list, text, vehiclelist);
case 1:
return OdoTotalKmsFragment.newInstance(text, list, vehName, false, new OdoTotalKmsFragment.Updateable() {
#Override
public void update(TextView total_kms, TextView vehicle_name, RecyclerView odo_recycler) {
int total = 0;
for (int i = 0; i < list.size(); i++) {
total = total + (Integer.parseInt(list.get(i).getKms()) / 1000);
}
total_kms.setText("" + total);
vehicle_name.setText(vehName);
OdoKmsAdapter adapter = new OdoKmsAdapter(getActivity(), list);
odo_recycler.setLayoutManager(new LinearLayoutManager(getActivity()));
odo_recycler.setAdapter(adapter);
}
});
default:
return LineChartOdoFragment.newInstance(msg, list, text, vehiclelist);
}
Now the first fragment has a spinner and it is updated when different items are selected.
What i am looking for is when the values are updated in first fragment the second one should get updated too. (I have also set an interface in the second one.)
But the second one never updates and retains the previous values.
How do i update both fragmnets?
The first fragment calls an async task and the the handler updates both fragments.
private Handler handler = new Handler() {
#Override
public void handleMessage(Message msg) {
//myMessage.arg1
//1 - Home page data
if (msg.arg1 == 7) {
odoReadingsModelList = (ArrayList) msg.obj;
no_result_odo.setVisibility(View.GONE);
lineChart.setVisibility(View.VISIBLE);
lineChartOdo = new LineChartOdo(odoReadingsModelList, lineChart, context);
lineChartOdo.createLineChartOdo();
//updating second fragment
OdoTotalKmsFragment.newInstance("", odoReadingsModelList, vehName, true, new OdoTotalKmsFragment.Updateable() {
#Override
public void update(TextView total_kms, TextView vehicle_name, RecyclerView odo_recycler) {
Log.e("LineChart Odo ", "Updating");
int total=0;
for (int i = 0; i < odoReadingsModelList.size(); i++) {
total = total + (Integer.parseInt(odoReadingsModelList.get(i).getKms()) / 1000);
}
total_kms.setText(""+total);
vehicle_name.setText(vehName);
OdoKmsAdapter adapter = new OdoKmsAdapter(getActivity(), odoReadingsModelList);
odo_recycler.setLayoutManager(new LinearLayoutManager(getActivity()));
odo_recycler.setAdapter(adapter);
}
});
} else if (msg.arg1 == 8) {
String res = msg.obj.toString();
lineChart.setVisibility(View.GONE);
no_result_odo.setVisibility(View.VISIBLE);
no_result_odo.setText("" + res);
}
}
};
Use EventBus to pass data from 1 fragment to other.
Complete example from EventBus repo:
EventBus in 3 steps
Define events:
public static class MessageEvent { /* Additional fields if needed */ }
Prepare subscribers: Declare and annotate your subscribing method, optionally specify a thread mode:
#Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {/* Do something */};
Register and unregister your subscriber. For example on Android, activities and fragments should usually register according to their life cycle:
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
Post events:
EventBus.getDefault().post(new MessageEvent());
for detailed info read example on EventBus repo on github at this link
if you dont want to use third party library then use this method
To update 1 fragment from another you can create Interface as a Callback, and implement in your Activity and 2nd Fragment. Then you can pass data from 1 fragment to other.
I think OdoTotalKmsFragment.newInstance() will always give you a new object of OdoTotalKmsFragment, you should save the instance as a member, then update this member instance.
Related
With android Paging library it is really easy to load data from Database in chunks and ViewModel provides automatic UI update and data survival. All these frameworks modules help us create a great app in android platform.
A typical android app has to show a list of items and allows user to search that list. And this what I want to achieve with my app. So I have done an implementation by reading many documentations, tutorials and even stackoverflow answers. But I am not so sure whether I am doing it correctly or how I supposed to do it. So below, I have shown my way of implementing paging library with ViewModel and RecyclerView.
Please, review my implementation and correct me where I am wrong or show me how I supposed to do it. I think there are many new android developers like me are still confused how to do it correctly as there is no single source to have answers to all your questions on such implementation.
I am only showing what I think is important to show. I am using Room. Here is my Entity that I am working with.
#Entity(tableName = "event")
public class Event {
#PrimaryKey(autoGenerate = true)
public int id;
public String title;
}
Here is DAO for Event entity.
#Dao
public interface EventDao {
#Query("SELECT * FROM event WHERE event.title LIKE :searchTerm")
DataSource.Factory<Integer, Event> getFilteredEvent(String searchTerm);
}
Here is ViewModel extends AndroidViewModel which allows reading and searching by providing LiveData< PagedList< Event>> of either all events or filtered event according to search text. I am really struggling with the idea that every time when there is a change in filterEvent, I'm creating new LiveData which can be redundant or bad.
private MutableLiveData<Event> filterEvent = new MutableLiveData<>();
private LiveData<PagedList<Event>> data;
private MeDB meDB;
public EventViewModel(Application application) {
super(application);
meDB = MeDB.getInstance(application);
data = Transformations.switchMap(filterEvent, new Function<Event, LiveData<PagedList<Event>>>() {
#Override
public LiveData<PagedList<Event>> apply(Event event) {
if (event == null) {
// get all the events
return new LivePagedListBuilder<>(meDB.getEventDao().getAllEvent(), 5).build();
} else {
// get events that match the title
return new LivePagedListBuilder<>(meDB.getEventDao()
.getFilteredEvent("%" + event.title + "%"), 5).build();
}
}
});
}
public LiveData<PagedList<Event>> getEvent(Event event) {
filterEvent.setValue(event);
return data;
}
For searching event, I am using SearchView. In onQueryTextChange, I wrote the following code to search or to show all the events when no search terms is supplied meaning searching is done or canceled.
Event dumpEvent;
#Override
public boolean onQueryTextChange(String newText) {
if (newText.equals("") || newText.length() == 0) {
// show all the events
viewModel.getEvent(null).observe(this, events -> adapter.submitList(events));
}
// don't create more than one object of event; reuse it every time this methods gets called
if (dumpEvent == null) {
dumpEvent = new Event(newText, "", -1, -1);
}
dumpEvent.title = newText;
// get event that match search terms
viewModel.getEvent(dumpEvent).observe(this, events -> adapter.submitList(events));
return true;
}
Thanks to George Machibya for his great answer. But I prefer to do some modifications on it as bellow:
There is a trade off between keeping none filtered data in memory to make it faster or load them every time to optimize memory. I prefer to keep them in memory, so I changed part of code as bellow:
listAllFood = Transformations.switchMap(filterFoodName), input -> {
if (input == null || input.equals("") || input.equals("%%")) {
//check if the current value is empty load all data else search
synchronized (this) {
//check data is loaded before or not
if (listAllFoodsInDb == null)
listAllFoodsInDb = new LivePagedListBuilder<>(
foodDao.loadAllFood(), config)
.build();
}
return listAllFoodsInDb;
} else {
return new LivePagedListBuilder<>(
foodDao.loadAllFoodFromSearch("%" + input + "%"), config)
.build();
}
});
Having a debouncer helps to reduce number of queries to database and improves performance. So I developed DebouncedLiveData class as bellow and make a debounced livedata from filterFoodName.
public class DebouncedLiveData<T> extends MediatorLiveData<T> {
private LiveData<T> mSource;
private int mDuration;
private Runnable debounceRunnable = new Runnable() {
#Override
public void run() {
DebouncedLiveData.this.postValue(mSource.getValue());
}
};
private Handler handler = new Handler();
public DebouncedLiveData(LiveData<T> source, int duration) {
this.mSource = source;
this.mDuration = duration;
this.addSource(mSource, new Observer<T>() {
#Override
public void onChanged(T t) {
handler.removeCallbacks(debounceRunnable);
handler.postDelayed(debounceRunnable, mDuration);
}
});
}
}
And then used it like bellow:
listAllFood = Transformations.switchMap(new DebouncedLiveData<>(filterFoodName, 400), input -> {
...
});
I usually prefer to use DataBiding in android. By using two way Data Binding you don't need to use TextWatcher any more and you can bind your TextView to the viewModel directly.
BTW, I modified George Machibya solution and pushed it in my Github. For more details you can see it here.
I will strong advice to start using RxJava and you it can simplify the entire problem of looking on the search logic.
I recommend in the Dao Room Class you implement two method, one to query all the data when the search is empty and the other one is to query for the searched item as follows. Datasource is used to load data in the pagelist
#Query("SELECT * FROM food order by food_name")
DataSource.Factory<Integer, Food> loadAllFood();
#Query("SELECT * FROM food where food_name LIKE :name order by food_name")
DataSource.Factory<Integer, Food> loadAllFoodFromSearch(String name);
In the ViewModel Class we need to two parameter that one will be used to observed searched text and that we use MutableLiveData that will notify the Views during OnChange. And then LiveData to observe the list of Items and update the UI.
SwitchMap apply the function that accept the input LiveData and generate the corresponding LiveData output. Please find the below Code
public LiveData<PagedList<Food>> listAllFood;
public MutableLiveData<String> filterFoodName = new MutableLiveData<>();
public void initialFood(final FoodDao foodDao) {
this.foodDao = foodDao;
PagedList.Config config = (new PagedList.Config.Builder())
.setPageSize(10)
.build();
listAllFood = Transformations.switchMap(filterFoodName, outputLive -> {
if (outputLive == null || outputLive.equals("") || input.equals("%%")) {
//check if the current value is empty load all data else search
return new LivePagedListBuilder<>(
foodDao.loadAllFood(), config)
.build();
} else {
return new LivePagedListBuilder<>(
foodDao.loadAllFoodFromSearch(input),config)
.build();
}
});
}
The viewModel will then propagate the LiveData to the Views and observe the data onchange. In the MainActivity then we call the method initialFood that will utilize our SwitchMap function.
viewModel = ViewModelProviders.of(this).get(FoodViewModel.class);
viewModel.initialFood(FoodDatabase.getINSTANCE(this).foodDao());
viewModel.listAllFood.observe(this, foodlistPaging -> {
try {
Log.d(LOG_TAG, "list of all page number " + foodlistPaging.size());
foodsactivity = foodlistPaging;
adapter.submitList(foodlistPaging);
} catch (Exception e) {
}
});
recyclerView.setAdapter(adapter);
For the first onCreate initiate filterFoodName as Null so that to retrieve all items.
viewModel.filterFoodName.setValue("");
Then apply TextChangeListener to the EditText and call the MutableLiveData that will observe the Change and update the UI with the searched Item.
searchFood.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i,
int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int
i1, int i2) {
}
#Override
public void afterTextChanged(Editable editable) {
//just set the current value to search.
viewModel.filterFoodName.
setValue("%" + editable.toString() + "%");
}
});
}
Below is my github repo of full code.
https://github.com/muchbeer/PagingSearchFood
Hope that help
Usually on a RecyclerView I show an empty view when there are no items on the RecyclerView and since I control all updates to the RecyclerView via the notify methods then that is pretty simple but with PagedListAdapter updates just seem to happen on the background, so how can I hide or show my empty view?
For example, if I call deleteItem() on my Room DB, the PagedListAdapter will be updated on its own without me calling notifyItemDeleted but if it was the last item on the list, how does my code know to show the empty view? I could query the DB each time an action happens for the count but that seems wasteful. Is there a better way?
As mentioned in the comment, you can test emptiness of the list in the same LiveData observer you use for .submitList().
Java Example:
I am assuming you are following something similar to this snippet found in the PagedListAdapter document. I am simply adding emptiness check to that sample code.
class MyActivity extends AppCompatActivity {
#Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
RecyclerView recyclerView = findViewById(R.id.user_list);
UserAdapter<User> adapter = new UserAdapter();
viewModel.usersList.observe(this, pagedList -> {
// Check if the list is empty
updateView(pagedList.size());
adapter.submitList(pagedList));
pagedList.addWeakCallback(null, new PagedList.Callback() {
#Override
public void onChanged(int position, int count) {
updateView(pagedList.size())
// updateView(adapter.getItemCount())
}
...
}
}
recyclerView.setAdapter(adapter);
}
private void updateView(int itemCount) {
if (itemCount > 0) {
// The list is not empty. Show the recycler view.
recyclerView.setVisibility(View.VISIBLE);
emptyView.setVisibility(View.GONE);
} else {
// The list is empty. Show the empty list view
recyclerView.setVisibility(View.GONE);
emptyView.setVisibility(View.VISIBLE);
}
}
}
Kotlin Example:
The above Java example is actually just a Java translation of Kotlin example I found in this Android Paging codelab.
It's not the best solution, but you can give it a try
myViewModel.getMyPagedList().observe(MainActivity.this, items -> {
myPagedListAdapter.submitList(items);
Handler handler = new Handler();
Runnable runnable = new Runnable() {
#Override
public void run() {
if (myPagedListAdapter.getItemCount() == 0) {
runOnUiThread(() -> {
emptyTextView.setText("Empty");
emptyTextView.setVisibility(View.VISIBLE);
});
// The Runnable will be re-executed (repeated) as long as if condition is true
handler.postDelayed(this, 100);
} else
emptyTextView.setVisibility(View.GONE);
}
};
// trigger first time
handler.postDelayed(runnable, 1000);
});
I have a recyclerview and it includes several rows.
each row has an imageview(heart shape) for like and dislike and what I need is changing the image of imageview each time by my click.
when I start this activity and my list sets, it works well and the image changes by clicking on imageview, but the problem is when I start another activity and get back to this activity, onclick doesn't work.
It seems my adapter doesn't notify data!
This is my code after clicking on imageview:
public static void afterLikeChanged(Post post) {
if (!post.getPostIsLikedByYou()) {
post.setPostIsLikedByYou(true);
} else {
post.setPostIsLikedByYou(false);
}
mAdapter.notifyDataSetChanged();
}
and on the other side in adapter class:
boolean isliked = post.getPostIsLikedByYou();
if (!isliked) {
holder.imglike.setImageResource(R.drawable.btn_like);
} else {
holder.imglike.setImageResource(R.drawable.btn_liked);
}
any idea guys?!
make interface in adapter for like and dislike click event like below code ..
Note : take boolean value for like or like and make getter setter.
onItemClickListner onItemClickListner;
public interface onItemClickListner {
void onClick(User str);//pass your object types.
}
public void setOnItemClickListner(RecyclerViewAdpater.onItemClickListner onItemClickListner) {
this.onItemClickListner = onItemClickListner;
}
#Override
public void onBindViewHolder(ItemViewHolder holder, int position) {
// below code handle click event on recycler view item.
User data=userlist.get(position);
if (!data.isliked()) {
holder.imglike.setImageResource(R.drawable.btn_like);
} else {
holder.imglike.setImageResource(R.drawable.btn_liked);
}
holder.imageHeart.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
onItemClickListner.onClick(data);
}
});
}
then after adapter set into recylcerview call below code..
recyclerViewAdpater.setOnItemClickListner(new RecyclerViewAdpater.onItemClickListner() {
#Override
public void onClick(User str) {
if (str.isLike()){
str.setLike(false);
}
else{
str.setLike(true);
}
recyclerViewAdpater.notifyDataSetChanged();
}
});
You need to update your post list before notify the adapter
Something like this:
public static void afterLikeChanged(int position, Post post) {
if (!post.getPostIsLikedByYou()) {
post.setPostIsLikedByYou(true);
} else {
post.setPostIsLikedByYou(false);
}
// postList is a ArrayList that you passed to your adapter
postList.set(position, post);
mAdapter.notifyDataSetChanged();
}
You can not just call like that. Please try below steps:
Assume your current Activity is A. you will switch to activity named B
Using startActivityForResult(B.class, 0x0) instead of startActivity
Implement this in activity A:
boolean getPostIsLikedByYou = false;// This is the variable you get new value from Activity B
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// Check which request we're responding to
if (requestCode == 0x0) {
// Make sure the request was successful
if (resultCode == RESULT_OK) {
getPostIsLikedByYou = data.getBoolean("getPostIsLikedByYou");
}
}
}
From Activity B, do this when go back:
Intent resultIntent = new Intent();
enter code here
resultIntent.putBoolean("getPostIsLikedByYou", "your_value_here");
setResult(Activity.RESULT_OK, resultIntent);
Hope this help.
It seems like your code is a bit seperated. Something like this will encapsulate the behaviour. All you want is to change the icon and preserve the state of the posts.
It's important to keep track of what you've saved.
Your adapter class. It's missing a lot of code but something like this.
List<Post> posts; // init this in constructor
public void update(List<Post> posts) {
// do some nullchecking, maybe clone the posts. It's sent as a reference now.
this.posts = posts;
notifyDataSetChanged();
}
#Override
public void onBindViewHolder(final MyViewHolder viewHolder, int position) {
final Post post = posts.get(position);
if (post != null) {
if (post.isLiked()) {
holder.imglike.setImageResource(R.drawable.btn_liked);
} else {
holder.imglike.setImageResource(R.drawable.btn_like); // change name of this
}
}
}
public void afterLikeChanged(Post post) {
post.setIsLiked(!post.isLiked());
notifyDataChanged();
}
class MyViewHolder extends RecyclerView.ViewHolder {
// super, etc...
// this is a lazy way to use it, not safe.
// You should create a listener that sends back the getAdapterPosition()
itemView.setOnClickListener(v -> afterLikeChanged(posts.get(getAdapterPosition())));
}
Now make sure that you're hooking up the adapter to the recyclerview correctly. If it works the first time and not the second time, it seems like you are hooking things up in the wrong places. Use onPause and onResume instead.
Finally, I found the solution!
The problem was I had this line in onResume:
mAdapter = new RecyclerViewAdapterQuestions(this, arrayList);
ŮŽAfter removing this line the problem was resolved!
It seems in this section my adapter was resetting with new data, not with my previous array!
Thank you for answering me:)
is realm have something like listener for commiting data?
my code is
...
RealmDatabase.submitNewUser(mainActivity.getMyRealm(), userModel);
mainActivity.closeKeyboard();
mainActivity.onBackPressed();
...
public static void submitNewUser(Realm myRealm, UserModel user) {
myRealm.beginTransaction();
UserDb userDb = myRealm.createObject(UserDb.class);
userDb.setUserid(getNextUserId(myRealm));
userDb.setName(user.name);
....
myRealm.commitTransaction();
}
private static int getNextUserId(Realm myRealm) {
try {
return myRealm.where(UserDb.class).max("userid").intValue() + 1;
} catch (ArrayIndexOutOfBoundsException e) {
return 1;
}
}
after save data, i closed fragment and back to previous fragment.
on start function, checking if have data
#Override
public void onStart() {
super.onStart();
if (loading.isShowing()) loading.dismiss();
if (reloadList && checkContent()) {
....
}
reloadList = true;
}
private boolean checkContent() {
users = RealmDatabase.loadUserList(mainActivity.getMyRealm());
if (users.size() > 0 && users.get(0).userid > 0) {
// note:
// user id is auto increament while saved
// if no data, return dataset with userid = 0 for other purpose
return true;
} else {
....
return false;
}
}
public static List<UserModel> loadUserList(Realm myRealm) {
List<UserModel> list = new ArrayList<>();
RealmResults<UserDb> results = myRealm.where(UserDb.class).findAll();
results = results.sort("userid", Sort.DESCENDING);
for (UserDb result : results) {
UserModel userModel = new UserModel();
userModel.userid = result.getUserid();
....
userModel.note = result.getNote();
list.add(userModel);
}
if (list.size() == 0) {
UserModel userModel = new UserModel();
userModel.userid = 0;
userModel.note = "You still have no user at this time";
list.add(userModel);
}
return list;
}
checkContent(), user.size detected as 1 (new data is added) but userid still 0.
am i miss something in this logic? because everything is working well if i reopen app after add new user.
update
after using listener, i got my dataset but still not showing my content. after some trial i found that my list view is not showing the data even after i re-input data and do notifydataset on adapter.
users = RealmDatabase.loadUserList(mainActivity.getMyRealm());
homeAdapter.reloadList(users);
....
public void reloadList(List<UserModel> users) {
this.users = users;
notifyDataSetChanged();
}
update 2
everything going well for the 2nd, 3rd, and later item except the first one
is realm have something like listener for commiting data?
Yes
realmResults.addChangeListener(realmChangeListener);
One must keep a field reference to the RealmResults.
everything is working well if i reopen app after add new user.
Probably the ArrayList you build from the RealmResults is not updated.
as suggested by #epicPandaForce answer, i use listener to my code.
and to solved my problem as i mentioned in the last comment in #epicPandaForce answer, i change my code like this
getMyRealm().addChangeListener(new RealmChangeListener<Realm>() {
#Override
public void onChange(Realm element) {
// previous code in my activity
// getFragmentManager().popBackStackImmediate();
// new code in my activity
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.container, fragment);
}
});
those code not really inside the listener instead calling function on my activity. those placement just for where its called.
I'm building an application with Firebase on Android. The scenario for my application is as follows. Look at the following screen.
As you can see, in the above screen, I'm displaying a list of transactions based on the user role: Renter and Owner. Also, at the toolbar, user can easily filter any transaction statuses, shown in the following screen.
To achieve this scenario, I've modeled my database with the following structure:
- transactionsAll:
- transactionID1:
- orderDate: xxx
- role: Renter / Owner
- transactionID2:
....
- transactionsWaitingApproval:
- transactionID1:
- orderDate: xxx
- role: Renter / Owner
The thing is, in each of the Fragment, I've used an orderByChild query just to display the list of transactions based on the user role in each of the fragment, whether it's the Renter or the Owner, like so
public void refreshRecyclerView(final String transactionStatus) {
Query transactionsQuery = getQuery(transactionStatus);
//Clean up old data
if (mFirebaseRecyclerAdapter != null) {
mFirebaseRecyclerAdapter.cleanup();
}
mFirebaseRecyclerAdapter = new FirebaseRecyclerAdapter<Transaction, TransactionViewHolder>(Transaction.class, R.layout.item_transaction,
TransactionViewHolder.class, transactionsQuery) {
#Override
public int getItemCount() {
int itemCount = super.getItemCount();
if (itemCount == 0) {
mRecyclerView.setVisibility(View.GONE);
mEmptyView.setVisibility(View.VISIBLE);
} else {
mRecyclerView.setVisibility(View.VISIBLE);
mEmptyView.setVisibility(View.GONE);
}
return itemCount;
}
#Override
protected void populateViewHolder(final TransactionViewHolder viewHolder, final Transaction transaction, final int position) {
final CardView cardView = (CardView) viewHolder.itemView.findViewById(R.id.transactionCardView);
cardView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
}
});
viewHolder.bindToPost(getActivity(), transaction, new View.OnClickListener() {
#Override
public void onClick(View view) {
}
});
}
};
mRecyclerView.setAdapter(mFirebaseRecyclerAdapter);
mFirebaseRecyclerAdapter.notifyDataSetChanged();
}
Where the getQuery method is as follows:
private Query getQuery(String transactionStatus) {
Query transactionsQuery = null;
int sectionNumber = getArguments().getInt(SECTION_NUMBER);
if (sectionNumber == 0) { // Renter fragment
if (transactionStatus == null || transactionStatus.equals(MyConstants.TransactionStatusConstants.allTransactionsValue))
transactionsQuery = FirebaseDatabaseHelper.getTransactionsAllReference().orderByChild("productRenter").equalTo(UserHelper.getCurrentUser().getUid());
else if (transactionStatus.equals(MyConstants.TransactionStatusConstants.waitingApprovalValue))
transactionsQuery = FirebaseDatabaseHelper.getTransactionsWaitingApprovalReference().orderByChild("productRenter").equalTo(UserHelper.getCurrentUser().getUid());
...
}
if (sectionNumber == 1) { // Owner fragment
if (transactionStatus == null || transactionStatus.equals(MyConstants.TransactionStatusConstants.allTransactionsValue))
transactionsQuery = FirebaseDatabaseHelper.getTransactionsAllReference().orderByChild("productOwner").equalTo(UserHelper.getCurrentUser().getUid());
else if (transactionStatus.equals(MyConstants.TransactionStatusConstants.waitingApprovalValue))
transactionsQuery = FirebaseDatabaseHelper.getTransactionsWaitingApprovalReference().orderByChild("productOwner").equalTo(UserHelper.getCurrentUser().getUid());
...
}
return transactionsQuery;
}
With the above query, I've ran out of options to perform another orderByKey/Child/Value on the query. As written in the docs, I can't perform a double orderBy query.
You can only use one order-by method at a time. Calling an order-by
method multiple times in the same query throws an error.
The problem: With every new Transaction object pushed to the database, it is shown on the bottom of the recycler view. How can I sort the data based on the orderDate property, in descending order? So that every new transaction will be shown as the first item the recycler view?
In the same documentation page, it is said that:
Use the push() method to append data to a list in multiuser
applications. The push() method generates a unique key every time a
new child is added to the specified Firebase reference. By using these
auto-generated keys for each new element in the list, several clients
can add children to the same location at the same time without write
conflicts. The unique key generated by push() is based on a timestamp,
so list items are automatically ordered chronologically.
I want the items to be ordered chronologically-reversed.
Hopefully someone from the Firebase team can provide me with a suggestion on how to achieve this scenario gracefully.
Originally, I was thinking there would be some additional Comparators in the works, but it turns out Frank's answer led me to the right direction.
Per Frank's comment above, these two tricks worked for me:
Override the getItem method inside the FirebaseRecyclerAdapter as follows:
#Override
public User getItem(int position) {
return super.getItem(getItemCount() - 1 - position);
}
But I finally went with setting the LinearLayoutManager as follows:
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false);
linearLayoutManager.setStackFromEnd(true);
linearLayoutManager.setReverseLayout(true);
mRecyclerView.setLayoutManager(linearLayoutManager);
Although this solution does solve my problem, hopefully there will be more enhancements coming to the Firebase library for data manipulation.