Skip to content

Commit

Permalink
Categories related client API's migrated to retrofit (commons-app#3053)
Browse files Browse the repository at this point in the history
* Commit 1

* searchCategories migrated to retrofit

* SearchCategoriesFragment migrated to new API

* Removed unused code

* Created tests

* implemented searching by prefix
fixed SearchCategoryFragment behaviour where the same categories would be added to the list instead of new ones.

* added tests

* Migrated searchTitles to searchCategories, function behaviour seems identical
  • Loading branch information
ilgazer authored and maskaravivek committed Jul 10, 2019
1 parent 78141cb commit d5198be
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 170 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class CategoriesModel{
private static final int SEARCH_CATS_LIMIT = 25;

private final MediaWikiApi mwApi;
private final CategoryClient categoryClient;
private final CategoryDao categoryDao;
private final JsonKvStore directKvStore;

Expand All @@ -32,9 +33,11 @@ public class CategoriesModel{
@Inject GpsCategoryModel gpsCategoryModel;
@Inject
public CategoriesModel(MediaWikiApi mwApi,
CategoryClient categoryClient,
CategoryDao categoryDao,
@Named("default_preferences") JsonKvStore directKvStore) {
this.mwApi = mwApi;
this.categoryClient = categoryClient;
this.categoryDao = categoryDao;
this.directKvStore = directKvStore;
this.categoriesCache = new HashMap<>();
Expand Down Expand Up @@ -121,8 +124,8 @@ public Observable<CategoryItem> searchAll(String term, List<String> imageTitleLi
}

//otherwise, search API for matching categories
return mwApi
.allCategories(term, SEARCH_CATS_LIMIT)
return categoryClient
.searchCategoriesForPrefix(term, SEARCH_CATS_LIMIT)
.map(name -> new CategoryItem(name, false));
}

Expand Down Expand Up @@ -185,7 +188,7 @@ private Observable<CategoryItem> titleCategories(List<String> titleList) {
* @return
*/
private Observable<CategoryItem> getTitleCategories(String title) {
return mwApi.searchTitles(title, SEARCH_CATS_LIMIT)
return categoryClient.searchCategories(title, SEARCH_CATS_LIMIT)
.map(name -> new CategoryItem(name, false));
}

Expand Down
97 changes: 97 additions & 0 deletions app/src/main/java/fr/free/nrw/commons/category/CategoryClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package fr.free.nrw.commons.category;


import org.wikipedia.dataclient.mwapi.MwQueryPage;
import org.wikipedia.dataclient.mwapi.MwQueryResponse;

import java.util.List;

import javax.inject.Inject;
import javax.inject.Singleton;

import io.reactivex.Observable;
import timber.log.Timber;

/**
* Category Client to handle custom calls to Commons MediaWiki APIs
*/
@Singleton
public class CategoryClient {

private final CategoryInterface CategoryInterface;

@Inject
public CategoryClient(CategoryInterface CategoryInterface) {
this.CategoryInterface = CategoryInterface;
}

/**
* Searches for categories containing the specified string.
*
* @param filter The string to be searched
* @param itemLimit How many results are returned
* @param offset Starts returning items from the nth result. If offset is 9, the response starts with the 9th item of the search result
* @return
*/
public Observable<String> searchCategories(String filter, int itemLimit, int offset) {
return responseToCategoryName(CategoryInterface.searchCategories(filter, itemLimit, offset));

}

/**
* Searches for categories containing the specified string.
*
* @param filter The string to be searched
* @param itemLimit How many results are returned
* @return
*/
public Observable<String> searchCategories(String filter, int itemLimit) {
return searchCategories(filter, itemLimit, 0);

}

/**
* Searches for categories starting with the specified string.
*
* @param prefix The prefix to be searched
* @param itemLimit How many results are returned
* @param offset Starts returning items from the nth result. If offset is 9, the response starts with the 9th item of the search result
* @return
*/
public Observable<String> searchCategoriesForPrefix(String prefix, int itemLimit, int offset) {
return responseToCategoryName(CategoryInterface.searchCategoriesForPrefix(prefix, itemLimit, offset));
}

/**
* Searches for categories starting with the specified string.
*
* @param prefix The prefix to be searched
* @param itemLimit How many results are returned
* @return
*/
public Observable<String> searchCategoriesForPrefix(String prefix, int itemLimit) {
return searchCategoriesForPrefix(prefix, itemLimit, 0);
}


/**
* Internal function to reduce code reuse. Extracts the categories returned from MwQueryResponse.
*
* @param responseObservable The query response observable
* @return Observable emitting the categories returned. If our search yielded "Category:Test", "Test" is emitted.
*/
private Observable<String> responseToCategoryName(Observable<MwQueryResponse> responseObservable) {
return responseObservable
.flatMap(mwQueryResponse -> {
List<MwQueryPage> pages = mwQueryResponse.query().pages();
if (pages != null)
return Observable.fromIterable(pages);
else
Timber.d("No categories returned.");
return Observable.empty();
})
.map(MwQueryPage::title)
.doOnEach(s -> Timber.d("Category returned: %s", s))
.map(cat -> cat.replace("Category:", ""));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package fr.free.nrw.commons.category;

import org.wikipedia.dataclient.mwapi.MwQueryResponse;

import io.reactivex.Observable;
import retrofit2.http.GET;
import retrofit2.http.Query;

/**
* Interface for interacting with Commons category related APIs
*/
public interface CategoryInterface {

/**
* Searches for categories with the specified name.
* Replaces ApacheHttpClientMediaWikiApi#allCategories
*
* @param filter The string to be searched
* @param itemLimit How many results are returned
* @return
*/
@GET("w/api.php?action=query&format=json&formatversion=2"
+ "&generator=search&gsrnamespace=14")
Observable<MwQueryResponse> searchCategories(@Query("gsrsearch") String filter,
@Query("gsrlimit") int itemLimit, @Query("gsroffset") int offset);

@GET("w/api.php?action=query&format=json&formatversion=2"
+ "&generator=allcategories")
Observable<MwQueryResponse> searchCategoriesForPrefix(@Query("gacprefix") String prefix,
@Query("gaclimit") int itemLimit, @Query("gacoffset") int offset);
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import dagger.Module;
import dagger.Provides;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.category.CategoryInterface;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.media.MediaInterface;
import fr.free.nrw.commons.mwapi.ApacheHttpClientMediaWikiApi;
Expand Down Expand Up @@ -132,4 +133,10 @@ public ReviewInterface provideReviewInterface(@Named(NAMED_COMMONS_WIKI_SITE) Wi
public MediaInterface provideMediaInterface(@Named(NAMED_COMMONS_WIKI_SITE) WikiSite commonsWikiSite) {
return ServiceFactory.get(commonsWikiSite, BuildConfig.COMMONS_URL, MediaInterface.class);
}

@Provides
@Singleton
public CategoryInterface provideCategoryInterface(@Named(NAMED_COMMONS_WIKI_SITE) WikiSite commonsWikiSite) {
return ServiceFactory.get(commonsWikiSite, BuildConfig.COMMONS_URL, CategoryInterface.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import butterknife.BindView;
import butterknife.ButterKnife;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.category.CategoryClient;
import fr.free.nrw.commons.category.CategoryDetailsActivity;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.explore.recentsearches.RecentSearch;
Expand Down Expand Up @@ -58,9 +59,12 @@ public class SearchCategoryFragment extends CommonsDaggerSupportFragment {
String query;
@BindView(R.id.bottomProgressBar)
ProgressBar bottomProgressBar;
boolean isLoadingCategories;

@Inject RecentSearchesDao recentSearchesDao;
@Inject MediaWikiApi mwApi;
@Inject CategoryClient categoryClient;

@Inject
@Named("default_preferences")
JsonKvStore basicKvStore;
Expand Down Expand Up @@ -135,48 +139,51 @@ public void updateCategoryList(String query) {
progressBar.setVisibility(GONE);
queryList.clear();
categoriesAdapter.clear();
compositeDisposable.add(Observable.fromCallable(() -> mwApi.searchCategory(query,queryList.size()))
compositeDisposable.add(categoryClient.searchCategories(query,25)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
.doOnSubscribe(disposable -> saveQuery(query))
.collect(ArrayList<String>::new, ArrayList::add)
.subscribe(this::handleSuccess, this::handleError));
}


/**
* Adds more results to existing search results
* Adds 25 more results to existing search results
*/
public void addCategoriesToList(String query) {
if(isLoadingCategories) return;
isLoadingCategories=true;
this.query = query;
bottomProgressBar.setVisibility(View.VISIBLE);
progressBar.setVisibility(GONE);
compositeDisposable.add(Observable.fromCallable(() -> mwApi.searchCategory(query,queryList.size()))
compositeDisposable.add(categoryClient.searchCategories(query,25, queryList.size())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
.collect(ArrayList<String>::new, ArrayList::add)
.subscribe(this::handlePaginationSuccess, this::handleError));
}

/**
* Handles the success scenario
* it initializes the recycler view by adding items to the adapter
* @param mediaList
*/
private void handlePaginationSuccess(List<String> mediaList) {
queryList.addAll(mediaList);
progressBar.setVisibility(View.GONE);
bottomProgressBar.setVisibility(GONE);
categoriesAdapter.addAll(mediaList);
categoriesAdapter.notifyDataSetChanged();
isLoadingCategories=false;
}



/**
* Handles the success scenario
* it initializes the recycler view by adding items to the adapter
* @param mediaList
*/
private void handleSuccess(List<String> mediaList) {
queryList = mediaList;
Expand All @@ -194,7 +201,6 @@ private void handleSuccess(List<String> mediaList) {

/**
* Logs and handles API error scenario
* @param throwable
*/
private void handleError(Throwable throwable) {
Timber.e(throwable, "Error occurred while loading queried categories");
Expand All @@ -213,7 +219,7 @@ private void handleError(Throwable throwable) {
private void initErrorView() {
progressBar.setVisibility(GONE);
categoriesNotFoundView.setVisibility(VISIBLE);
categoriesNotFoundView.setText(getString(R.string.categories_not_found, query));
categoriesNotFoundView.setText(getString(R.string.categories_not_found));
}

/**
Expand Down
Loading

0 comments on commit d5198be

Please sign in to comment.