Architecture components: Observer keep observing even after removing it on onDestroy - android

I'm developing an app where I need to do a network call every 30 seconds, and delete the previous data and insert the new one. And every time the new data is inserted I'm showing it in the RecyclerView. I'm using Handler to give a network call and LiveData for observing data changes. Everything just works fine, just Live data observer triggers multiple time, so the data is getting deleted and inserted multiple times in result to refresh the RecyclerView frequently causing it to flash multiple times every 30 seconds.
So below is the code what I've tried:
In my Fragment I do this:
private LiveData<List<RestaurantTablesModel>> mData;
private Observer<List<RestaurantTablesModel>> mObserver;
private TablesViewModel mViewModel;
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View mView = inflater.inflate(R.layout.fragment_tables, container, false);
ButterKnife.bind(this, mView);
TablesViewModelFactory factory = InjectorUtils.provideTablesFactory(getActivity());
mViewModel = ViewModelProviders.of(this, factory).get(TablesViewModel.class);
setUpUserRecyclerView();
return mView;
}
private void setUpRecyclerView() {
mData = mViewModel.getTablesData(mLocationID);
mObserver = tablesModels -> {
if (tablesModels != null) {
mTablesRecyclerAdapter.addTables(tablesModels);
Log.e(LOG_TAG, "setUpUserRecyclerView: tablesModels");
}
};
mData.observe(this, mObserver);
}
Removing the observer onDestroy:
#Override
public void onDestroy() {
mData.removeObserver(mObserver);
super.onDestroy();
}
Following is my method in ViewModel:
public LiveData<List<TablesModel>> getTablesData(int mLocationID){
return mRepository.getTablesData(mLocationID);
}
Repository:
public LiveData<List<TablesModel>> getTablesData(int mLocationID){
LiveData<TablesModel[]> mTablesData = mDataSource.getTablesData();
mTablesData.observeForever(tablesModels -> {
mExecutors.diskIO().execute(() -> {
//Completed: delete old table data if there are conflicts.
if (tablesModels != null) {
mDatabaseDao.deleteTables();
mDatabaseDao.insertTablesData(tablesModels);
}else {
Log.e(LOG_TAG, "Nothing: ");
}
});
Log.e("Handlers", "repository getTablesData");
});
return mDatabaseDao.getTablesData(mLocationID);
}
DataSource:
private MutableLiveData<RestaurantTablesModel[]> mDownloadedTablesModel;
public LiveData<RestaurantTablesModel[]> getTablesData() {
Log.e("Handlers", "getTablesData");
fetchTablesData();
return mDownloadedTablesModel;
}
public void fetchTablesData() {
if (Utils.isNetworkAvailable(mContext)) {
NetworkUtils.NetworkInterface mInterface = this;
handler = new Handler();
runnableCode = new Runnable() {
#Override
public void run() {
// Do something here on the main thread
Log.e("Handlers", "Called on network thread");
URL getTablesURL = NetworkUtils.getAllTableUrl(mContext);
NetworkUtils.getResponseFromAPI(mContext, getTablesURL, mInterface);
// Repeat this the same runnable code block again another 30 seconds
// 'this' is referencing the Runnable object
handler.postDelayed(this, 30000);
}
};
handler.post(runnableCode);
} else {
Log.d(LOG_TAG, "fetchTablesData: No network!");
}
}
Now the problem is when my fragment is destroyed and recreated the Observer gets triggered multiple times, here are the logs:
09-05 10:28:29.853 3666-3666/? E/TablesFragment: setUpRecyclerView: tablesModels
09-05 10:28:30.039 3666-3666/? E/TablesFragment: setUpRecyclerView: tablesModels
09-05 10:28:30.607 3666-3666/? E/TablesFragment: setUpRecyclerView: tablesModels
09-05 10:28:30.657 3666-3666/? E/TablesFragment: setUpRecyclerView: tablesModels
09-05 10:28:30.669 3666-3666/? E/TablesFragment: setUpRecyclerView: tablesModels
09-05 10:28:30.704 3666-3666/? E/TablesFragment: setUpRecyclerView: tablesModels
And it triggers more times than before, every time fragment is recreated, I think it the observer is getting called on a recreation of the fragment and the previous instance of the observer is still in a play.
But if I'm removing the observer in OnDestroy, why should it be happening?
Any help would be highly appreciated.
EDIT:
I changed my code to check if the LiveData and Observer are null, and then only initialize it. But it doesn't help, it is still getting called multiple times.
if (mTablesData == null){
mData = mViewModel.getTablesData(mLocationID);
if (mObserver == null){
mObserver = tablesModels -> {
if (tablesModels != null) {
mTablesRecyclerAdapter.addTables(tablesModels);
Log.e(LOG_TAG, "setUpUserRecyclerView: tablesModels");
}
};
mData.observe(this, mObserver);
}
}
EDIT 2:
Tried this also, but didn't work as well:
mTablesData = mViewModel.getTablesData(mLocationID);
mObserver = tablesModels -> {
if (tablesModels != null) {
mTablesRecyclerAdapter.addTables(tablesModels);
Log.e(LOG_TAG, "setUpRecyclerView: tablesModels");
}
};
if (!mTablesData.hasObservers()) {
mTablesData.observe(this, mObserver);
}

