2

I'm creating an app where the user has the possibility to copy the text to clipboard for 30 seconds. After 30 seconds the text needs to be removed from clipboard, even if the app has been closed

In my main activity I have the following code:

OneTimeWorkRequest worker = new OneTimeWorkRequest.Builder(clipboardWorker.class)
                .setInitialDelay(30, TimeUnit.SECONDS).build();
        WorkManager.getInstance().enqueue(worker);

The clipboardWorker looks like this:

public class clipboardWorker extends Worker {
    private static final String TAG = "clipboardWorker";

    public clipboardWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }

    @NonNull
    @Override
    public Result doWork() {

        ClipboardManager clipboardManager = (ClipboardManager) getApplicationContext().getSystemService(Context.CLIPBOARD_SERVICE);
        ClipData clip = ClipData.newPlainText("clp", "");
        clipboardManager.setPrimaryClip(clip);

        return Result.success();
    }
}

This works on Android version 28 but on 27 it throws the following error:

Caused by: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

Here is the stack trace:

2019-04-07 11:41:15.948 18924-18944/com.example.clipboard E/WM-WorkerWrapper: Work [ id=fa06342b-0130-4562-b323-5871ea0e67fb, tags={ com.example.clipboard.clipboardWorker } ] failed because it threw an exception/error
    java.util.concurrent.ExecutionException: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
        at androidx.work.impl.utils.futures.AbstractFuture.getDoneValue(AbstractFuture.java:516)
        at androidx.work.impl.utils.futures.AbstractFuture.get(AbstractFuture.java:475)
        at androidx.work.impl.WorkerWrapper$2.run(WorkerWrapper.java:289)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
        at java.lang.Thread.run(Thread.java:764)
     Caused by: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
        at android.os.Handler.<init>(Handler.java:204)
        at android.os.Handler.<init>(Handler.java:118)
        at android.content.ClipboardManager$2.<init>(ClipboardManager.java:62)
        at android.content.ClipboardManager.<init>(ClipboardManager.java:62)
        at android.app.SystemServiceRegistry$11.createService(SystemServiceRegistry.java:254)
        at android.app.SystemServiceRegistry$11.createService(SystemServiceRegistry.java:252)
        at android.app.SystemServiceRegistry$CachedServiceFetcher.getService(SystemServiceRegistry.java:962)
        at android.app.SystemServiceRegistry.getSystemService(SystemServiceRegistry.java:914)
        at android.app.ContextImpl.getSystemService(ContextImpl.java:1667)
        at android.content.ContextWrapper.getSystemService(ContextWrapper.java:714)
        at com.example.clipboard.clipboardWorker.doWork(clipboardWorker.java:26)
        at androidx.work.Worker$1.run(Worker.java:85)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162) 
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636) 
        at java.lang.Thread.run(Thread.java:764) 

The error is thrown here:

ClipboardManager clipboardManager = (ClipboardManager) getApplicationContext().getSystemService(Context.CLIPBOARD_SERVICE);

Below are the gradle plugins:

    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
    implementation 'com.google.android.material:material:1.0.0'
    implementation 'androidx.work:work-runtime:2.0.0'
John
  • 23
  • 5
  • Hmm, yep, you're right. Prior to API level 28, `ClipboardManager` instantiates a `Handler` in a field initializer, which is why that throws when you try to get that service in `doWork()`. From 28 on, it's handed a `Handler` via its constructor. Try making `clipboardManager` a field – e.g., `private ClipboardManager clipboardManager;` – and getting in your `clipboardWorker`'s constructor, after the `super` call – `clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);`. – Mike M. Apr 07 '19 at 09:03
  • I mean, like this: https://drive.google.com/file/d/1Rh6nH4AzyEI_2f1rhmauwHBKWbrog2c2/view?usp=drivesdk. Sorry, I tried to abbreviate things in my comment, and just made it confusing. – Mike M. Apr 07 '19 at 09:20
  • Hi Mike, thanks for answering. I've change the code exactly like you but it seems that the error persists. – John Apr 07 '19 at 09:24
  • OK, I wasn't sure if `Worker`s were instantiated on the main thread. Apparently, they're not. Check that link again, and try that. – Mike M. Apr 07 '19 at 09:33
  • It works, thank you. – John Apr 07 '19 at 09:44

1 Answers1

1

That's because The Worker Class of the WorkManager Api run on a Background Thread,

In the Worker Class you could try to declare an Handler attached to the Main Thread :

Handler mainThreadHandler = new Handler(Looper.getMainLooper());

Then use the Handler's post() Method to run your ClipBoard Code

User One
  • 455
  • 3
  • 11