Skip to content

Commit 57865b0

Browse files
ilgazermaskaravivek
authored andcommitted
Categories related client API's migrated to retrofit (commons-app#3053)
* 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
1 parent 781448d commit 57865b0

File tree

8 files changed

+264
-170
lines changed

8 files changed

+264
-170
lines changed

app/src/main/java/fr/free/nrw/commons/category/CategoriesModel.java

+6-3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public class CategoriesModel{
2323
private static final int SEARCH_CATS_LIMIT = 25;
2424

2525
private final MediaWikiApi mwApi;
26+
private final CategoryClient categoryClient;
2627
private final CategoryDao categoryDao;
2728
private final JsonKvStore directKvStore;
2829

@@ -32,9 +33,11 @@ public class CategoriesModel{
3233
@Inject GpsCategoryModel gpsCategoryModel;
3334
@Inject
3435
public CategoriesModel(MediaWikiApi mwApi,
36+
CategoryClient categoryClient,
3537
CategoryDao categoryDao,
3638
@Named("default_preferences") JsonKvStore directKvStore) {
3739
this.mwApi = mwApi;
40+
this.categoryClient = categoryClient;
3841
this.categoryDao = categoryDao;
3942
this.directKvStore = directKvStore;
4043
this.categoriesCache = new HashMap<>();
@@ -121,8 +124,8 @@ public Observable<CategoryItem> searchAll(String term, List<String> imageTitleLi
121124
}
122125

123126
//otherwise, search API for matching categories
124-
return mwApi
125-
.allCategories(term, SEARCH_CATS_LIMIT)
127+
return categoryClient
128+
.searchCategoriesForPrefix(term, SEARCH_CATS_LIMIT)
126129
.map(name -> new CategoryItem(name, false));
127130
}
128131

@@ -185,7 +188,7 @@ private Observable<CategoryItem> titleCategories(List<String> titleList) {
185188
* @return
186189
*/
187190
private Observable<CategoryItem> getTitleCategories(String title) {
188-
return mwApi.searchTitles(title, SEARCH_CATS_LIMIT)
191+
return categoryClient.searchCategories(title, SEARCH_CATS_LIMIT)
189192
.map(name -> new CategoryItem(name, false));
190193
}
191194

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package fr.free.nrw.commons.category;
2+
3+
4+
import org.wikipedia.dataclient.mwapi.MwQueryPage;
5+
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
6+
7+
import java.util.List;
8+
9+
import javax.inject.Inject;
10+
import javax.inject.Singleton;
11+
12+
import io.reactivex.Observable;
13+
import timber.log.Timber;
14+
15+
/**
16+
* Category Client to handle custom calls to Commons MediaWiki APIs
17+
*/
18+
@Singleton
19+
public class CategoryClient {
20+
21+
private final CategoryInterface CategoryInterface;
22+
23+
@Inject
24+
public CategoryClient(CategoryInterface CategoryInterface) {
25+
this.CategoryInterface = CategoryInterface;
26+
}
27+
28+
/**
29+
* Searches for categories containing the specified string.
30+
*
31+
* @param filter The string to be searched
32+
* @param itemLimit How many results are returned
33+
* @param offset Starts returning items from the nth result. If offset is 9, the response starts with the 9th item of the search result
34+
* @return
35+
*/
36+
public Observable<String> searchCategories(String filter, int itemLimit, int offset) {
37+
return responseToCategoryName(CategoryInterface.searchCategories(filter, itemLimit, offset));
38+
39+
}
40+
41+
/**
42+
* Searches for categories containing the specified string.
43+
*
44+
* @param filter The string to be searched
45+
* @param itemLimit How many results are returned
46+
* @return
47+
*/
48+
public Observable<String> searchCategories(String filter, int itemLimit) {
49+
return searchCategories(filter, itemLimit, 0);
50+
51+
}
52+
53+
/**
54+
* Searches for categories starting with the specified string.
55+
*
56+
* @param prefix The prefix to be searched
57+
* @param itemLimit How many results are returned
58+
* @param offset Starts returning items from the nth result. If offset is 9, the response starts with the 9th item of the search result
59+
* @return
60+
*/
61+
public Observable<String> searchCategoriesForPrefix(String prefix, int itemLimit, int offset) {
62+
return responseToCategoryName(CategoryInterface.searchCategoriesForPrefix(prefix, itemLimit, offset));
63+
}
64+
65+
/**
66+
* Searches for categories starting with the specified string.
67+
*
68+
* @param prefix The prefix to be searched
69+
* @param itemLimit How many results are returned
70+
* @return
71+
*/
72+
public Observable<String> searchCategoriesForPrefix(String prefix, int itemLimit) {
73+
return searchCategoriesForPrefix(prefix, itemLimit, 0);
74+
}
75+
76+
77+
/**
78+
* Internal function to reduce code reuse. Extracts the categories returned from MwQueryResponse.
79+
*
80+
* @param responseObservable The query response observable
81+
* @return Observable emitting the categories returned. If our search yielded "Category:Test", "Test" is emitted.
82+
*/
83+
private Observable<String> responseToCategoryName(Observable<MwQueryResponse> responseObservable) {
84+
return responseObservable
85+
.flatMap(mwQueryResponse -> {
86+
List<MwQueryPage> pages = mwQueryResponse.query().pages();
87+
if (pages != null)
88+
return Observable.fromIterable(pages);
89+
else
90+
Timber.d("No categories returned.");
91+
return Observable.empty();
92+
})
93+
.map(MwQueryPage::title)
94+
.doOnEach(s -> Timber.d("Category returned: %s", s))
95+
.map(cat -> cat.replace("Category:", ""));
96+
}
97+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package fr.free.nrw.commons.category;
2+
3+
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
4+
5+
import io.reactivex.Observable;
6+
import retrofit2.http.GET;
7+
import retrofit2.http.Query;
8+
9+
/**
10+
* Interface for interacting with Commons category related APIs
11+
*/
12+
public interface CategoryInterface {
13+
14+
/**
15+
* Searches for categories with the specified name.
16+
* Replaces ApacheHttpClientMediaWikiApi#allCategories
17+
*
18+
* @param filter The string to be searched
19+
* @param itemLimit How many results are returned
20+
* @return
21+
*/
22+
@GET("w/api.php?action=query&format=json&formatversion=2"
23+
+ "&generator=search&gsrnamespace=14")
24+
Observable<MwQueryResponse> searchCategories(@Query("gsrsearch") String filter,
25+
@Query("gsrlimit") int itemLimit, @Query("gsroffset") int offset);
26+
27+
@GET("w/api.php?action=query&format=json&formatversion=2"
28+
+ "&generator=allcategories")
29+
Observable<MwQueryResponse> searchCategoriesForPrefix(@Query("gacprefix") String prefix,
30+
@Query("gaclimit") int itemLimit, @Query("gacoffset") int offset);
31+
}

app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.java

+7
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import dagger.Module;
1919
import dagger.Provides;
2020
import fr.free.nrw.commons.BuildConfig;
21+
import fr.free.nrw.commons.category.CategoryInterface;
2122
import fr.free.nrw.commons.kvstore.JsonKvStore;
2223
import fr.free.nrw.commons.media.MediaInterface;
2324
import fr.free.nrw.commons.mwapi.ApacheHttpClientMediaWikiApi;
@@ -132,4 +133,10 @@ public ReviewInterface provideReviewInterface(@Named(NAMED_COMMONS_WIKI_SITE) Wi
132133
public MediaInterface provideMediaInterface(@Named(NAMED_COMMONS_WIKI_SITE) WikiSite commonsWikiSite) {
133134
return ServiceFactory.get(commonsWikiSite, BuildConfig.COMMONS_URL, MediaInterface.class);
134135
}
136+
137+
@Provides
138+
@Singleton
139+
public CategoryInterface provideCategoryInterface(@Named(NAMED_COMMONS_WIKI_SITE) WikiSite commonsWikiSite) {
140+
return ServiceFactory.get(commonsWikiSite, BuildConfig.COMMONS_URL, CategoryInterface.class);
141+
}
135142
}

app/src/main/java/fr/free/nrw/commons/explore/categories/SearchCategoryFragment.java

+13-7
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import butterknife.BindView;
2626
import butterknife.ButterKnife;
2727
import fr.free.nrw.commons.R;
28+
import fr.free.nrw.commons.category.CategoryClient;
2829
import fr.free.nrw.commons.category.CategoryDetailsActivity;
2930
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
3031
import fr.free.nrw.commons.explore.recentsearches.RecentSearch;
@@ -58,9 +59,12 @@ public class SearchCategoryFragment extends CommonsDaggerSupportFragment {
5859
String query;
5960
@BindView(R.id.bottomProgressBar)
6061
ProgressBar bottomProgressBar;
62+
boolean isLoadingCategories;
6163

6264
@Inject RecentSearchesDao recentSearchesDao;
6365
@Inject MediaWikiApi mwApi;
66+
@Inject CategoryClient categoryClient;
67+
6468
@Inject
6569
@Named("default_preferences")
6670
JsonKvStore basicKvStore;
@@ -135,48 +139,51 @@ public void updateCategoryList(String query) {
135139
progressBar.setVisibility(GONE);
136140
queryList.clear();
137141
categoriesAdapter.clear();
138-
compositeDisposable.add(Observable.fromCallable(() -> mwApi.searchCategory(query,queryList.size()))
142+
compositeDisposable.add(categoryClient.searchCategories(query,25)
139143
.subscribeOn(Schedulers.io())
140144
.observeOn(AndroidSchedulers.mainThread())
141145
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
142146
.doOnSubscribe(disposable -> saveQuery(query))
147+
.collect(ArrayList<String>::new, ArrayList::add)
143148
.subscribe(this::handleSuccess, this::handleError));
144149
}
145150

146151

147152
/**
148-
* Adds more results to existing search results
153+
* Adds 25 more results to existing search results
149154
*/
150155
public void addCategoriesToList(String query) {
156+
if(isLoadingCategories) return;
157+
isLoadingCategories=true;
151158
this.query = query;
152159
bottomProgressBar.setVisibility(View.VISIBLE);
153160
progressBar.setVisibility(GONE);
154-
compositeDisposable.add(Observable.fromCallable(() -> mwApi.searchCategory(query,queryList.size()))
161+
compositeDisposable.add(categoryClient.searchCategories(query,25, queryList.size())
155162
.subscribeOn(Schedulers.io())
156163
.observeOn(AndroidSchedulers.mainThread())
157164
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
165+
.collect(ArrayList<String>::new, ArrayList::add)
158166
.subscribe(this::handlePaginationSuccess, this::handleError));
159167
}
160168

161169
/**
162170
* Handles the success scenario
163171
* it initializes the recycler view by adding items to the adapter
164-
* @param mediaList
165172
*/
166173
private void handlePaginationSuccess(List<String> mediaList) {
167174
queryList.addAll(mediaList);
168175
progressBar.setVisibility(View.GONE);
169176
bottomProgressBar.setVisibility(GONE);
170177
categoriesAdapter.addAll(mediaList);
171178
categoriesAdapter.notifyDataSetChanged();
179+
isLoadingCategories=false;
172180
}
173181

174182

175183

176184
/**
177185
* Handles the success scenario
178186
* it initializes the recycler view by adding items to the adapter
179-
* @param mediaList
180187
*/
181188
private void handleSuccess(List<String> mediaList) {
182189
queryList = mediaList;
@@ -194,7 +201,6 @@ private void handleSuccess(List<String> mediaList) {
194201

195202
/**
196203
* Logs and handles API error scenario
197-
* @param throwable
198204
*/
199205
private void handleError(Throwable throwable) {
200206
Timber.e(throwable, "Error occurred while loading queried categories");
@@ -213,7 +219,7 @@ private void handleError(Throwable throwable) {
213219
private void initErrorView() {
214220
progressBar.setVisibility(GONE);
215221
categoriesNotFoundView.setVisibility(VISIBLE);
216-
categoriesNotFoundView.setText(getString(R.string.categories_not_found, query));
222+
categoriesNotFoundView.setText(getString(R.string.categories_not_found));
217223
}
218224

219225
/**

0 commit comments

Comments
 (0)