Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: [ANDROAPP-5536] Search uses paging3 #3469

Merged
merged 6 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.paging.PagedList;
import androidx.paging.PagingData;

import org.dhis2.commons.data.EventViewModel;
import org.dhis2.commons.data.SearchTeiModel;
Expand All @@ -15,6 +16,8 @@
import org.hisp.dhis.android.core.program.Program;
import org.hisp.dhis.android.core.settings.AnalyticsDhisVisualizationsGroup;
import org.hisp.dhis.android.core.trackedentity.TrackedEntityType;
import org.hisp.dhis.android.core.trackedentity.search.TrackedEntitySearchCollectionRepository;
import org.hisp.dhis.android.core.trackedentity.search.TrackedEntitySearchItem;
import org.jetbrains.annotations.NotNull;

import java.util.Date;
Expand All @@ -29,9 +32,6 @@ public interface SearchRepository {

Observable<List<Program>> programsWithRegistration(String programTypeId);

@NonNull
LiveData<PagedList<SearchTeiModel>> searchTrackedEntities(SearchParametersModel searchParametersModel, boolean isOnline);

void clearFetchedList();

@NonNull
Expand All @@ -58,6 +58,10 @@ public interface SearchRepository {

TeiDownloadResult download(String teiUid, @Nullable String enrollmentUid, String reason);

SearchTeiModel transform(TrackedEntitySearchItem searchItem, @Nullable Program selectedProgram, boolean offlineOnly, SortingItem sortingItem);

TrackedEntitySearchCollectionRepository getFilteredRepository(SearchParametersModel searchParametersModel);

void setCurrentProgram(@Nullable String currentProgram);
boolean programStagesHaveCoordinates(String programUid);
boolean teTypeAttributesHaveCoordinates(String typeId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Transformations;
import androidx.paging.DataSource;
import androidx.paging.LivePagedListBuilder;
import androidx.paging.PagedList;
import androidx.paging.Pager;
import androidx.paging.PagingData;
import androidx.paging.PagingDataTransforms;
import androidx.paging.PagingLiveData;

import org.dhis2.R;
import org.dhis2.bindings.ExtensionsKt;
Expand Down Expand Up @@ -97,6 +102,11 @@
import io.reactivex.Flowable;
import io.reactivex.Observable;
import io.reactivex.Single;
import kotlin.Unit;
import kotlin.coroutines.Continuation;
import kotlinx.coroutines.ExecutorsKt;
import kotlinx.coroutines.flow.Flow;
import kotlinx.coroutines.flow.FlowCollector;

public class SearchRepositoryImpl implements SearchRepository {

Expand All @@ -105,7 +115,7 @@ public class SearchRepositoryImpl implements SearchRepository {
private final D2 d2;
private final SearchSortingValueSetter sortingValueSetter;
private TrackedEntitySearchCollectionRepository trackedEntityInstanceQuery;
private SearchParametersModel savedSearchParameters;
public SearchParametersModel savedSearchParameters;
private FilterManager savedFilters;
private FilterPresenter filterPresenter;
private DhisPeriodUtils periodUtils;
Expand Down Expand Up @@ -168,40 +178,6 @@ public void clearFetchedList() {
fetchedTeiUids.clear();
}

@NonNull
@Override
public LiveData<PagedList<SearchTeiModel>> searchTrackedEntities(SearchParametersModel searchParametersModel, boolean isOnline) {
boolean allowCache = false;
if (!searchParametersModel.equals(savedSearchParameters) || !FilterManager.getInstance().sameFilters(savedFilters)) {
trackedEntityInstanceQuery = getFilteredRepository(searchParametersModel);
} else {
getFilteredRepository(searchParametersModel);
allowCache = true;
}

if (!fetchedTeiUids.isEmpty() && searchParametersModel.getSelectedProgram() == null) {
trackedEntityInstanceQuery = trackedEntityInstanceQuery.excludeUids().in(new ArrayList<>(fetchedTeiUids));
}

DataSource<TrackedEntitySearchItem, SearchTeiModel> dataSource;

if (isOnline && FilterManager.getInstance().getStateFilters().isEmpty()) {
dataSource = trackedEntityInstanceQuery.allowOnlineCache().eq(allowCache).offlineFirst().getResultDataSource()
.map(result -> transformResult(result, searchParametersModel.getSelectedProgram(), false, FilterManager.getInstance().getSortingItem()));
} else {
dataSource = trackedEntityInstanceQuery.allowOnlineCache().eq(allowCache).offlineOnly().getResultDataSource()
.map(result -> transformResult(result, searchParametersModel.getSelectedProgram(), true, FilterManager.getInstance().getSortingItem()));
}

return new LivePagedListBuilder<>(new DataSource.Factory<TrackedEntitySearchItem, SearchTeiModel>() {
@NonNull
@Override
public DataSource<TrackedEntitySearchItem, SearchTeiModel> create() {
return dataSource;
}
}, 10).build();
}

@NonNull
@Override
public Flowable<List<SearchTeiModel>> searchTeiForMap(SearchParametersModel searchParametersModel, boolean isOnline) {
Expand All @@ -225,7 +201,8 @@ public Flowable<List<SearchTeiModel>> searchTeiForMap(SearchParametersModel sear
.toList().toFlowable();
}

private TrackedEntitySearchCollectionRepository getFilteredRepository(SearchParametersModel searchParametersModel) {
@Override
public TrackedEntitySearchCollectionRepository getFilteredRepository(SearchParametersModel searchParametersModel) {
this.savedSearchParameters = searchParametersModel.copy();
this.savedFilters = FilterManager.getInstance().copy();

Expand All @@ -247,12 +224,12 @@ private TrackedEntitySearchCollectionRepository getFilteredRepository(SearchPara

boolean isUnique = d2.trackedEntityModule().trackedEntityAttributes().uid(dataId).blockingGet().unique();
if (isUnique) {
trackedEntityInstanceQuery = trackedEntityInstanceQuery.byAttribute(dataId).eq(dataValue);
trackedEntityInstanceQuery = trackedEntityInstanceQuery.byFilter(dataId).eq(dataValue);
} else if (dataValue.contains("_os_")) {
dataValue = dataValue.split("_os_")[1];
trackedEntityInstanceQuery = trackedEntityInstanceQuery.byAttribute(dataId).eq(dataValue);
trackedEntityInstanceQuery = trackedEntityInstanceQuery.byFilter(dataId).eq(dataValue);
} else
trackedEntityInstanceQuery = trackedEntityInstanceQuery.byAttribute(dataId).like(dataValue);
trackedEntityInstanceQuery = trackedEntityInstanceQuery.byFilter(dataId).like(dataValue);
}
}

Expand Down Expand Up @@ -725,7 +702,7 @@ public TeiDownloadResult download(String teiUid, @Nullable String enrollmentUid,
return teiDownloader.download(teiUid, enrollmentUid, reason);
}

private SearchTeiModel transformResult(Result<TrackedEntitySearchItem, D2Error> result, @Nullable Program selectedProgram, boolean offlineOnly, SortingItem sortingItem) {
public SearchTeiModel transformResult(Result<TrackedEntitySearchItem, D2Error> result, @Nullable Program selectedProgram, boolean offlineOnly, SortingItem sortingItem) {
try {
return transform(result.getOrThrow(), selectedProgram, offlineOnly, sortingItem);
} catch (Exception e) {
Expand All @@ -736,7 +713,8 @@ private SearchTeiModel transformResult(Result<TrackedEntitySearchItem, D2Error>
}
}

private SearchTeiModel transform(TrackedEntitySearchItem searchItem, @Nullable Program selectedProgram, boolean offlineOnly, SortingItem sortingItem) {
@Override
public SearchTeiModel transform(TrackedEntitySearchItem searchItem, @Nullable Program selectedProgram, boolean offlineOnly, SortingItem sortingItem) {
if (!fetchedTeiUids.contains(searchItem.uid())) {
fetchedTeiUids.add(searchItem.uid());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.dhis2.usescases.searchTrackEntity

import androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow
import org.dhis2.commons.filters.FilterManager
import org.dhis2.data.search.SearchParametersModel
import org.hisp.dhis.android.core.trackedentity.search.TrackedEntitySearchCollectionRepository
import org.hisp.dhis.android.core.trackedentity.search.TrackedEntitySearchItem

class SearchRepositoryImplKt(
private val searchRepositoryJava: SearchRepository,
) : SearchRepositoryKt {

private lateinit var savedSearchParamenters: SearchParametersModel

private lateinit var savedFilters: FilterManager

private lateinit var trackedEntityInstanceQuery: TrackedEntitySearchCollectionRepository

private val fetchedTeiUids = HashSet<String>()

override fun searchTrackedEntities(
searchParametersModel: SearchParametersModel,
isOnline: Boolean,
): Flow<PagingData<TrackedEntitySearchItem>> {
var allowCache = false
savedSearchParamenters = searchParametersModel.copy()
savedFilters = FilterManager.getInstance().copy()

if (searchParametersModel != savedSearchParamenters || !FilterManager.getInstance().sameFilters(savedFilters)) {
trackedEntityInstanceQuery = searchRepositoryJava.getFilteredRepository(searchParametersModel)
} else {
trackedEntityInstanceQuery = searchRepositoryJava.getFilteredRepository(searchParametersModel)
allowCache = true
}

if (fetchedTeiUids.isNotEmpty() && searchParametersModel.selectedProgram == null) {
trackedEntityInstanceQuery =
trackedEntityInstanceQuery.excludeUids().`in`(fetchedTeiUids.toList())
}

val pagerFlow = if (isOnline && FilterManager.getInstance().stateFilters.isNotEmpty()) {
trackedEntityInstanceQuery.allowOnlineCache().eq(allowCache).offlineFirst().getPagingData(10)
} else {
trackedEntityInstanceQuery.allowOnlineCache().eq(allowCache).offlineOnly().getPagingData(10)
}

return pagerFlow
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.dhis2.usescases.searchTrackEntity

import androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow
import org.dhis2.data.search.SearchParametersModel
import org.hisp.dhis.android.core.trackedentity.search.TrackedEntitySearchItem

interface SearchRepositoryKt {

fun searchTrackedEntities(
searchParametersModel: SearchParametersModel,
isOnline: Boolean,
): Flow<PagingData<TrackedEntitySearchItem>>
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagedList
import androidx.paging.PagingData
import androidx.paging.cachedIn
import androidx.paging.map
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.dhis2.commons.data.SearchTeiModel
import org.dhis2.commons.filters.FilterManager
import org.dhis2.commons.idlingresource.SearchIdlingResourceSingleton
import org.dhis2.commons.network.NetworkUtils
import org.dhis2.commons.viewmodel.DispatcherProvider
Expand All @@ -29,6 +34,7 @@ class SearchTEIViewModel(
private val initialProgramUid: String?,
initialQuery: MutableMap<String, String>?,
private val searchRepository: SearchRepository,
private val searchRepositoryKt: SearchRepositoryKt,
private val searchNavPageConfigurator: SearchPageConfigurator,
private val mapDataRepository: MapDataRepository,
private val networkUtils: NetworkUtils,
Expand Down Expand Up @@ -226,46 +232,106 @@ class SearchTEIViewModel(
}
}

fun fetchListResults(onPagedListReady: (LiveData<PagedList<SearchTeiModel>>?) -> Unit) {
fun fetchListResults(onPagedListReady: (Flow<PagingData<SearchTeiModel>>?) -> Unit) {
viewModelScope.launch {
val resultPagedList = when {
searching -> loadSearchResults()
displayFrontPageList() -> loadDisplayInListResults()
searching -> loadSearchResults().cachedIn(viewModelScope)
displayFrontPageList() -> loadDisplayInListResults().cachedIn(viewModelScope)
else -> null
}
onPagedListReady(resultPagedList)
}
}

private suspend fun loadSearchResults() = withContext(dispatchers.io()) {
return@withContext searchRepository.searchTrackedEntities(
SearchParametersModel(
selectedProgram = searchRepository.getProgram(initialProgramUid),
queryData = queryData,
),
val searchParametersModel = SearchParametersModel(
selectedProgram = searchRepository.getProgram(initialProgramUid),
queryData = queryData,
)
val getPagingData = searchRepositoryKt.searchTrackedEntities(
searchParametersModel,
searching && networkUtils.isOnline(),
)

return@withContext getPagingData.map { pagingData ->
pagingData.map { item ->
if (
searching && networkUtils.isOnline() &&
FilterManager.getInstance().stateFilters.isEmpty()
) {
searchRepository.transform(
item,
searchParametersModel.selectedProgram,
false,
FilterManager.getInstance().sortingItem,
)
} else {
searchRepository.transform(
item,
searchParametersModel.selectedProgram,
true,
FilterManager.getInstance().sortingItem,
)
}
}
}
}

private suspend fun loadDisplayInListResults() = withContext(dispatchers.io()) {
return@withContext searchRepository.searchTrackedEntities(
SearchParametersModel(
selectedProgram = searchRepository.getProgram(initialProgramUid),
queryData = queryData,
),
val searchParametersModel = SearchParametersModel(
selectedProgram = searchRepository.getProgram(initialProgramUid),
queryData = queryData,
)
val getPagingData = searchRepositoryKt.searchTrackedEntities(
searchParametersModel,
false,
)

return@withContext getPagingData.map { pagingData ->
pagingData.map { item ->
searchRepository.transform(
item,
searchParametersModel.selectedProgram,
true,
FilterManager.getInstance().sortingItem,
)
}
}
}

fun fetchGlobalResults(): LiveData<PagedList<SearchTeiModel>>? {
return if (searching) {
searchRepository.searchTrackedEntities(
SearchParametersModel(
selectedProgram = null,
queryData = queryData,
),
searching && networkUtils.isOnline(),
)
suspend fun fetchGlobalResults() = withContext(dispatchers.io()) {
val searchParametersModel = SearchParametersModel(
selectedProgram = searchRepository.getProgram(initialProgramUid),
queryData = queryData,
)
val getPagingData = searchRepositoryKt.searchTrackedEntities(
searchParametersModel,
searching && networkUtils.isOnline(),
)

return@withContext if (searching) {
getPagingData.map { pagingData ->
pagingData.map { item ->
if (
searching && networkUtils.isOnline() &&
FilterManager.getInstance().stateFilters.isEmpty()
) {
searchRepository.transform(
item,
searchParametersModel.selectedProgram,
false,
FilterManager.getInstance().sortingItem,
)
} else {
searchRepository.transform(
item,
searchParametersModel.selectedProgram,
true,
FilterManager.getInstance().sortingItem,
)
}
}
}
} else {
null
}
Expand Down
Loading
Loading