I am using RecyclerView with GridLayoutManager. For each item in the grid, I need to call a REST api to retrieve data. Then, after get data asynchronously from remote,I use UIL to load/display image.
Everything seems to be fine. But i find onBindViewHolder is called too too too many times for specific items.
logcat log
Talk is cheap, let me show you the code:
#Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
EditCourseImageAttachmentHolder holder = (EditCourseImageAttachmentHolder) viewHolder;
holder.itemView.setLayoutParams(getLayoutParams(position));
holder.attachmentView.initData(dataSource.get(headerView == null ? position : position - 1), urlMap);
}
public static class EditCourseImageAttachmentHolder extends RecyclerView.ViewHolder {
public final AttachmentView attachmentView;
public EditCourseImageAttachmentHolder(AttachmentView itemView) {
super(itemView);
this.attachmentView = itemView;
}
}
-----------AttachmentView.Java-----------------
#Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
subscription = tryLoadLocalImage(data)
.subscribeOn(Schedulers.io())
.flatMap(new Func1<String, Observable<String>>() {
#Override
public Observable<String> call(String localImageUrl) {
if (StringUtils.isNotBlank(localImageUrl)) {
return Observable.just(localImageUrl);
}
if (urlMap.containsKey(data.getUid())) {
return Observable.just(urlMap.get(data.getUid()));
} else {
//Remote API call
String qiNiuUrl = getQiNiuUrl(data);
urlMap.put(data.getUid(), qiNiuUrl);
return Observable.just(qiNiuUrl);
}
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<String>() {
#Override
public void call(String localImageUrl) {
showImage(localImageUrl);
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
}
});
}
#Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (subscription != null && subscription.isUnsubscribed()) {
subscription.unsubscribe();
}
}
Root cause is I changed the LayoutParam of the ItemView....
It works well after i remove those code.
But, it's quite strange that only the item at specific position would re-bind.
if (subscription != null && !subscription.isUnsubscribed()) {
subscription.unsubscribe();
}
your code should be this...
Related
I am getting duplicate items from an API in my adapter but I want to remove those duplicates from my side means from my adapter and print it once any idea how? Thanks in advance.
got: duplicate rows in my cardview but through an API.
want: just want to print it once and remove those duplicates.
MyAdapter:
#Override
public void onBindViewHolder(#NonNull catView holder, int position) {
holder.id.setText(oilResponses.get(position).getId());
}
#Override
public int getItemCount() {
if (oilResponses.size() != 0) {
return oilResponses.size();
}
return 0;
}
MainActivity:
private void oilList() {
Api.getClient().do_oil(CAR_ID)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(new Observer<List<OilResponse>>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(#NotNull List<OilResponse> responses) {
oilAdapter = new OilAdapter(responses);
oilRecycler.setAdapter(oilAdapter);
}
#Override
public void onError(#NotNull Throwable e) {
}
#Override
public void onComplete() {
}
});
}
you can try to save them in a Set instead of a List, it is basically the same thing but in a set each object can be saved one time, so you can't have duplicates even if you'd like to.
I've got problem with my "RecyclerView" and "BottomNavigationView" when I've tried to refactor my app and use Dependency Injection. The problem is when i click on items in Navigation View, "RecyclerView" won't change items in list. I have a code status for my HTTP request and base on that request I'm getting items. I've checked API and worked fine, also in "LOGCAT" got 200 Code. here's the code for the parts. when activity open app will show data with 0 status code, then when we change status code with bottom, nothing gonna change and still I'm getting same list.
This is not a duplicate since I'm using dependency injection and MVP, and I've also added dependency injection tag because this is happening when i use dependency injection and i'm beginner.
Activity: I'm calling methods in onCreate for data and View Here's the data.
private void setupData() {
bottomNavigation.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
if (item.getItemId() == R.id.new_request) {
mPresenter.setupLoadOrders("0");
} else if (item.getItemId() == R.id.canceled_request) {
mPresenter.setupLoadOrders("-1");
} else if (item.getItemId() == R.id.submited_request) {
mPresenter.setupLoadOrders("1");
} else if (item.getItemId() == R.id.added_request) {
mPresenter.setupLoadOrders("2");
}
return true;
}
});
mPresenter.setupLoadOrders("0");
}
private void setupView() {
recyclerRequests.setLayoutManager(new LinearLayoutManager(this));
recyclerRequests.setHasFixedSize(true);
}
#Override
public void displayOrders(List<Requests> requests) {
requestAdapter.setData(requests);
recyclerRequests.setAdapter(requestAdapter);
}
And Presenter :
#Override
public void setupLoadOrders(String statusReq) {
compositeDisposable.add(atlasApiReference.getRequest(Common.currentUser.getPhone(), statusReq)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(new Consumer<List<Requests>>() {
#Override
public void accept(List<Requests> requests) throws Exception {
mView.displayOrders(requests);
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
Log.e("Log",throwable.getMessage());
}
}));
}
public void unSubscribe()
{
AtlasRxJavaUtil.unsubscribeClear(compositeDisposable);
}
And Adapter :
private List<Requests> requestsList;
#Inject
public RequestAdapter() {
requestsList = new ArrayList<>();
}
#NonNull
#Override
public RequestAdapterViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
return new RequestAdapterViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.request_detail_layout, parent, false));
}
#Override
public void onBindViewHolder(#NonNull RequestAdapterViewHolder holder, final int position) {
holder.request_number.setText(new StringBuilder(" ").append(requestsList.get(position).getId()));
holder.request_name.setText(new StringBuilder(" ").append(requestsList.get(position).getNameReq()));
}
public class RequestAdapterViewHolder extends RecyclerView.ViewHolder {
public PsTextView request_number,request_name;
CardView request_detail_layout;
public RequestAdapterViewHolder(final View itemView) {
super(itemView);
request_number = (PsTextView)itemView.findViewById(R.id.request_number);
request_name = (PsTextView)itemView.findViewById(R.id.request_name);
request_detail_layout = (CardView)itemView.findViewById(R.id.request_detail_layout);
request_detail_layout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Common.currentRequest = requestsList.get(getAdapterPosition());
itemView.getContext().startActivity(new Intent(itemView.getContext(), ShowDetail.class));
}
});
}
}
#Override
public int getItemCount() {
return requestsList.size();
}
public void setData(List<Requests> requestsList){
this.requestsList.addAll(requestsList);
notifyDataSetChanged();
}
Try removing the following line:
recyclerRequests.setHasFixedSize(true);
More info on this post.
Following this tutorial I've created two sources for fetching data. I expect that if there is no data locally I'll send network request. But all the time get list of null objects from local source (which is first in Observable.concat).
For local source using SQLite with SQLBrite wrapper and Retrofit for remote source:
#Override
public Observable<Item> get(String id) {
//creating sql query
return databaseHelper.createQuery(ItemEntry.TABLE_NAME, sqlQuery, id)
.mapToOneOrDefault(mapperFunction, null);
}
There is method in repository for concating observables:
#Override
public Observable<Item> get(String id) {
Observable<Item> local = localDataSource
.get(id)
.doOnNext(new Action1<Item>() {
#Override
public void call(final Item item) {
// put item to cache
}
});
Observable<Item> remote = remoteDataSource
.get(id)
.doOnNext(new Action1<Item>() {
#Override
public void call(final Item item) {
// save item to database and put to cache
}
});
return Observable.concat(local, remote).first();
}
For getting it with list of ids I'm using next method:
#Override
public Observable<List<Item>> getList(final List<String> ids) {
return Observable
.from(ids)
.flatMap(new Func1<String, Observable<Item>>() {
#Override
public Observable<Item> call(String id) {
return get(id);
}
}).toList();
}
And subscription in fragment:
Subscription subscription = repository
.getList(ids)
.flatMap(new Func1<List<Item>, Observable<Item>>() {
#Override
public Observable<Item> call(List<Item> result) {
return Observable.from(result);
}
})
.toList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<Item>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(List<Item> result) {
// there get list of null objects
}
});
So, main goal is first check local storage and if there is no item - make request to server. But now if item isn't exist I get null instead of send request.
Could someone help me understand why?
Calling first() after concat will of course return the first item, regardless if it's valid.
Yout first() function should validate the value and only submit a 'valid' first item.
Something like:
public void test() {
Integer[] ids = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
Observable.from(ids)
.flatMap(new Func1<Integer, Observable<String>>() {
#Override
public Observable<String> call(Integer id) {
return get(id);
}
})
.toList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<List<String>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(List<String> strings) {
}
});
}
public Observable<String> get(int id) {
return Observable.concat(getLocal(id), getRemote(id))
.first(new Func1<String, Boolean>() {
#Override
public Boolean call(String s) {
return s != null;
}
});
}
public Observable<String> getLocal(int id) {
return Observable.just(id < 5 ? "fromLocal id:"+id : null);
}
public Observable<String> getRemote(int id) {
return Observable.just(id >= 5 ? "fromRemote id:"+id : null);
}
Will bring u the result:
fromLocal id:1
fromLocal id:2
fromLocal id:3
fromLocal id:4
fromRemote id:5
fromRemote id:6
fromRemote id:7
fromRemote id:8
fromRemote id:9
fromRemote id:10
Answer from github:
Your function Observable get() returns endless Observable because
mapToOneOrDefault does not complete Observable on its own and reacts to
updates of the db. You need to limit emission of this Observable because
operator concat waits for onCompleted event.
For example, it should looks like:
return Observable.concat(localSource.first(), remoteSource)
.filter(new Func1<Item, Boolean>() {
#Override
public Boolean call(Item item) {
return item!= null;
}
});
i'm new in Rx programming (and I'm having a lot of fun so far ^^).
I'm trying to transform a AsyncTask call into an Rx function.
My function :
Get all the installed apps
normalize the labels
sort everything alphabetically
arrange them by group of letter (it was a Multimap(letter, list of apps)) and pass the result to an adapter to display everything.
Here is how I'm doing so far with Rx :
Observable.from(getInstalledApps(getActivity(), false))
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.map(new Func1<ResolvedActivityInfoWrapper, ResolvedActivityInfoWrapper>() {
#Override
public ResolvedActivityInfoWrapper call(ResolvedActivityInfoWrapper act) {
// Normalize labels
act.setLabel(Normalizer.normalize(act.getLabel(getPackageManager()).replace(String.valueOf((char) 160), "").trim(), Normalizer.Form.NFD).replaceAll("\\p{M}", ""));
return act;
}
})
.toList()
.subscribe(new Observer<List<ResolvedActivityInfoWrapper>>() {
List<ResolvedActivityInfoWrapper> list;
#Override
public void onCompleted() {
Observable.from(list).groupBy(new Func1<ResolvedActivityInfoWrapper, String>() {
#Override
public String call(ResolvedActivityInfoWrapper input) {
//Get groups by letter
String label = input.getLabel(getPackageManager());
if (!TextUtils.isEmpty(label)) {
String firstChar = label.substring(0, 1);
if (pattern.matcher(firstChar).matches()) {
return firstChar.toUpperCase();
}
}
return "#";
}
}).subscribe(this); // implementation below
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(List<ResolvedActivityInfoWrapper> list) {
Collections.sort(list, new Comparator<ActivityInfoWrapper>() {
#Override
// Sort all the apps in the list, not sure it's a good way to do it
public int compare(ActivityInfoWrapper info1, ActivityInfoWrapper info2) {
return info1.getLabel(getPackageManager()).compareToIgnoreCase(info2.getLabel(getPackageManager()));
}
});
this.list = list;
}
});
Once I groupedBy letters, on complete I subscribe with this :
#Override
public void onCompleted() {
//display the apps
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(GroupedObservable<String, ResolvedActivityInfoWrapper> input) {
//For each list of apps by letter i subscribe with an observer that will handle those apps (observer code below)
input.subscribe(new TestObserver(input.getKey()));
}
Observer :
private class TestObserver implements Observer<ResolvedActivityInfoWrapper> {
List<ResolvedActivityInfoWrapper> list;
String letter;
public TestObserver(String letter) {
list = new ArrayList<>();
this.letter = letter;
}
#Override
public void onCompleted() {
adapter.addData(letter, list);
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(ResolvedActivityInfoWrapper input) {
list.add(input);
}
}
Everything works correctly excpets for one problem : the observer's onCompleted are called not in the right order. So I got all my apps, sorted by letter, but the groups are nots displayed in the right order (C first, then Y, then M etc ...).
I guess there are plenty of errors in the code, can you help me with this probleme and maybe understanding how all this works please ?
Thanks
UPDATE :
Following the advices in the commentary section (thanks people), here is what I'm trying after normalizing the labels :
Observable.from(list).groupBy(new Func1<ResolvedActivityInfoWrapper, String>() {
#Override
public String call(ResolvedActivityInfoWrapper input) {
String label = input.getLabel(getPackageManager());
if (!TextUtils.isEmpty(label)) {
String firstChar = label.substring(0, 1);
if (pattern.matcher(firstChar).matches()) {
return firstChar.toUpperCase();
}
}
return "#";
}
})
.toSortedList(new Func2<GroupedObservable<String, ResolvedActivityInfoWrapper>, GroupedObservable<String, ResolvedActivityInfoWrapper>, Integer>() {
#Override
public Integer call(GroupedObservable<String, ResolvedActivityInfoWrapper> obs1, GroupedObservable<String, ResolvedActivityInfoWrapper> obs2) {
return obs1.getKey().compareToIgnoreCase(obs2.getKey());
}
})
.subscribe(new Observer<List<GroupedObservable<String, ResolvedActivityInfoWrapper>>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(List<GroupedObservable<String, ResolvedActivityInfoWrapper>> input) {
String test = input.get(0).getKey();
}
});
But it never goes into the Compare function.
I'm looking a way to define order(?) of observers.
#GET("/get_user_msgs")
Observable<PrivateMessagesResponse> getPrivateMessages(#QueryMap Map<String, String> params);
For example I gave a Observable from my Rest API created by Retrofit.
In my ListView I'm observing this Observable.
api.getPrivateMessages(params).subscribe(new Observer());
I also have an API wrapper for my Espresso tests and I'm subscribing to same Observable there. This way observer in API wrapper is called first and only then observer in ListView
is called.
public class IdlingWrapper implements Api, IdlingResource {
....
public IdlingWrapper(Api realApi) {
this.realApi = realApi;
}
...
public Observable<PrivateMessagesResponse> getPrivateMessages(#QueryMap Map<String, String> params); {
counter.incrementAndGet();
return wrapObservable(realApi.getPrivateMessages(params));
}
protected <T> Observable<T> wrapObservable(final Observable<PrivateMessagesResponse> observable) {
//what to do here?
}
}
Is there a way to force some observer to be notified after all others are done? Or something similar in that matter?
Something like
Observable observable = getObservable();
observable.subscribeAsLast(new LastObserver());
observable.subscribe(new ObserverA());
observable.subscribe(new ObserverB());
And so that ObserverA would be notified first, then ObserverB and only then LastObserver.
Or any other approach where I could find out when all registered observers were notified and completed.
I'm not exactly sure what you are trying to do in IdlingWrapper, but I think the current implementation is very fragile.
I think the most important thing that needs to happen is to guarantee the observable can only be called once.
Here is a quick implementation to demonstrate that as well as my implementation of wrapObservable.
public class Test {
private static int counter = 0;
private static final List<Observable<?>> list = Collections.synchronizedList(new ArrayList<>());
protected static <T> Observable<T> wrapObservable(final Observable<T> original) {
// run atleast once???
synchronized (list) {
list.add(original);
}
return Observable.create(new Observable.OnSubscribe<Void>() {
#Override
public void call(Subscriber<? super Void> subscriber) {
synchronized (list) {
counter++;
if (!list.contains(original)) {
subscriber.onError(new Exception("You can only subscribe once!"));
return;
}
list.remove(original);
}
// Sleep to make it easier to see things happening...
try {
Thread.sleep(3000);
} catch (InterruptedException ignored) {
}
subscriber.onCompleted();
}
}).flatMap(new Func1<Void, Observable<? extends T>>() {
#Override
public Observable<? extends T> call(Void o) {
return original;
}
}).finallyDo(new Action0() {
#Override
public void call() {
synchronized (list) {
counter--;
if (list.size() == 0 && counter == 0) {
System.err.println("finally");
}
}
}
});
}
public static void main(String[] args) throws InterruptedException {
for(int i = 0; i < 10; i++) {
// running in io thread for simulating async call.
Observable<String> test = wrapObservable(Observable.from("TEST!!!!!!")).subscribeOn(Schedulers.io());
test.subscribe(new Observer<String>() {
#Override
public void onCompleted() {
System.err.println("completed");
}
#Override
public void onError(Throwable e) {
System.err.println("error");
}
#Override
public void onNext(String s) {
System.err.println("next");
}
});
// example of calling the same observable twice.
test.subscribe(new Observer<String>() {
#Override
public void onCompleted() {
System.err.println("completed");
}
#Override
public void onError(Throwable e) {
System.err.println("error");
}
#Override
public void onNext(String s) {
System.err.println("next");
}
});
}
Thread.sleep(10000);
}
}
It seems, that this worked just fine.
protected <T> Observable<T> wrapObservable(final Observable<T> original) {
return Observable.create(new Observable.OnSubscribeFunc<T>() {
#Override
public Subscription onSubscribe(final Observer<? super T> t1) {
original.subscribe(new Observer<T>() {
#Override
public void onCompleted() {
t1.onCompleted();
uiThreadHandler.post(new Runnable() {
#Override
public void run() {
counter.decrementAndGet();
notifyIdle();
}
});
}
#Override
public void onError(Throwable e) {
t1.onError(e);
uiThreadHandler.post(new Runnable() {
#Override
public void run() {
counter.decrementAndGet();
notifyIdle();
}
});
}
#Override
public void onNext(T args) {
t1.onNext(args);
}
});
return Subscriptions.empty();
}
});
}
If you want to just use built in RxJava methods to order your observers, you can use flatMap and range to turn each item into multiple items each with a priority and then filter on priority. Observers are ordered based on how they filter.
Here's a trivial example:
Observable<Pair<Integer, Object>> shared = RxView.clicks(findViewById(R.id.textView))
.flatMap(c -> Observable.range(0, 2).map(i -> Pair.create(i, c)))
.share();
shared.filter(p -> p.first == 1)
.map(p -> p.second)
.doOnSubscribe(c -> Log.d(TAG, "first subscribed doOnSubscribe"))
.subscribe(c -> Log.d(TAG, "first subscribed onNext"));
shared.filter(p -> p.first == 0)
.map(p -> p.second)
.doOnSubscribe(c -> Log.d(TAG, "second subscribed doOnSubscribe"))
.subscribe(c -> Log.d(TAG, "second subscribed onNext"));
If you are doing this all over the place