How to sort a list in Flutter - android

I am building a list in flutter and need to sort it by date by showing the most recent timestamp first.
The list is created from a json like the one below:
[
{
"id":100,
"timestamp":"2021-02-02T15:15:11Z",
"name":"Entry1"
},
{
"id":101,
"timestamp":"2021-03-02T11:12:56Z",
"name":"Entry2"
}
]
Once the json is fetched with the fetchEntries function, I'd like to sort the list. This is my code:
class Values extends Object {
int id;
String timestamp;
String name;
Values(
{this.id,
this.timestamp,
this.name});
Values.fromJson(Map<String, dynamic> json) {
id = json["id"];
timestamp = json["timestamp"];
name = json["name"];
}
}
List<Values> _myList = [];
fetchReport() {
_timer = new Timer.periodic(Duration(seconds: 1), (timer) {
fetchEntries(dates.id.toString(), dates.from, dates.to)
.then((value) => {
_myList.addAll(value),
_postsController.add(1),
setState(() {})
});
_timer.cancel();
});
//This is the sort code that doesn't work
_myList.sort((a,b)=> a.timestamp.compareTo(b.timestamp));
}
Alternatively, the list can be sorted by id in decreasing order but the timestamp method is preferred. Any suggestions how to do it properly?

It is better to parse time after fetch data.
_myList.sort((a,b) {
DateFormat formatter = DateFormat("yyyy-MM-ddTHH:mm:ssZ");
DateTime aTime = formatter.parse(a["timestamp"]);
DateTime bTime = formatter.parse(b["timestamp"]);
return aTime.compareTo(bTime);
//return bTime.compareTo(aTime);
}

I think that issue why sorting does not work is that _myList.sort function is called before _myList is filled with data. The reason of that is that _myList is populated in future (when callback of fetchEntires(...).then) is called, while sort function is called right after timer is created (after Timer.periodic constructor).
In order to fix that you need to move _myList.sort to callback just after list is populated with data.
Regarding sorting itself.. While it should work comparing date in the format of your example, I would rather parse time to milliseconds and then compare those instead. Reason is that once you change date format to different one, like 'dd-MM-yyyy' sorting will be broken.

Related

How to Write Data Into Firebase Realtime Database with Cloud Functions when new Data is added to a Specific Child

I have the following realtime database schema:
schema
I'm working on a social app where a user can like another user. When the user clicks on the like button, a new entry will be added to myLikes->userId list
MyLike myLike = new MyLike(userId, System.currentTimeMillis();
FirebaseDatabase.getInstance().getReference().child("myLikes").child(userId).child(System.currentTimeMillis()).setValue(myLike);
When the new entry is completed, the cloud function gets triggered to write a new entry in whoLikedMe>userId list for the other User who has been liked.
exports.whoLikedMe = functions.database.ref('/myLikes/{userId}')
.onWrite((change, context) => {
// Only edit data when it is first created.
if (change.before.exists()) {
return null;
}
// Exit when the data is deleted.
if (!change.after.exists()) {
return null;
}
// Grab the current value of what was written to the Realtime Database.
const data2 = change.after.val();
const likedDate = data2.date;
const myUserId = data2.I_Liked_UserId;
var likedMe = {
date: likedDate,
Who_Liked_Me_UserId: myUserId,
}
//get the current date in millis
var hrTime = process.hrtime();
const dateMillis = hrTime[0];
return admin.database().ref('/WholikedMe/'+myUserId+'/'+hrTime[0]).set(likedMe);
});
The function gets triggered ok but no entries are inserted into the realtime database. What I'm doing wrong?
EDIT:
When I changed:
exports.whoLikedMe = functions.database.ref('/myLikes/{userId}')
with
exports.whoLikedMe = functions.database.ref('/myLikes/{userId}/915170400000')
(which adds the hardcoded timestamp to the path) all works fine. The problem is that the timestamp is constantly changing. any idea how to accomplish this with the timestamp?
You're using wildcard syntax (curly braces) in the following line:
return change.after.ref('/WholikedMe/{myUserId}/{hrTime[0]}').set(likeMe)
That syntax does not apply to a set statement. You need to use :
return change.after.ref('/WholikedMe/' + myUserId + '/' + hrTime[0]).set(likeMe)
... I think, doing this on my phone so can't double check. :-)
I figure d out. In addition to the syntax correction suggested by #GrahamD I had to change
exports.whoLikedMe = functions.database.ref('/myLikes/{userId}')
with
exports.whoLikedMe = functions.database.ref('/myLikes/{userId}/{timestamp')
and now all is working fine.

Retrieving null object from Firestore when its not actually null

I've been trying to understand why my query returns null object from server generated timestamp value.
Basically, I used onUpdate() trigger on my Firestore database and check, if the product is low on stock and make a reminder when the stock is <=5. This is my Node.js code and it currently works even tho it's got no proper responses.
const date = admin.firestore.FieldValue.serverTimestamp();
const reminder = {
productID : product.barcode,
date : date,
status: 'Order'
}
const docSnapShot = admin.firestore().collection('reminders').doc(product.barcode).get().then(documentSnapShot =>{
if(documentSnapShot.exists){
return admin.firestore().collection('reminders').doc(product.barcode).update({date: date}).then(res=> {
console.log('Document updated');
return null;
}).catch(error =>{
console.log('Error',error);
return null;
});
}
else{
exists = docSnapShot.exists;
return null;
}
});
Server successfully inserts the generated timestamp and even when manually added, It still retrieves a null object in Java/Android. Using FirestoreRecyclerView and an RecyclerView custom adapter and class. I tried ServerTimeStamp and double checked variable names, sequence and I still get a null object. I get proper productID and status values.
public reminderFirestore(Timestamp date, String productID, String status) {
this.productID = productID;
this.date = date;
this.status = status;
}
Has this something to do with my constructor and object type or did I mess up in the server/Node.js code?
You need to include the default empty constructor and getters for all fields. If only your timestamp is null, then you must not have a getter for timestamp.

Compare two date from JSON result in RecyclerView

I got a recyclerView that will populate according to a JSON result using databinding, what i want to do is to check/compare the start date & expired date to see is the "Deals" is expired (if expired a textview with text "DEALS EXPIRED" will appear)
"DealsPageInfo": [
{
"DisplayName": "string",
"StartDate": "2018-04-27T03:06:18.890Z",
"ExpiredDate": "2018-04-27T03:06:18.890Z",
"Url": "string",
"ImageUrl": "string",
"ShortDescription": "string"
}
Here is some question from me:
* should i use DATE / String to store the object?
* where should i perform this action? under my fragment / adapter?
Appreciate if any source / example provided.
<TextView
android:id="#+id/list_offers_startDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="3dp"
android:text="#{offers.StartDate}"
android:textColor="#color/colorGrayText"
android:textSize="#dimen/list_fontsize"
/>
<TextView
android:id="#+id/list_offers_endDate"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:text="#{offers.ExpiredDate}"
android:textColor="#color/colorGrayText"
android:textSize="#dimen/form_fontsize"
/>
* where should i perform this action? under my fragment / adapter?
perform this in onBindViewHolder of your adapter,
#Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, final int position){
if (isDateExpired(listItems.get(position).getStartDate(),listItems.get(position).getEndDate())){
viewHolder.dealsExpiredtextview.setVisibility(View.GONE);
}else {
viewHolder.dealsExpiredtextview.setVisibility(View.VISIBLE);
}
}
add this method in your adapter,
public boolean isDateExpired(String startDate, String endDate) {
SimpleDateFormat dfDate = new SimpleDateFormat("dd-MM-yyyy");
boolean b = false;
try {
if (dfDate.parse(startDate).before(dfDate.parse(endDate))) {
return true; // If start date is before end date.
} else if (dfDate.parse(startDate).equals(dfDate.parse(endDate))) {
return false; // If two dates are equal.
} else {
return false; // If start date is after the end date.
}
} catch (ParseException e) {
e.printStackTrace();
return false;
}
}
should i use DATE / String to store the object?
Yes you have to save both values in Date or string.
where should i perform this action? under my fragment / adapter?
You have to perform this function in your adapter. while populate data to view.
compare date :
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
Date StartDate= sdf.parse(StartDate);
Date ExpiredDate= sdf.parse(ExpiredDate);
if (StartDate.after(ExpiredDate)) {
//create your logic here
}
Hope it will help you!!
You can use timestamp to do this kind of time checking thing. Send start and end timestamp in your json. And you can do this in your adapter.
Yes, I think you should use Date to represent start date and whatever other date you have, it makes more sense.
I don't think your Adapter/View is the best place do to this conversion. You should either have your parser do that for you OR you could do some post processing on your List of objects once they have been fetched from the network. Preferably even on a background thread.
Parsing those Strings to Dates in your Adapter is kind of wasteful and might make your RecyclerView a bit laggy. It will cause for repeated calculations of the same values and possibly a lengthy date parsing on the UI thread.
Firstly if you want change textview text to "DEALS EXPIRED" you should perform action under your adapter.
If you don't want parse string again you can store date as Date object.
You can parse your date like this.
Instant instant = Instant.parse( "2018-04-27T03:06:18.890Z" );
Date date = java.util.Date.from( instant );
if ( Calendar.getInstance().after( date ) ) {
// if this statement is true deal is expired.
}

RealmResult dealing with time constraints

How to modify realm result data when dealing with date and time? ASCENDING and DESCENDING is not enough for me.
Say for example I am getting a task thats within an hour of due?
RealmResults<Task> tmp = realm.where(Task.class).findAll();
for (final Task task : tmp) {
String dateFormat = Utility.TIMEFORMAT;
String current = "2:30:00 PM";
Date currentTime = new Date(task.gettime());
//currentTime = new SimpleDateFormat(dateFormat).parse(current);
if( isIncomingWithInHour(currentTime, calendar)){
realmresult.add(tmp) /// this result in error.
}
}
As you can see ASCENDING and DESCENDING wont work in this kind of sorting. Anyway to give the reamlResult back to the adapter? Realmresult has an onchangeListener and I want to use that.
Instead of
RealmResults<Task> tmp = realm.where(Task.class).findAll();
You can do something like
RealmResults<Task> tasksInOneHour = realm.where(Task.class)
.greaterThanOrEqualTo("time", startTime)
.lowerThan("time", endTime)
.findAll();
Time constraints - while it is not clear in field of which type you store time in your Task class i'll provide an answer for a long field (and if it's not long, i suggest you to use it). Realm has a nice query picking capabilities, which we can make a use of:
RealmResults <Task> tasksInOneHourList =
realm.where(Task.class).between("TIME_LONG_FIELD_NAME", CURRENT_TIME, CURRENT_TIME + HOUR_IN_MILLIS).findAll();
Notify adapter - you can update adapter data as the following:
tasksInOneHourList.addChangeListener(new RealmChangeListener<RealmResults<Task>>() {
#Override
public void onChange(RealmResults<Task> tasks) {
adapter.notifyDataSetChanged();
}
});

Android Firebase negative ServerValue.TIMESTAMP for ordering

I use ServerValue.TIMESTAMP in Firebase for ordering in query. I set timestamp in my model class to dats for example 1494325636954.
public Attendance(String usico) {
this.usico = usico;
HashMap<String, Object> datsObj = new HashMap<String, Object>();
datsObj.put("date", ServerValue.TIMESTAMP);
this.dats = datsObj;
}
How can I set to datt negative ServerValue.TIMESTAMP for example -1494325636954 ?
You can create a Firebase Function to update the value to be negative, while maintaining the server accurate timestamp.
exports.makeNegativeTimestamp = functions.database.ref('/posts/{postID}/date').onWrite(event => {
// get the timestamp from the DeltaSnapshot
const timestamp = event.data.val();
// ensure that the timestamp is a number
if (isNaN(timestamp)) { return; }
// only make negative if it's currently positive
if (timestamp >= 0) {
// returns a promise
return event.data.adminRef.set(timestamp * -1);
}
});
Use timeStamp in other cases but when it comes to firebase you don't need to go all the to convert timeStamp to negative for just sorting, my way is put decrementing long or int whenever child is added (inside child's property) then use that as a sorting method, this will simplify lot of time if you care only about order .

Categories

Resources