2

I'm working on an editText and a recyclerView. My recyclerView is updated when I write letters in my EditText.

I put a Timer inside my textWatcher in order to avoid sending requests each time user write a letter.

searchDestinationEt.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
            //There is nothing to do here
        }

        @Override
        public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
            if (timer != null) {
                timer.cancel();
            }
        }

        @Override
        public void afterTextChanged(final Editable s) {

            timer = new Timer();

            //we schedule this in order to avoid sending useless request.
            //We wait the user is finishing writing before sending requests
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    ((Activity) context).runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            actionsListener.onDestinationSearch(s.toString());
                        }
                    });
                }
            }, DELAY_SEND_REQUEST);
        }
    });

It works well but leakcanary says that I have a leak in this part of code. Any idea ?

Bob
  • 589
  • 1
  • 11
  • 25

2 Answers2

0

Sorry for being late with the response, but did you try separating textwatcher like this ?: TextWatcher for more than one EditText

0
  1. Why do you use Timer and TimerTask for delayed, not recurring action? The easiest and the common way is to use just a regular Handler with postDelayed():

    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
           //do somthing here
        }
    }, DELAY_SEND_REQUEST);
    
  2. The leak occurs because you're starting a thread which has a reference to your context (fragment or activity). And until your thread is done - it won't be garbage collected.

That means, for example, if a user types something and you're waiting for a time to start requesting and meanwhile the user turns the phone and orientation change occurs - your activity/fragment will be recreated - but the old one (which started a thread and should be used when thread is done) is not gone and still present in memory.

  1. Why are doing a request on the UI Thread? It blocks the UI, you know that right? I assume an AsyncTask may fit better.

What should you do? Replace Timer with Handler and do the requests in a worker thread. Regarding the leak you have 2 options:

a) do nothing since the time for which your activity/fragment will be preserved is very small and it will be GCed after the request is done. (not recommended)

b) Utilize the AsyncTask and in the constructor of the AsyncTask pass the context (your listener) and store it as a weak reference object, like this:

private static class SomeWorkTask extends AsyncTask<Void,Void,Void>{

    private WeakReference<ActionsListenerWithContext> weakListener;

    public SomeWorkTask(ActionsListenerWithContext listener){
        this.weakListener = new WeakReference<>(listener);
    }

    @Override
    protected Void doInBackground(Void... voids) {
        //do some work here
        return null;
    }

    @Override
    protected void onPostExecute(Void aVoid) {
        if(weakListener.get() != null){
            weakListener.get().callYourCallbacks();
        }
    }
}

and then you call it

 new SomeWorkTask(listener).execute();

Utilizing WeakReference wrapper is a common and recommended practice.

Kirill Karmazin
  • 6,256
  • 2
  • 54
  • 42