So what we learned from experiments in the comments, you needed to check if mTablesData is already being observed before observing it, and observe only if it is not being observed, like
if (!mTablesData.hasObservers()) {
mTablesData.observeForever(tablesModels -> {
...

First, If I understand correct you use RecyclerView and every fragment in that RecyclerView called setUpUserRecyclerView(); in its onCreate() method. So, if you have 3 Fragments, you will have 3 Observers. If you want all of them to use the ViewModel of the Activity you have to point the parent Activity here -> ViewModelProviders.of(getActivity(), factory)
Second, Why do you use observeForever in your Repository? Can you use just observe?
And last if you want to run this request in every 30 seconds, why don't you use PeriodicWorkRequest of WorkManager -> https://developer.android.com/topic/libraries/architecture/workmanager/basics#java
Hope I help somehow :)

I think you need wrap the mObserver in a CompositeDisposable.
CompositeDisposable disposable = new CompositeDisposable();
disposable.add(mObserver);
#Override
public void onDestroy() {
mData.removeObserver(mObserver);
disposable.clear();
super.onDestroy();
}
I hope it helps you.

Related

How to invalidate ViewModel/LiveData after some time?

As recommended by the latest documentation I'm using ViewModel/LiveData to show some data:
public class EventDatesViewModel extends AndroidViewModel {
private final MutableLiveData<String> timeToEvent = new MutableLiveData<>();
...
public LiveData<String> getTimeToEvent() {
if (timeToEvent == null)
new Thread(this::calculateTimeToEvent).start();
return timeToEvent;
}
private void calculateTimeToEvent() {
...
timeToEvent.postValue(t);
}
}
I use that in my fragment:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_garbage, container, false);
EventDatesViewModel model = new ViewModelProvider(requireActivity(), new ViewModelProvider.AndroidViewModelFactory(requireActivity().getApplication())).get(EventDatesViewModel.class);
model.getTimeToEvent().observe(this, timeToEvent -> {
((TextView)root.findViewById(R.id.textTimeToEvent)).setText(timeToEvent);
});
return root;
}
Now the problem is that timeToEvent is something like "2 days 4 hours". So if I leave the app open, at some point it will be outdated and needs to be recalculated. If the app is not open or is in the background, I don't want to run the rather expensive calculation.
How can I achieve this periodical recalculation?
What didn't work
I added a Handler that uses postDelayed() to schedule the recalculation.
private final Handler handler = new Handler();
private final Runnable runnable = new Runnable() {
#Override
public void run() {
new Thread(() -> calculateTimeToEvent()).start();
handler.postDelayed(this, 10000);
}
};
public LiveData<String> getTimeToEvent() {
if (timeToEvent == null) {
new Thread(this::calculateTimeToEvent).start();
handler.postDelayed(runnable, 10000);
}
return timeToEvent;
}
#Override
protected void onCleared() {
super.onCleared();
handler.removeCallbacks(runnable);
}
Unfortunately this runs the calculation even when the app is in the background or the device is sleeping. I logged the runs of calculateTimeToEvent() with timestamps into a file and I can see that they occur every 10 seconds, with no interruption.
Since you are using Java you can use a Handler using postDelayed to fire a runnable after X amount of time then schedule it again after it fires
This is kotlin but can easily be turned back into Java
In your ViewModel
private val _handler = Handler()
private val _runnable = object : Runnable {
override fun run() {
calculateTimeToEvent()
_handler.postDelayed(this, 3600000)
}
}
Then at some point call this in your viewmodel to start the recurring operation
_handler.postDelayed(_runnable, 3600000)
Then in your viewmodel's on onCleared you can remove the callbacks to stop it
_handler.removeCallbacks(_runnable)
There is also the option of Using RxJava and using interval

How to subscribe and unsubscribe or cancel an rxjava observable

