From cfd48cb99778ea848ac31d01e4044248daaca847 Mon Sep 17 00:00:00 2001 From: Ilia Kurtov Date: Thu, 12 May 2016 21:20:35 +0300 Subject: [PATCH] abstract provider --- .../listing/AbstractSimplePagingProvider.java | 220 ++++++++++++++++++ .../listing/SimplePagingProvider.java | 179 +------------- 2 files changed, 224 insertions(+), 175 deletions(-) create mode 100644 src/main/java/ru/touchin/roboswag/components/listing/AbstractSimplePagingProvider.java diff --git a/src/main/java/ru/touchin/roboswag/components/listing/AbstractSimplePagingProvider.java b/src/main/java/ru/touchin/roboswag/components/listing/AbstractSimplePagingProvider.java new file mode 100644 index 0000000..4b9f1c5 --- /dev/null +++ b/src/main/java/ru/touchin/roboswag/components/listing/AbstractSimplePagingProvider.java @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) + * + * This file is part of RoboSwag library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package ru.touchin.roboswag.components.listing; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.SparseArray; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import ru.touchin.roboswag.core.log.Lc; +import ru.touchin.roboswag.core.utils.android.RxAndroidUtils; +import rx.Observable; +import rx.Scheduler; + +/** + * Created by Ilia Kurtov on 12.05.2016. + * TODO: fill description + */ +public abstract class AbstractSimplePagingProvider extends ItemsProvider { + + public static final int DEFAULT_PAGE_SIZE = 25; + + protected final Scheduler scheduler = RxAndroidUtils.createLooperScheduler(); + private final Object lock = new Object(); + private final int pageSize; + private final SparseArray> loadedPages = new SparseArray<>(); + private final SparseArray>> loadingPages = new SparseArray<>(); + @Nullable + private Integer maxLoadedPage; + private boolean isLastPageLoaded; + + public AbstractSimplePagingProvider(final int pageSize) { + super(); + this.pageSize = pageSize; + } + + public int getPageSize() { + return pageSize; + } + + @Override + public int getSize() { + synchronized (lock) { + return (maxLoadedPage != null ? maxLoadedPage * pageSize + loadedPages.get(maxLoadedPage).size() : 0) + + (isLastPageLoaded ? 0 : 1); + } + } + + @NonNull + public List getLoadedItems() { + final List result = new ArrayList<>(); + if (maxLoadedPage != null) { + for (int i = 0; i <= maxLoadedPage; i++) { + final List page = loadedPages.get(i); + if (page != null) { + result.addAll(page); + } + } + } + return result; + } + + private int pageIndexOf(final int position) { + return position / pageSize; + } + + private int indexOnPage(final int position) { + return position % pageSize; + } + + @Nullable + @Override + public T getItem(final int position) { + synchronized (lock) { + final List page = loadedPages.get(pageIndexOf(position)); + final int indexOnPage = indexOnPage(position); + return page != null && page.size() > indexOnPage ? page.get(indexOnPage) : null; + } + } + + @Override + @NonNull + public Observable loadItem(final int position) { + final int indexOfPage = pageIndexOf(position); + final int indexOnPage = indexOnPage(position); + return Observable.create(subscriber -> { + final List page = loadedPages.get(indexOfPage); + subscriber.onNext(page != null && page.size() > indexOnPage ? page.get(indexOnPage) : null); + subscriber.onCompleted(); + }).switchMap(item -> { + if (item != null || (isLastPageLoaded && maxLoadedPage != null && maxLoadedPage <= indexOfPage)) { + return Observable.just(item); + } + Observable> loadingPage = loadingPages.get(indexOfPage); + if (loadingPage == null) { + loadingPage = createPageLoadingObservable(indexOfPage); + loadingPages.put(indexOfPage, loadingPage); + } + return loadingPage.map(page -> { + int index = 0; + for (final T nextItem : page) { + if (index == indexOnPage) { + return nextItem; + } + index++; + } + return null; + }); + }).subscribeOn(scheduler); + } + + private boolean shouldReplaceMaxLoaded(@NonNull final List pageItems, final int indexOfPage) { + return (indexOfPage == 0 || !pageItems.isEmpty()) && (maxLoadedPage == null || maxLoadedPage < indexOfPage); + } + + @NonNull + private Observable> createPageLoadingObservable(final int indexOfPage) { + return observeItems(indexOfPage) + .observeOn(scheduler) + .map(this::validatePageItems) + .doOnNext(pageItems -> { + synchronized (lock) { + final int oldSize = getSize(); + final boolean oldIsLastPageLoaded = isLastPageLoaded; + if (pageItems.size() < pageSize) { + if (maxLoadedPage != null && maxLoadedPage > indexOfPage) { + maxLoadedPage = indexOfPage == 0 || !pageItems.isEmpty() ? indexOfPage : null; + downgradeMaxLoadedPages(indexOfPage); + isLastPageLoaded = false; + } + if (shouldReplaceMaxLoaded(pageItems, indexOfPage)) { + maxLoadedPage = indexOfPage; + } + isLastPageLoaded = isLastPageLoaded + || (maxLoadedPage != null + && (maxLoadedPage == indexOfPage || maxLoadedPage == indexOfPage - 1)); + } else if (shouldReplaceMaxLoaded(pageItems, indexOfPage)) { + maxLoadedPage = indexOfPage; + } + if (indexOfPage == 0 || !pageItems.isEmpty()) { + loadedPages.put(indexOfPage, pageItems); + } + loadingPages.remove(indexOfPage); + updateItemsChanges(indexOfPage, pageItems, oldSize, oldIsLastPageLoaded); + + } + }).replay(1) + .refCount(); + } + + protected abstract Observable> observeItems(final int indexOfPage); + + + private void downgradeMaxLoadedPages(final int maximum) { + for (int i = 0; i <= loadedPages.size(); i++) { + final int key = loadedPages.keyAt(i); + if (key > maximum || loadedPages.get(key).size() < pageSize) { + loadedPages.remove(key); + } else if (maxLoadedPage == null || maxLoadedPage < key) { + maxLoadedPage = key; + } + } + } + + private void updateItemsChanges(final int indexOfPage, @NonNull final List pageItems, final int oldSize, final boolean oldIsLastPageLoaded) { + final List changes = new ArrayList<>(); + final int size = getSize(); + + if (size == oldSize) { + if (!pageItems.isEmpty()) { + changes.add(new Change(Change.Type.CHANGED, indexOfPage * pageSize, pageItems.size())); + } + } else if (size > oldSize) { + if (!oldIsLastPageLoaded) { + changes.add(new Change(Change.Type.CHANGED, oldSize - 1, 1)); + } + changes.add(new Change(Change.Type.INSERTED, oldSize, size - oldSize)); + } else { + changes.add(new Change(Change.Type.REMOVED, size, oldSize - size)); + if (!isLastPageLoaded) { + changes.add(new Change(Change.Type.CHANGED, size - 1, 1)); + } + } + if (!changes.isEmpty()) { + notifyChanges(changes); + } + } + + @NonNull + private List validatePageItems(@NonNull final Collection pageItems) { + if (pageItems.size() > pageSize) { + Lc.assertion("Unexpectedly big pageItems: " + pageItems.size() + '/' + pageSize); + return Collections.unmodifiableList(new ArrayList<>(pageItems).subList(0, pageSize)); + } else { + return Collections.unmodifiableList(new ArrayList<>(pageItems)); + } + } + +} diff --git a/src/main/java/ru/touchin/roboswag/components/listing/SimplePagingProvider.java b/src/main/java/ru/touchin/roboswag/components/listing/SimplePagingProvider.java index 73d56eb..fc48314 100644 --- a/src/main/java/ru/touchin/roboswag/components/listing/SimplePagingProvider.java +++ b/src/main/java/ru/touchin/roboswag/components/listing/SimplePagingProvider.java @@ -20,35 +20,19 @@ package ru.touchin.roboswag.components.listing; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.util.SparseArray; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; -import ru.touchin.roboswag.core.log.Lc; -import ru.touchin.roboswag.core.utils.android.RxAndroidUtils; import rx.Observable; -import rx.Scheduler; /** * Created by Gavriil Sitnikov on 07/12/2015. * TODO: fill description */ -public class SimplePagingProvider extends ItemsProvider { +public class SimplePagingProvider extends AbstractSimplePagingProvider { - public static final int DEFAULT_PAGE_SIZE = 25; - - private final Scheduler scheduler = RxAndroidUtils.createLooperScheduler(); - private final Object lock = new Object(); - private final int pageSize; - private final SparseArray> loadedPages = new SparseArray<>(); - @Nullable - private Integer maxLoadedPage; - private boolean isLastPageLoaded; - private final SparseArray>> loadingPages = new SparseArray<>(); @NonNull private final PagesProvider pagesProvider; @@ -57,168 +41,13 @@ public class SimplePagingProvider extends ItemsProvider { } public SimplePagingProvider(@NonNull final PagesProvider pagesProvider, final int pageSize) { - super(); + super(pageSize); this.pagesProvider = pagesProvider; - this.pageSize = pageSize; - } - - public int getPageSize() { - return pageSize; } @Override - public int getSize() { - synchronized (lock) { - return (maxLoadedPage != null ? maxLoadedPage * pageSize + loadedPages.get(maxLoadedPage).size() : 0) - + (isLastPageLoaded ? 0 : 1); - } - } - - @NonNull - public List getLoadedItems() { - final List result = new ArrayList<>(); - if (maxLoadedPage != null) { - for (int i = 0; i <= maxLoadedPage; i++) { - final List page = loadedPages.get(i); - if (page != null) { - result.addAll(page); - } - } - } - return result; - } - - private int pageIndexOf(final int position) { - return position / pageSize; - } - - private int indexOnPage(final int position) { - return position % pageSize; - } - - @Nullable - @Override - public T getItem(final int position) { - synchronized (lock) { - final List page = loadedPages.get(pageIndexOf(position)); - final int indexOnPage = indexOnPage(position); - return page != null && page.size() > indexOnPage ? page.get(indexOnPage) : null; - } - } - - @Override - @NonNull - public Observable loadItem(final int position) { - final int indexOfPage = pageIndexOf(position); - final int indexOnPage = indexOnPage(position); - return Observable.create(subscriber -> { - final List page = loadedPages.get(indexOfPage); - subscriber.onNext(page != null && page.size() > indexOnPage ? page.get(indexOnPage) : null); - subscriber.onCompleted(); - }).switchMap(item -> { - if (item != null || (isLastPageLoaded && maxLoadedPage != null && maxLoadedPage <= indexOfPage)) { - return Observable.just(item); - } - Observable> loadingPage = loadingPages.get(indexOfPage); - if (loadingPage == null) { - loadingPage = createPageLoadingObservable(indexOfPage); - loadingPages.put(indexOfPage, loadingPage); - } - return loadingPage.map(page -> { - int index = 0; - for (final T nextItem : page) { - if (index == indexOnPage) { - return nextItem; - } - index++; - } - return null; - }); - }).subscribeOn(scheduler); - } - - private boolean shouldReplaceMaxLoaded(@NonNull final List pageItems, final int indexOfPage) { - return (indexOfPage == 0 || !pageItems.isEmpty()) && (maxLoadedPage == null || maxLoadedPage < indexOfPage); - } - - @NonNull - private Observable> createPageLoadingObservable(final int indexOfPage) { - return pagesProvider - .loadPage(indexOfPage * pageSize, pageSize) - .observeOn(scheduler) - .map(this::validatePageItems) - .doOnNext(pageItems -> { - synchronized (lock) { - final int oldSize = getSize(); - final boolean oldIsLastPageLoaded = isLastPageLoaded; - if (pageItems.size() < pageSize) { - if (maxLoadedPage != null && maxLoadedPage > indexOfPage) { - maxLoadedPage = indexOfPage == 0 || !pageItems.isEmpty() ? indexOfPage : null; - downgradeMaxLoadedPages(indexOfPage); - isLastPageLoaded = false; - } - if (shouldReplaceMaxLoaded(pageItems, indexOfPage)) { - maxLoadedPage = indexOfPage; - } - isLastPageLoaded = isLastPageLoaded - || (maxLoadedPage != null - && (maxLoadedPage == indexOfPage || maxLoadedPage == indexOfPage - 1)); - } else if (shouldReplaceMaxLoaded(pageItems, indexOfPage)) { - maxLoadedPage = indexOfPage; - } - if (indexOfPage == 0 || !pageItems.isEmpty()) { - loadedPages.put(indexOfPage, pageItems); - } - loadingPages.remove(indexOfPage); - updateItemsChanges(indexOfPage, pageItems, oldSize, oldIsLastPageLoaded); - } - }).replay(1) - .refCount(); - } - - private void downgradeMaxLoadedPages(final int maximum) { - for (int i = 0; i <= loadedPages.size(); i++) { - final int key = loadedPages.keyAt(i); - if (key > maximum || loadedPages.get(key).size() < pageSize) { - loadedPages.remove(key); - } else if (maxLoadedPage == null || maxLoadedPage < key) { - maxLoadedPage = key; - } - } - } - - private void updateItemsChanges(final int indexOfPage, @NonNull final List pageItems, final int oldSize, final boolean oldIsLastPageLoaded) { - final List changes = new ArrayList<>(); - final int size = getSize(); - - if (size == oldSize) { - if (!pageItems.isEmpty()) { - changes.add(new Change(Change.Type.CHANGED, indexOfPage * pageSize, pageItems.size())); - } - } else if (size > oldSize) { - if (!oldIsLastPageLoaded) { - changes.add(new Change(Change.Type.CHANGED, oldSize - 1, 1)); - } - changes.add(new Change(Change.Type.INSERTED, oldSize, size - oldSize)); - } else { - changes.add(new Change(Change.Type.REMOVED, size, oldSize - size)); - if (!isLastPageLoaded) { - changes.add(new Change(Change.Type.CHANGED, size - 1, 1)); - } - } - if (!changes.isEmpty()) { - notifyChanges(changes); - } - } - - @NonNull - private List validatePageItems(@NonNull final Collection pageItems) { - if (pageItems.size() > pageSize) { - Lc.assertion("Unexpectedly big pageItems: " + pageItems.size() + '/' + pageSize); - return Collections.unmodifiableList(new ArrayList<>(pageItems).subList(0, pageSize)); - } else { - return Collections.unmodifiableList(new ArrayList<>(pageItems)); - } + protected Observable> observeItems(final int indexOfPage) { + return pagesProvider.loadPage(indexOfPage * getPageSize(), getPageSize()).map(ArrayList::new); } public interface PagesProvider {