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.
Related
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.
I am trying to sort a static array of Ticket objects into two different arrays based on their dates and Class. These two arrays will be used as data to populate two different list views. I only want to add one Ticket per Class object to each array. I get an error
ArrayList<Ticket> myUpcomingTickets = new ArrayList<>();
ArrayList<Ticket> myPastTickets = new ArrayList<>();
for (Ticket t : Ticket.getTickets()) {
if (t.getClass().getDate().after(date)) {
if(myUpcomingTickets.isEmpty()){
myUpcomingTickets.add(t);
}else {
for(Ticket t1: myUpcomingTickets) {
if (!t.getClass().getId().equals(t1.getClass().getId())) {
myUpcomingTickets.add(t);
}
}
}
} else {
if(myPastTickets.isEmpty()){
myPastTickets.add(t);
} else {
for(Ticket t2: myPastTickets) {
if (!t.getClass().getId().equals(t2.getClass().getId())) { //Add if not already there
myPastTickets.add(t);
}
}
}
}
}
Error Show in Logcat java.util.ConcurrentModificationException
There are a few things wrong with this. The error is caused by this
for(Ticket t2: myPastTickets) {
if (!t.getClass().getId().equals(t2.getClass().getId())) { //Add if not already there
myPastTickets.add(t);
}
}
you are not allowed to add or remove from a list you are currently looping through.
In addition this method would be very slow because of the nested loops you have.
My suggesting would be
1) extend your objects hash and equals to make your object easier to work with
public class Ticket {
#Override
public int hashCode() {
return getId().hashCode();
}
#Override
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final Ticket other = (Ticket ) obj;
return other.getId() == this.getId();
}
}
and then you can do this:
ArrayList<Ticket> myUpcomingTickets = new ArrayList<>();
ArrayList<Ticket> myPastTickets = new ArrayList<>();
for (Ticket t : Ticket.getTickets()) {
if (t.getClass().getDate().after(date)) {
if(!myUpcomingTickets.contains(t)){
myUpcomingTickets.add(t);
}
} else {
if(!myPastTickets.contains(t)){
myPastTickets.add(t);
}
}
}
and if you dont need the lists to be in an order i would suggest using a hashmap instead of arraylist to speed this up
You are modifying the lists (myUpcomingTickets, myPastTickets) while iterating them!
What you could do:
1. If not implemented yet, implement the equals and hashcode for Ticket object
2. Use list.contains(Obj) instead of using the for loop
E.g:
if(!myUpcomingTickets.contains(t)){
myUpcomingTickets.add(t);
}
I've made a simple application with a recyclerview in a fragment. In OnCreateView I get a list from SharedPreferences (from a separate class), then I save it into a private list like this:
mModel = AnotherClass.GetListFromSharedPreferences();
The problem is when I try to add an element to the RecyclerView (and in SharedPreferences) by clicking a button. This is my code when the button is pressed:
AnotherClass.saveInSharedPreferences(itemAdded);
mModel.add(ItemAdded);
saveInSharedPreferences get the saved list from SharedPreferences:
public boolean saveInSharedPreferences(#NonNull final Item item) {
List<item> currentSaved = GetListFromSharedPreferences();
if (currentSaved.size() == 0) {
currentSaved = new LinkedList<>();
}
currentSaved.add(item);
mCache = currentSaved ;
return save();
}
And it calls the save method, that save the list edited in SharedPreferences:
private boolean save() {
if (mCache != null) {
try {
final JSONArray array = new JSONArray();
for (Item item : mCache) {
JSONObject item = item.toJson(); //Just put everthing in a JSON Object
array.put(item);
}
final String arrayAsString = array.toString();
mSharedPreferences.edit().putString(KEY, arrayAsString).apply();
return true;
} catch (JSONException e) {
e.printStackTrace();
return false;
}
}
return false;
}
It works correctly(it adds the new item in SharedPreferences), but after the calling of this methods, seems like that my mModel already has the new Item added! Before calling mModel.add.
Since OnCreateView is called just 1 time when I call the fragment, and the mModel is valorized only in that time, I don't see how is possible that my mModel is modified just after I edit my SharedPreferences...?
to listen to newly added items use registerOnSharedPreferenceChangeListener it will keep called when items added
use it on onCreateView
SharedPreference.registerOnSharedPreferenceChangeListener (SharedPreferences.OnSharedPreferenceChangeListener listener)
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.
Currently, we are creating a new instance for every RealmObject that we want to save in our Mapper class.
#Override
public Person toRealmObject(Realm realm, PersonXML businessObject) {
Person person = new Person();
person.setId(businessObject.getId());
person.setName(businessObject.getName());
return person;
}
When we create a new one, we collect it into a list.
#Override
public void populateRealmListWithMappedModel(Realm realm, RealmList<Person> realmList, PersonsXML personXML) {
for(PersonXML personXML : personXML.getPersons()) {
realmList.add(personMapper.toRealmObject(realm, personXML));
}
}
/*next the following happens:*/
//realm.beginTransaction();
//personRepository.saveOrUpdate(realm, list);
//realm.commitTransaction();
Then we save the list.
#Override
public RealmList<T> saveOrUpdate(Realm realm, RealmList<T> list) {
RealmList<T> realmList = new RealmList<T>();
for(T t : realm.copyToRealmOrUpdate(list)) {
realmList.add(t);
}
return realmList;
}
The question is, is the following possible, can I re-use the same Person object instead and change its values to specify to Realm the objects that I want to have saved, but not have a whole object associated with it?
As in, something like this:
#Override
public Person toRealmObject(Realm realm, PersonXML businessObject, Person person) {
person.setId(businessObject.getId());
person.setName(businessObject.getName());
return person;
}
Then
#Override
public void writeObjectsToRealm(Realm realm, PersonsXML personXML) {
realm.beginTransaction();
Person person = new Person();
for(PersonXML personXML : personXML.getPersons()) {
person = personMapper.toRealmObject(realm, personXML, person));
personRepository.saveOrUpdate(person);
}
realm.closeTransaction();
}
Where this method is
#Override
public T saveOrUpdate(Realm realm, T t) {
return realm.copyToRealmOrUpdate(t);
}
I'm asking this because rewriting the architecture to use the following would require rewriting every populateRealmListWithMappedModel() methods I have, and that would be a bit concerning if it doesn't work. So I'm curious if it theoretically works.
Basically the short question is, if I call copyToRealmOrUpdate(t) on a realmObject, and alter its id and data, and save the same object again and again, will the write transaction succeed?
Yes you can re-use the object used as input to copyToRealm. We are creating a copy of your input object but not altering the original in any way, so reusing the object like you are doing should work and will also reduce the amount work the GC has to do.
Person javaPerson = new Person();
Person realmPerson = realm.copyToRealm(javaPerson);
// The following is true
assertTrue(javaPerson != realmPerson)
assertFalse(javaPerson.equals(realmPerson))
assertFalse(realmPerson.equals(javaPerson))
Yep, it worked.
#Override
public void persist(Realm realm, SchedulesXML schedulesXML) {
Schedule defaultSchedule = new Schedule();
if(schedulesXML.getSchedules() != null) {
realm.beginTransaction();
for(SchedulesByChannelXML schedulesByChannelXML : schedulesXML.getSchedules()) {
Channel channel = channelRepository.findOne(realm, schedulesByChannelXML.getChannelId());
if(channel == null) {
Log.w(TAG,
"The channel [" + schedulesByChannelXML.getChannelId() + "] could not be found in Realm!");
}
for(ScheduleForChannelXML scheduleForChannelXML : schedulesByChannelXML.getSchedules()) {
defaultSchedule = scheduleMapper.toRealmObject(realm, scheduleForChannelXML, defaultSchedule);
defaultSchedule.setChannel(channel);
defaultSchedule.setChannelId(schedulesByChannelXML.getChannelId());
boolean isInRealm = scheduleRepository.findOne(realm,
scheduleForChannelXML.getScheduleId()) != null;
Schedule savedSchedule = scheduleRepository.saveOrUpdate(realm, defaultSchedule);
if(!isInRealm) {
if(channel != null) {
channel.getSchedules().add(savedSchedule);
}
}
}
}
realm.commitTransaction();
}
}
Instead of creating 150 Schedule each time I save the batch of schedules, now I only create one per batch, and it works flawlessly.