I am new in RxJava and trying to update my asyncTask works to RxJava. As a first try I have done the following codes:
public class MainActivity extends AppCompatActivity
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
doSomeWork();
}
private String funcCallServerGet()
{
//Some code to call a HttpClient Get method & return a response string
//this is the method which previously i used to call inside asynctask doInbackground method
}
private void doSomeWork() {
getSingleObservable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getSingleObserver()) ;
}
private Single<String> getSingleObservable()
{
return Single.create(new SingleOnSubscribe<String>() {
#Override
public void subscribe(SingleEmitter<String> emitter) throws Exception {
if(!emitter.isDisposed()) {
String strRxResponse = funcCallServerGet();
emitter.onSuccess(strRxResponse);
}
}
});
}
private SingleObserver<String> getSingleObserver()
{
return new SingleObserver<String>() {
#Override
public void onSubscribe(Disposable d) {
Log.d(TAG, " onSubscribe getSingleObserver: " + d.isDisposed()); }
#Override
public void onSuccess(String value) {
Log.d(TAG, " onNext : value : " + value); }
#Override
public void onError(Throwable e) {
Log.d(TAG, " onError : " + e.getMessage()); }
};
}
}
But I have some confusions:
Why am I getting false in onSubscribe() of SingleObserver getSingleObserver() .
How do I unsubscribe or cancel the observable/observer when activities onStop() is called.
Also, what really happens when screen oriantation. Does the observable get unsubscribed automatically or it continues its work ? what to do for the device rotation ?
Why am I getting false in onSubscribe() of SingleObserver getSingleObserver() .
You're currently logging whether the disposable is disposed within the onSubscribe method. At this point the disposable hasn't been disposed yet.
How do I unsubscribe or cancel the observable/observer when activities onStop() is called.
Rather than use a SingleObserver you could use the subscribe method which returns a disposable. With this you could either manage the disposable directly or use a CompositeDisposable. You would then call the dispose method on that disposable, with CompositeDisposable this is achieved by calling clear()
private final CompositeDisposable disposables = new CompositeDisposable();
#Override
protected void onStart() {
super.onStart();
disposables.add(getSingleObservable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(value -> {
Log.d(TAG, " onSuccess: " + value);
}, error -> {
Log.e(TAG, " onError", error);
}
)
);
}
#Override
protected void onStop() {
disposables.clear();
super.onStop();
}
Also, what really happens when screen oriantation. Does the observable get unsubscribed automatically or it continues its work ? what to do for the device rotation ?
By default no automatic management of the observable occurs, it's your responsibility to manage it. In your example code when the device rotates you will receive another call to onCreate, here you're scheduling the work to be executed again, work that was scheduled before rotation could still be running, so you could end up leaking the old activity and receiving a callback when the work succeeds or fails - in this case you'd see a log statement.
There are some tools that provide automatic observable management, though you should read the authors article about some of the issues that exist with this approach.
https://blog.danlew.net/2017/08/02/why-not-rxlifecycle/
https://github.com/trello/RxLifecycle
https://github.com/uber/AutoDispose
Another option for you could be to look at the new Architecture Components library, specifically ViewModel and LiveData. This will simplify what you need to do with respect to subscription management and configuration changes.

Using RxJava inside RecyclerView Adapter

So a few weeks ago i asked this question: recyclerview periodic ui child updates.
And today i want to refactor that funcionality using Rxjava. It's actually pretty simple, i accomplish the following way:
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (friend.getGameStatus().equals(GameStatus.INGAME)) {
holderOnline.startRepeatingTask();
} else {
holderOnline.stopRepeatingTask();
}
}
class MyViewHolderOnline extends RecyclerView.ViewHolder {
private Subscription subscribe;
public MyViewHolderOnline(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
public void startRepeatingTask() {
subscribe = Observable.interval(updateInterval, TimeUnit.MILLISECONDS)
.map(aLong -> current.getGameStatusToPrint())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<String>() {
#Override public void onCompleted() { }
#Override public void onError(Throwable e) { }
#Override
public void onNext(String gameStatusToPrint) {
gameStatus.setText(gameStatusToPrint);
}
});
}
void stopRepeatingTask() {
if(subscribe != null && !subscribe.isUnsubscribed())
subscribe.unsubscribe();
}
}
The problem however is different. I have a navigationDrawer implemented with Activities that are Paused and not Destroyed when switched. So, after i switch to the activity that don't contains this adapter, the observable keeps on sending stuff because its a periodic interval Observable. So my question is, what should i do? I need to unsubscribe when the activity is paused, but i have no idea how since to, and also how to subscribe back. Any help or ideas?
So, if I understand you correctly, one way to solve your problem is to answer the question: In an Activity that contains a RecyclerView, how do I get references to all the ViewHolders that are currently displayed/bound?
For example, to stop all the updates you could then do the following in the onPause() of your Activity:
// assuming you are using a LinearLayoutManager:
final int firstVisibleIndex = mLinearLayoutManager.findFirstVisibleItemPosition();
final int lastVisibleIndex = mLinearLayoutManager.findLastVisibleItemPosition();
for (int i = firstVisibleIndex; i <= lastVisibleIndex; i++) {
YourViewHolder viewHolder = (YourViewHolder) findViewHolderForAdapterPosition (i);
viewHolder.stopRepeatingTask();
}
And likewise, you could restart the tasks in the onResume of your Activity.
BUT: Now that I wrote this I am not sure whether there may be ViewHolders beyond the visible area of the RecyclerView (i. e. before the first or after the last visible item) that are already bound but that would not be reached with this method. If that turns out to be the case you can still iterate over all indices of the items in your adapter and just discard any null return values.

