3

I have a RecyclerView that displays items with a title, text and last edit time. enter image description here

In the RecyclerView adapter's onBindViewHolder I use getRelativeTimeSpanString and compare the note object's time to current time:

@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
    Note note = getItem(position);
    holder.title.setText(note.getTitle());
    holder.text.setText(note.getText());

    String s = DateUtils.getRelativeTimeSpanString(note.getTime(), 
    System.currentTimeMillis(), 0L, DateUtils.FORMAT_ABBREV_ALL).toString();
    holder.timestamp.setText(s);
}

If I just added the item, it says "0 sec ago". How do I update that over time?

While writing this question, SO suggested this question: Updating the relative time in android , however I don't think updating the RecyclerView every X seconds is the the best idea:

  1. it drains battery
  2. it updates items that don't need updating (e.g., last update was 6 hours ago, but it's updating holder.timestamp every X seconds)

Is there a more efficient way to accomplish this?

Akres
  • 126
  • 2
  • 16
  • 2
    You only need to update this time whilst the UI is visible, so it shouldn't impact performance/battery too much. If checking for updates every minute is acceptable for your use case, then you can register to the "Time Tick" intent sent by the system every minute in `onResume()` if your Activity/Fragment, and unregister in `onPause()`, so you don't even need to create a new timer mechanism. – PPartisan Mar 31 '19 at 12:28
  • @PPartisan You're right, I only need to update when the UI is visible. Do I call notifyDataSetChanged on the adapter? Is there a more granular way than notifyDataSetChanged with respect to older items, which only need to be updated, say, every hour? – Akres Mar 31 '19 at 12:32
  • 1
    If you're using a `RecyclerView`, then yes - you can [`notifyItemChanged()`](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter#notifyitemchanged_1) – PPartisan Mar 31 '19 at 12:36
  • Added a `BroadcastReceiver` with the intent filter `Intent.ACTION_TIME_TICK` which fires every minute. I loop through all note objects and if difference between now and note's time is what I need, then I update the time. This is just what I wanted! You can post as an answer, thank you! – Akres Mar 31 '19 at 13:20

1 Answers1

7

Given you only need to update the UI when it is visible, you can reduce battery drain by only updating your UI when you know it's in the foreground using the onResume() and onPause() lifecycle callbacks in your Fragment/Acitvity. In addition, if minute granularity is good enough for your use case, you can use the ACTION_TIME_TICK system broadcast, which is fired every one minute, so you don't need to create your own timer needlessly.

First, register a BroadcastReceiver to listen for this broadcast in onResume(), and unregister in onPause():

@Override
public void onResume() {
    final IntentFilter filter = new IntentFilter(Intent.ACTION_TIME_TICK);
    getContext().registerReceiver(receiver, filter);
}

@Override
public void onPause() {
    getContext().unregisterReceiver(receiver);
}

Inside receiver, contain the logic for updating the relevant items of your RecyclerView (or delegate it to your Adapter, as a rough example:

final BroadcastReceiver receiver = new BroadcastReceiver() {
   @Override
   public void onReceive(Context context, Intent intent) {
      adapter.updateTimes();
   }
}

//Inside your Adapter...
void updateTimes() {
    for(int i = 0; i < notes.size(); i++) {
       final Note note = notes.get(i);
       //Pseudocode for determening whether or not an update it necessary
       //Actual implementation will depend on how you handle updates
       final String relative = getCurrentRelativeTimeString(); 
       if(!Objects.equals(relative, note.getRelativeTimeString())) {
           note.setRelativeTimeString(relative);
           notifyItemChanged(i);
       }
    }
}
PPartisan
  • 8,173
  • 4
  • 29
  • 48