Search code examples
androidandroid-roomrx-java2

Android Room with RxJava: get data from Room with the loop


I am new for using RxJava and Room. What I trying to do is run a for loop to get data from database. The for loop iterate from first day of month to the last day of month.
Here is the Dao for this query.

@Query("SELECT SUM(duration) FROM xxx WHERE timeStamp >= :start and timeStamp <= :end and userId = :userId")
    Flowable<Integer> getDuration(String userId, long start, long end);

And Here is how i using RxJava to get the result.

Calendar day1 = Calendar.getInstance();
Calendar day2 = Calendar.getInstance();
int maxLoopIndex = day1.getActualMaximum(Calendar.DAY_OF_MONTH);
day1.setFirstDayOfWeek(Calendar.MONDAY);
day2.setFirstDayOfWeek(Calendar.MONDAY);
day1.set(Calendar.DATE, day1.getActualMinimum(Calendar.DATE));
day2.set(Calendar.DATE, day2.getActualMinimum(Calendar.DATE));
day1.set(Calendar.HOUR_OF_DAY, 0);
day1.set(Calendar.MINUTE, 0);
day1.set(Calendar.SECOND, 0);
day1.set(Calendar.MILLISECOND, 0);
day2.set(Calendar.HOUR_OF_DAY, 23);
day2.set(Calendar.MINUTE, 59);
day2.set(Calendar.SECOND, 59);
day2.set(Calendar.MILLISECOND, 999);
ArrayList<Pair<Long, Long>> maxDayCount = new ArrayList<>();

//Get all the timeStamp in a month, where maxDayCount can be 30, 31, 28, 29. 
for (int i = 0; i < maxDayCount; i++) {
         Pair<Long, Long> P = Pair.create(day1.getTimeInMillis(), day2.getTimeInMillis());
         pairArrayList.add(P);
         day1.add(Calendar.DATE, 1);
         day2.add(Calendar.DATE, 1);
}

// Using Flowable.formIterable to run through the list and get the data from room
Flowable.fromIterable(pairArrayList)
                    .flatMap(new Function<Pair<Long, Long>, Flowable<Integer>>() {
                        @Override
                        public Flowable<Integer> apply(@NonNull Pair<Long, Long> date) throws Exception {
                            return roomdb.Dao().getDuration(
                                    User.getCurUser().getId(), date.first, date.second
                            );
                        }
                    })
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new Consumer<Integer>() {
                        @Override
                        public void accept(@NonNull Integer source) throws Exception {
                            Log.d(TAG, "Duration: "+source);
                            // I want to get the index of pairArrayList to store the duration in 
                            // corresponding array
                        }
                    });

However in subscribe I can get the result return by room however I can not get which index is run in pairArrayList. Is there any way I can get the index? Furthermore is there any better way to get data from room with the loop?


Solution

  • Let's begin with the final structure. It should contain the day of month and duration:

    class DayDuration {
    
        public Integer day;
        public Long duration;
    
        public DayDuration(Integer day, Long duration) {
            this.day = day;
            this.duration = duration;
        }
    
        @Override
        public boolean equals(Object o) { /* implementation */ }
    
        @Override
        public int hashCode() { /* implementation */ }
    }
    

    Creation of final Flowable what emits requested items might look like the following code. I have used ThreetenBP library to handle date/time operations because Android Calendar API is pure hell. Recommend you do the same:

    class SO64870062 {
    
        private Flowable<Long> getDuration(String userId, long start, long end) {
            return Flowable.fromCallable(() -> start); // mock data
        }
    
        @NotNull
        private Flowable<LocalDate> getDaysInMonth(YearMonth yearMonth) { // (1)
            LocalDate start = LocalDate.of(yearMonth.getYear(), yearMonth.getMonthValue(), 1);
            LocalDate end = start.with(TemporalAdjusters.lastDayOfMonth());
    
            return Flowable.create(emitter -> {
                LocalDate current = start;
    
                while (!current.isAfter(end)) { // (2)
                    emitter.onNext(current);
                    current = current.plusDays(1);
                }
    
                emitter.onComplete();
            }, BackpressureStrategy.BUFFER);
        }
    
        @NotNull
        private Flowable<DayDuration> getDurationForDay(String userId, LocalDate localDate) {
            long startDayMillis = localDate.atStartOfDay().atZone(ZoneOffset.UTC) // (3)
                    .toInstant()
                    .toEpochMilli();
    
            long endDayMillis = localDate.atTime(LocalTime.MAX).atZone(ZoneOffset.UTC)
                    .toInstant()
                    .toEpochMilli();
    
            return getDuration(userId, startDayMillis, endDayMillis) // (4)
                    .map(duration -> new DayDuration(localDate.getDayOfMonth(), duration));
        }
    
        public Flowable<DayDuration> getDayDurations(String userId, YearMonth yearMonth) {
            return getDaysInMonth(yearMonth)
                    .flatMap(localDate -> getDurationForDay(userId, localDate));
        }
    }
    

    Important and interesting parts:

    1. Function getDaysInMonth() creates Flowable what emits all days of requested month.
    2. Iteration from start (first day of a month) to end (last day of a month) date and emitting all of the days.
    3. Make sure you set the zone you use within timestamps in your database. I have used UTC for simplicity.
    4. Combine duration from a database with the current date.

    Last but not least, let's check if it works correctly:

    public class SO64870062Test {
    
        @Test
        public void whenDaysRequestedForApril2020ThenEmitted() {
            SO64870062 tested = new SO64870062();
    
            TestSubscriber<DayDuration> testSubscriber = tested
                    .getDayDurations("userId", YearMonth.of(2020, 11))
                    .test();
    
            testSubscriber.assertValueCount(30);
            testSubscriber.assertValueAt(1, new DayDuration(2, 1604275200000L));
            testSubscriber.assertComplete();
        }
    }