How to know when the RecyclerView has finished laying down the items?

I have a RecyclerView that is inside a CardView. The CardView has a height of 500dp, but I want to shorten this height if the RecyclerView is smaller.
So I wonder if there is any listener that is called when the RecyclerView has finished laying down its items for the first time, making it possible to set the RecyclerView's height to the CardView's height (if smaller than 500dp).
I also needed to execute code after my recycler view finished inflating all elements. I tried checking in onBindViewHolder in my Adapter, if the position was the last, and then notified the observer. But at that point, the recycler view still was not fully populated.
As RecyclerView implements ViewGroup, this anwser was very helpful. You simply need to add an OnGlobalLayoutListener to the recyclerView:
View recyclerView = findViewById(R.id.myView);
recyclerView
.getViewTreeObserver()
.addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// At this point the layout is complete and the
// dimensions of recyclerView and any child views
// are known.
recyclerView
.getViewTreeObserver()
.removeOnGlobalLayoutListener(this);
}
});
Working modification of #andrino anwser.
As #Juancho pointed in comment above. This method is called several times. In this case we want it to be triggered only once.
Create custom listener with instance e.g
private RecyclerViewReadyCallback recyclerViewReadyCallback;
public interface RecyclerViewReadyCallback {
void onLayoutReady();
}
Then set OnGlobalLayoutListener on your RecyclerView
recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (recyclerViewReadyCallback != null) {
recyclerViewReadyCallback.onLayoutReady();
}
recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
after that you only need implement custom listener with your code
recyclerViewReadyCallback = new RecyclerViewReadyCallback() {
#Override
public void onLayoutReady() {
//
//here comes your code that will be executed after all items are laid down
//
}
};
If you use Kotlin, then there is a more compact solution.
Sample from here.
This layout listener is usually used to do something after a View is measured, so you typically would need to wait until width and height are greater than 0.
... it can be used by any object that extends View and also be able to access to all its specific functions and properties from the listener.
// define 'afterMeasured' layout listener:
inline fun <T: View> T.afterMeasured(crossinline f: T.() -> Unit) {
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
if (measuredWidth > 0 && measuredHeight > 0) {
viewTreeObserver.removeOnGlobalLayoutListener(this)
f()
}
}
})
}
// using 'afterMeasured' handler:
myRecycler.afterMeasured {
// do the scroll (you can use the RecyclerView functions and properties directly)
// ...
}
The best way that I found to know when has finished laying down the items was using the LinearLayoutManager.
For example:
private RecyclerView recyclerView;
...
recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false){
#Override
public void onLayoutCompleted(RecyclerView.State state) {
super.onLayoutCompleted(state);
// TODO
}
);
...
I improved the answer of android developer to fix this problem. It's a Kotlin code but should be simple to understand even if you know only Java.
I wrote a subclass of LinearLayoutManager which lets you listen to the onLayoutCompleted() event:
/**
* This class calls [mCallback] (instance of [OnLayoutCompleteCallback]) when all layout
* calculations are complete, e.g. following a call to
* [RecyclerView.Adapter.notifyDataSetChanged()] (or related methods).
*
* In a paginated listing, we will decide if load more needs to be called in the said callback.
*/
class NotifyingLinearLayoutManager(context: Context) : LinearLayoutManager(context, VERTICAL, false) {
var mCallback: OnLayoutCompleteCallback? = null
override fun onLayoutCompleted(state: RecyclerView.State?) {
super.onLayoutCompleted(state)
mCallback?.onLayoutComplete()
}
fun isLastItemCompletelyVisible() = findLastCompletelyVisibleItemPosition() == itemCount - 1
interface OnLayoutCompleteCallback {
fun onLayoutComplete()
}
}
Now I set the mCallback like below:
mLayoutManager.mCallback = object : NotifyingLinearLayoutManager.OnLayoutCompleteCallback {
override fun onLayoutComplete() {
// here we know that the view has been updated.
// now you can execute your code here
}
}
Note: what is different from the linked answer is that I use onLayoutComplete() which is only invoked once, as the docs say:
void onLayoutCompleted (RecyclerView.State state)
Called after a full layout calculation is finished. The layout
calculation may include multiple onLayoutChildren(Recycler, State)
calls due to animations or layout measurement but it will include only
one onLayoutCompleted(State) call. This method will be called at the
end of layout(int, int, int, int) call.
This is a good place for the LayoutManager to do some cleanup like
pending scroll position, saved state etc.
I tried this and it worked for me. Here is the Kotlin extension
fun RecyclerView.runWhenReady(action: () -> Unit) {
val globalLayoutListener = object: ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
action()
viewTreeObserver.removeOnGlobalLayoutListener(this)
}
}
viewTreeObserver.addOnGlobalLayoutListener(globalLayoutListener)
}
then call it
myRecyclerView.runWhenReady {
// Your action
}
Also in same cases you can use RecyclerView.post() method to run your code after list/grid items are popped up. In my cases it was pretty enough.
I have been struggling with trying to remove OnGlobalLayoutListener once it gets triggered but that throws an IllegalStateException. Since what I need is to scroll my recyclerView to the second element what I did was to check if it already have children and if it is the first time this is true, only then I do the scroll:
public class MyActivity extends BaseActivity implements BalanceView {
...
private boolean firstTime = true;
...
#Override
protected void onCreate(Bundle savedInstanceState) {
...
ViewTreeObserver vto = myRecyclerView.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (myRecyclerView.getChildCount() > 0 && MyActivity.this.firstTime){
MyActivity.this.firstTime = false;
scrollToSecondPosition();
}
}
});
}
...
private void scrollToSecondPosition() {
// do the scroll
}
}
HTH someone!
(Of course, this was inspired on #andrino and #Phatee answers)
Here is an alternative way:
You can load your recycler view in a thread. Like this
First, create a TimerTask
void threadAliveChecker(final Thread thread){
Timer timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
if(!thread.isAlive()){
runOnUiThread(new Runnable() {
#Override
public void run() {
// stop your progressbar here
}
});
}
}
},500,500);
}
Second, create a runnable
Runnable myRunnable = new Runnable() {
#Override
public void run() {
runOnUiThread(new Runnable() {
#Override
public void run() {
// load recycler view from here
// you can also toast here
}
});
}
};
Third, create a thread
Thread myThread = new Thread(myRunnable);
threadAliveChecker();
// start showing progress bar according to your need (call a method)
myThread.start();
Understanding the above code now:
TimerTask - It will run and will check the thread
(every 500 milliseconds) is running or completed.
Runnable - runnable is just like a method, here you have written the code that is needed to be done in that thread. So our recycler view will be called from this runnable.
Thread - Runnable will be called using this thread. So we have started this thread and when the recyclerView load (runnable code load) then this thread will be completed (will not live in programming words).
So our timer is checking the thread is alive or not and when the thread.isAlive is false then we will remove the progress Bar.
If you are using the android-ktx library and if you need to perform an action after positioning all elements of the Activity, you can use this method:
// define 'afterMeasured' Activity listener:
fun Activity.afterMeasured(f: () -> Unit) {
window.decorView.findViewById<View>(android.R.id.content).doOnNextLayout {
f()
}
}
// in Activity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(...)
afterMeasured {
// do something here
}
}
This is how I did it
recyclerView.setLayoutManager(new LinearLayoutManager(this){
#Override
public void onLayoutCompleted(RecyclerView.State state) {
super.onLayoutCompleted(state);
//code to run after loading recyclerview
new GuideView.Builder(MainActivity.this)
.setTargetView(targetView)
.setGravity(Gravity.auto)
.setDismissType(DismissType.outside)
.setContentTextSize(18)
.build()
.show();
}
});
I wish this will help you.
You can use with this approach
if ((adapterPosition + 1) == mHistoryResponse.size) {
Log.d("debug", "process done")
}
get the adapterPosition with plus 1 and check it with your data classes size, if it has same size, the process is practically complete.
For those that are not using Kotlin and are still struggling, I took a fast look at the doOnNextLayout(crossinline action: (view: T) -> Unit) solution they implemented, and it is pretty simple.
IF you are NOT working with a custom RecyclerView (CustomRecyclerView extends RecyclerView), you may want to rethink it as this will bring a lot of benefits you may want to add in the future (smooth scroll to position, vertical dividers, etc..)
Inside the CustomRecyclerView.class
public void doOnNextLayout(Consumer<List<View>> onChange) {
addOnLayoutChangeListener(
new OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
onChange.accept(getChildren());
removeOnLayoutChangeListener(this);
}
}
);
}
The getChildren() method is building a List of size getChildCount(); and a add(getChild(i)) on each iteration.
Now...
One important aspect about the code is this: removeOnLayoutChangeListener(this);
This means that the devs are asking for you to execute this before each list submission to the adapter.
In theory we could only place the listener ONCE upon RecyclerView creation (which IMO would be cheaper/better) + because we are retrieving the views, we could retrieve their respective binds with DataBindingUtils. and get whatever data the adapter gave the view onBind via their DataBind.
To do this tho it requires more code.
First the adapter needs to be aware of the Fragment they inhabit, OR the RecyclerView::setAdapter needs to provide a ViewLifeCyclerOwner, a third easier option is to provide the adapter with a onViewDestroy() method, and execute it on Fragment's onDestroyView() method.
#Override
public void onDestroyView() {
super.onDestroyView();
adater.onViewDestroyed();
}
by overriding the onAttachedToRecyclerView, we are able to attach them as observers.
private final List<Runnable> submitter = new ArrayList<>();
#Override
public void onAttachedToRecyclerView(#NonNull RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
if (recyclerView instanceof CustomRecyclerView) {
submitter.add(((CustomRecyclerView) recyclerView)::onSubmit);
}
}
Where the onSubmit method on the CustomRecyclerView side will provide a boolean that will tell the recyclerView whether a list is being submitted.
private boolean submitting;
public void doOnNextLayout(Consumer<List<View>> onChange) {
addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
if (submitting) {
onChange.accept(getChildren());
submitting = false;
}
}
);
}
public void onSubmit() {
submitting = true;
}
Each Runnable will be executed at the moment of list submission:
In the case of the ListAdapter there are 2 possible entry points:
private void notifyRVs() {
for (Runnable r:submitter
) {
r.run();
}
}
#Override
public void submitList(#Nullable List<X> list, #Nullable Runnable commitCallback) {
notifyRVs();
super.submitList(list, commitCallback);
}
#Override
public void submitList(#Nullable List<X> list) {
notifyRVs();
super.submitList(list);
}
Now to prevent memory leaks we must clear the List of Runnables on ViewDestroyed()
inside the Adapter...
public void onViewDestroyed() {
submitter.clear();
}
Now because the functionality of the method changed we should rename it, and decouple the Consumer<List> from the LayoutChangeListener()
private Consumer<List<View>> onChange = views -> {};
public void setOnListSubmitted(Consumer<List<View>> onChange) {
this.onChange = onChange;
}
public CustomRecyclerView(#NonNull Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
//Read attributes
setOnListSubmissionListener();
}
private void setOnListSubmissionListener() {
addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
if (submitting) {
onChange.accept(getChildren());
submitting = false;
}
}
);
}
What worked for me was to add the listener after setting the RecyclerView adapter.
ServerRequest serverRequest = new ServerRequest(this);
serverRequest.fetchAnswersInBackground(question_id, new AnswersDataCallBack()
{
#Override
public void done(ArrayList<AnswerObject> returnedListOfAnswers)
{
mAdapter = new ForumAnswerRecyclerViewAdapter(returnedListOfAnswers, ForumAnswerActivity.this);
recyclerView.setAdapter(mAdapter);
recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener()
{
#Override
public void onGlobalLayout()
{
progressDialog.dismiss();
}
});
}
});
This dismisses the "progressDialog" after the global layout state or the visibility of views within the view tree changes.
// Another way
// Get the values
Maybe<List<itemClass>> getItemClass(){
return /* */
}
// Create a listener
void getAll(DisposableMaybeObserver<List<itemClass>> dmo) {
getItemClass().subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(dmo);
}
// In the code where you want to track the end of loading in recyclerView:
DisposableMaybeObserver<List<itemClass>> mSubscriber = new DisposableMaybeObserver<List<itemClass>>() {
#Override
public void onSuccess(List<itemClass> item_list) {
adapter.setWords(item_list);
adapter.notifyDataSetChanged();
Log.d("RECYCLER", "DONE");
}
#Override
public void onError(Throwable e) {
Log.d("RECYCLER", "ERROR " + e.getMessage());
}
#Override
public void onComplete() {
Log.d("RECYCLER", "COMPLETE");
}
};
void getAll(mSubscriber);
//and
#Override
public void onDestroy() {
super.onDestroy();
mSubscriber.dispose();
Log.d("RECYCLER","onDestroy");
}
recyclerView.getChildAt(recyclerView.getChildCount() - 1).postDelayed(new Runnable() {
#Override
public void run() {
//do something
}
}, 300);
RecyclerView only lays down specific number of items at a time, we can get the number by calling getChildCount(). Next, we need to get the last item by calling getChildAt (int index). The index is getChildCount() - 1.
I'm inspired by this person answer and I can't find his post again. He said it's important to use postDelayed() instead of regular post() if you want to do something to the last item. I think it's to avoid NullPointerException. 300 is delayed time in ms. You can change it to 50 like that person did.

