How to add text to TextView from Loader

1

Now I'm studying Threads and my task is to make a counter, which will add number from 0 to 9 to TextView with the help of Loader. Of course, I know that it isn't the best variant to use Loader for such tasks, but I'd like to understand how does it work.

So, I have the following code:

package asus.example.com.exercise4;

import android.annotation.SuppressLint;
import android.content.Context;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class LoaderActivity extends AppCompatActivity {

    private TextView counter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_threads);
        Button startButton = findViewById(R.id.start_button);
        Button cancelButton = findViewById(R.id.cancel_button);
        startButton.setOnClickListener(listener);
        cancelButton.setOnClickListener(listener);
        counter = findViewById(R.id.counter);
    }

    private View.OnClickListener listener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            switch (v.getId()){
                case R.id.start_button:
                    getSupportLoaderManager().initLoader(0, null, new LoaderClass());
                    break;
                case R.id.cancel_button:
                    break;
            }
        }
    };



    @SuppressLint("StaticFieldLeak")
    class AsyncTaskLoaderClass extends AsyncTaskLoader<Void>{

        AsyncTaskLoaderClass(@NonNull Context context) {
            super(context);
        }

        @Nullable
        @Override
        public Void loadInBackground() {
            for (int i = 0; i<10;i++){
                counter.setText(i);
                SystemClock.sleep(500);
            }
            return null;
        }
    }

    private class LoaderClass implements LoaderManager.LoaderCallbacks<Void>{

        @NonNull
        @Override
        public Loader<Void> onCreateLoader(int i, @Nullable Bundle bundle) {
            return new LoaderActivity.AsyncTaskLoaderClass(LoaderActivity.this);
        }

        @SuppressLint("SetTextI18n")
        @Override
        public void onLoadFinished(@NonNull Loader<Void> loader, Void aVoid) {
            counter.setText("Done!");
        }

        @Override
        public void onLoaderReset(@NonNull Loader<Void> loader) {

        }
    }



}

When I run the project I have a runtime error:

java.lang.IllegalArgumentException: Object returned from onCreateLoader must not be a non-static inner member class: AsyncTaskLoaderClass{eed39bf id=0}

Yes, I understand, that it means that AsyncTaskLoaderClass should be in another file or static, but in such case I won't have an opportunity to add text to textview. So, how can I solve this problem?

UPD

I changed the code in clicking start button in such way:

case R.id.start_button:
                Loader loader = getSupportLoaderManager().initLoader(0, null, LoaderActivity.this);
                loader.forceLoad();
                Log.i(TAG, "Button start clicked");
                break;

And now each time in the loop I have the following error:

E/e.com.exercise: Invalid ID 0x00000009.
E/EventBus: Could not dispatch event: class asus.example.com.exercise4.LoaderActivity$MyAsyncTaskLoader$ProgressEvent to subscribing class class asus.example.com.exercise4.LoaderActivity
    android.content.res.Resources$NotFoundException: String resource ID #0x9

UPD 2

Finally fixed the problem in the following way:

Was

counter.setText(i);

Now

counter.setText(""+i);

Probably I don't understrand why it works, but it works

java
android
classloader
greenrobot-eventbus-3.0
asked on Stack Overflow Mar 16, 2019 by Sergei Mikhailovskii • edited Mar 17, 2019 by Sergei Mikhailovskii

3 Answers

1

Make the Activity implement LoaderCallbacks. Also a Loader retrieves one particular value in its onLoadFinished callback, and it should return the retrieved (loaded) item as a result.

To change what value is being loaded by a Loader, you're supposed to restart the loader with a new argument bundle, and pass in the parameters so that it knows what it is doing.

Then again, you are trying to create something like "publishProgress" in AsyncTask; Loaders cannot do that out of the box, and need some variant of "sending an event" (handler threads if you are adventurous, but most likely an event bus, see implementation 'org.greenrobot:eventbus:3.1.1').

TL;DR: use EventBus for this.

public class LoaderActivity extends AppCompatActivity  implements LoaderManager.LoaderCallbacks<Void> {
    private TextView counter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_threads);

        Button startButton = findViewById(R.id.start_button);
        Button cancelButton = findViewById(R.id.cancel_button);
        counter = findViewById(R.id.counter);
        startButton.setOnClickListener((view) -> {
            getSupportLoaderManager().initLoader(0, null, LoaderActivity.this);
        });
        cancelButton.setOnClickListener((view) -> {
            // do nothing, apparently
        });

        EventBus.getDefault().register(this);
    }

    @Override
    protected void onDestroy() {
        EventBus.getDefault().unregister(this);
        super.onDestroy();
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onLoaderProgressEvent(MyAsyncTaskLoader.ProgressEvent event) {
        counter.setText("" + event.getNumber());
    }

    @NonNull
    @Override
    public Loader<Void> onCreateLoader(int i, @Nullable Bundle bundle) {
        return new MyAsyncTaskLoader(LoaderActivity.this);
    }

    @SuppressLint("SetTextI18n")
    @Override
    public void onLoadFinished(@NonNull Loader<Void> loader, Void aVoid) {
        counter.setText("Done!");
    }

    @Override
    public void onLoaderReset(@NonNull Loader<Void> loader) {

    }

    public static class MyAsyncTaskLoader extends AsyncTaskLoader<Void> {
        public static class ProgressEvent {
            private final int number;

            public ProgressEvent(int number) {
                this.number = number;
            }

            public int getNumber() { return number; }
        }

        public MyAsyncTaskLoader(@NonNull Context context) {
            super(context);
        }

        @Nullable
        @Override
        public Void loadInBackground() {
            for (int i = 0; i<10;i++){
                EventBus.getDefault().post(new ProgressEvent(i));
                SystemClock.sleep(500);
            }
            return null;
        }
    }
}
answered on Stack Overflow Mar 16, 2019 by EpicPandaForce • edited Mar 17, 2019 by EpicPandaForce
0

Your are using inner AsyncTaskLoaderClass in Activity class. Inner class holds the reference of Outer class. That means your AsyncTaskLoaderClass may hold Activity reference in some cases. Make your inner class static.

You have 2 solutions. Make AsyncTaskLoaderClass a separate class file or make AsyncTaskLoaderClass a static class.

answered on Stack Overflow Mar 16, 2019 by Abu Yousuf • edited Mar 16, 2019 by Abu Yousuf
-2

make textview public static like this public static TextView counter;

answered on Stack Overflow Mar 16, 2019 by Nice umang

User contributions licensed under CC BY-SA 3.0