Android 使用 RxJava 的 Room:使用循环从 Room 获取数据

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

我是 RxJava 和 Room 的新手。我想做的是 运行 从数据库中获取数据的 for 循环。 for 循环从每月的第一天迭代到每月的最后一天。
这是此查询的 Dao

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

下面是我如何使用 RxJava 获得结果。

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
                        }
                    });

但是在订阅中我可以通过房间得到结果 return 但是我无法在 pairArrayList 中得到哪个索引是 运行。有什么办法可以获得索引吗?此外,有没有更好的方法可以通过循环从房间获取数据?

让我们从最终结构开始。它应该包含月份和持续时间:

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 */ }
}

创建最终 Flowable 发出请求项的内容可能类似于以下代码。我使用 ThreetenBP 库来处理 date/time 操作,因为 Android Calendar API 简直就是地狱。建议您也这样做:

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));
    }
}

重要和有趣的部分:

  1. 函数 getDaysInMonth() 创建 Flowable 发出所请求月份的所有天数的内容。
  2. 从开始(一个月的第一天)到结束(一个月的最后一天)日期并发出所有日期的迭代。
  3. 确保在数据库中的时间戳内设置您使用的区域。为了简单起见,我使用了 UTC
  4. 将数据库中的持续时间与当前日期相结合。

最后但同样重要的是,让我们检查它是否正常工作:

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();
    }
}