Android - Best and safe way to stop thread

I want to know which is the best way to stop a thread in Android. I know I can use AsyncTask instead of it and that there is a cancel() method. I have to use Threads in my situation. Here is how I'm using Thread:
Runnable runnable = new Runnable() {
#Override
public void run() {
//doing some work
}
};
new Thread(runnable).start();
So, does anyone have any idea of which is the best way to stop a thread?
You should make your thread support interrupts. Basically, you can call yourThread.interrupt() to stop the thread and, in your run() method you'd need to periodically check the status of Thread.interrupted()
There is a good tutorial here.
This situation isn't in any way different from the standard Java. You can use the standard way to stop a thread:
class WorkerThread extends Thread {
volatile boolean running = true;
public void run() {
// Do work...
if (!running) return;
//Continue doing the work
}
}
The main idea is to check the value of the field from time to time. When you need to stop your thread, you set running to false. Also, as Chris has pointed out, you can use the interruption mechanism.
By the way, when you use AsyncTask, your apporach won't differ much. The only difference is that you will have to call isCancel() method from your task instead of having a special field. If you call cancel(true), but don't implement this mechanism, the thread still won't stop by itself, it will run to the end.
On Android the same rules apply as in a normal Java environment.
In Java threads are not killed, but the stopping of a thread is done in a cooperative way. The thread is asked to terminate and the thread can then shutdown gracefully.
Often a volatile boolean field is used which the thread periodically checks and terminates when it is set to the corresponding value.
I would not use a boolean to check whether the thread should terminate. If you use volatile as a field modifier, this will work reliable, but if your code becomes more complex, for instead uses other blocking methods inside the while loop, it might happen, that your code will not terminate at all or at least takes longer as you might want.
Certain blocking library methods support interruption.
Every thread has already a boolean flag interrupted status and you should make use of it. It can be implemented like this:
public void run() {
try {
while(!Thread.currentThread().isInterrupted()) {
// ...
}
} catch (InterruptedException consumed)
/* Allow thread to exit */
}
}
public void cancel() { interrupt(); }
Source code taken from Java Concurrency in Practice. Since the cancel() method is public you can let another thread invoke this method as you wanted.
There is also a poorly named static method interrupted which clears the interrupted status of the current thread.
The Thread.stop() method that could be used to stop a thread has been deprecated; for more info see; Why are Thread.stop, Thread.suspend and Thread.resume Deprecated?.
Your best bet is to have a variable which the thread itself consults, and voluntarily exits if the variable equals a certain value. You then manipulate the variable inside your code when you want the thread to exit. Alternately of course, you can use an AsyncTask instead.
Currently and unfortunately we can't do anything to stop the thread....
Adding something to Matt's answer we can call interrupt() but that doesn't stop thread... Just tells the system to stop the thread when system wants to kill some threads. Rest is done by system, and we can check it by calling interrupted().
[p.s. : If you are really going with interrupt() I would ask you to do some experiments with a short sleep after calling interrupt()]
Try Like this
Thread thread = new Thread() {
#Override
public void run() {
Looper.prepare();
while(true){
Log.d("Current Thread", "Running");
try{
Thread.sleep(1000);
}catch(Exeption exception){ }
}
}
};
thread.start();
thread.interrupt();
There are 2 following ways preferred to stop a thread.
Create a volatile boolean variable and change its value to false and check inside the thread.
volatile isRunning = false;
public void run() {
if(!isRunning) {return;}
}
Or you can use the interrupt() method which can be receive inside a thread.
SomeThread.interrupt();
public void run() {
if(Thread.currentThread.isInterrupted()) {return;}
}
I used this method.
Looper.myLooper().quit();
you can try.
The thing is you need to check whether the thread is running or not !?
Field:
private boolean runningThread = false;
In the thread:
new Thread(new Runnable() {
#Override
public void run() {
while (true) {
try {
Thread.sleep((long) Math.floor(speed));
if (!runningThread) {
return;
}
yourWork();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
If you want to stop the thread you should make the below field
private boolean runningThread = false;
My requirement was slightly different than the question, still this is also a useful way of stopping the thread to be executing its tasks. All I wanted to do is to stop the thread on exiting the screen and resumes while returning to the screen.
As per the Android docs, this would be the proposed replacement for stop method which has been deprecated from API 15
Many uses of stop should be replaced by code that simply modifies some
variable to indicate that the target thread should stop running. The
target thread should check this variable regularly, and return from
its run method in an orderly fashion if the variable indicates that it
is to stop running.
My Thread class
class ThreadClass implements Runnable {
...
#Override
public void run() {
while (count < name.length()) {
if (!exited) // checks boolean
{
// perform your task
}
...
OnStop and OnResume would look like this
#Override
protected void onStop() {
super.onStop();
exited = true;
}
#Override
protected void onResume() {
super.onResume();
exited = false;
}
As we know that the Thread.stop() is deprecated in JAVA, under the hood the Thread.stop calls the interrupt() method on the thread to stop it, Interrupt is meant to be thrown from the methods which keep the thread waiting for some other thread to notify after the execution completes. Interrupt will cause nothing to the thread if it is not handled in the execution of a thread, like, if(Thread.interrupted())return;
So, all in all we need to basically manage the start and stop of the thread like calling the start() method like Thread.start() starts a while(true) inside the run() method of the thread and checks for interrupted status in each iteration and returns from the thread.
Please note that a thread will not die in the following situations:
The thread has not yet returned from the run().
Any of the objects owned by the thread is accessible. (This hints to null/dispose of the references for GC to do the rest)
This worked for me like this. Introduce a static variable in main activity and regularly check for it how i did was below.
public class MainActivity extends AppCompatActivity {
//This is the static variable introduced in main activity
public static boolean stopThread =false;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Thread thread = new Thread(new Thread1());
thread.start();
Button stp_thread= findViewById(R.id.button_stop);
stp_thread.setOnClickListener(new Button.OnClickListener(){
#Override
public void onClick(View v){
stopThread = true;
}
}
}
class Thread1 implements Runnable{
public void run() {
// YOU CAN DO IT ON BELOW WAY
while(!MainActivity.stopThread) {
Do Something here
}
//OR YOU CAN CALL RETURN AFTER EVERY LINE LIKE BELOW
process 1 goes here;
//Below method also could be used
if(stopThread==true){
return ;
}
// use this after every line
process 2 goes here;
//Below method also could be used
if(stopThread==true){
return ;
}
// use this after every line
process 3 goes here;
//Below method also could be used
if(stopThread==true){
return ;
}
// use this after every line
process 4 goes here;
}
}
}
If there is thread class with a handler in your project, when you started from one of the fragment class if you wanted to stop here is the solution how to stop and avoid crashing the app when fragment removes from the stack.
This code is in Kotlin. It perfectly works.
class NewsFragment : Fragment() {
private var mGetRSSFeedsThread: GetRSSFeedsThread? = null
private val mHandler = object : Handler() {
override fun handleMessage(msg: Message?) {
if (msg?.what == GetRSSFeedsThread.GETRSSFEEDSTHREAD_SUCCESS) {
val updateXMLdata = msg.obj as String
if (!updateXMLdata.isNullOrEmpty())
parseUpdatePager(CommonUtils.getJSONObjectFromXML(updateXMLdata).toString())
} else if (msg?.what == GetRSSFeedsThread.GETRSSFEEDSTHREAD_SUCCESS) {
BaseActivity.make_toast(activity, resources.getString(R.string.pleaseTryAgain))
}
}
}
private var rootview: View? = null;
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
rootview = inflater?.inflate(R.layout.fragment_news, container, false);
news_listView = rootview?.findViewById(R.id.news_listView)
mGetRSSFeedsThread = GetRSSFeedsThread(this.activity, mHandler)
if (CommonUtils.isInternetAvailable(activity)) {
mGetRSSFeedsThread?.start()
}
return rootview
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true);
}
override fun onAttach(context: Context?) {
super.onAttach(context)
println("onAttach")
}
override fun onPause() {
super.onPause()
println("onPause fragment may return to active state again")
Thread.interrupted()
}
override fun onStart() {
super.onStart()
println("onStart")
}
override fun onResume() {
super.onResume()
println("onResume fragment may return to active state again")
}
override fun onDetach() {
super.onDetach()
println("onDetach fragment never return to active state again")
}
override fun onDestroy() {
super.onDestroy()
println("onDestroy fragment never return to active state again")
//check the state of the task
if (mGetRSSFeedsThread != null && mGetRSSFeedsThread?.isAlive!!) {
mGetRSSFeedsThread?.interrupt();
} else {
}
}
override fun onDestroyView() {
super.onDestroyView()
println("onDestroyView fragment may return to active state again")
}
override fun onStop() {
super.onStop()
println("onStop fragment may return to active state again")
}
}
Above code stops the running thread when you switch to any other fragment or activity from current fragment. also it recreates when you return to current fragment
Inside of any Activity class you create a method that will assign NULL to thread instance which can be used as an alternative to the depreciated stop() method for stopping thread execution:
public class MyActivity extends Activity {
private Thread mThread;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mThread = new Thread(){
#Override
public void run(){
// Perform thread commands...
for (int i=0; i < 5000; i++)
{
// do something...
}
// Call the stopThread() method.
stopThread(this);
}
};
// Start the thread.
mThread.start();
}
private synchronized void stopThread(Thread theThread)
{
if (theThread != null)
{
theThread = null;
}
}
}
This works for me without a problem.

Categories

Resources