0

I implemented a custom Android camera using the camera2 api and when I click capture button I save the captured image to my phone eternal storage.

Now I needed to show a popup to enter the picture name before saving it and this popup has an imageView to show the captured image.

Even though I am loading the image from the Main thread I am getting this error

  "Only the original thread that created a view hierarchy can touch its views"

Here is the code

  private void initPopup(final Bitmap bitmap) {

    popAddName = new Dialog(this);
    popAddName.setContentView(R.layout.popup_add_name);
    popAddName.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
    popAddName.getWindow().setLayout(Toolbar.LayoutParams.MATCH_PARENT, Toolbar.LayoutParams.WRAP_CONTENT);
    popAddName.getWindow().getAttributes().gravity = Gravity.TOP;

    popup_add = popAddName.findViewById(R.id.popup_add);
    popup_img = popAddName.findViewById(R.id.popup_img);
    popup_name = popAddName.findViewById(R.id.popup_name);

    Thread thread = new Thread() {
        @Override
        public void run() {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Picasso.with(getApplicationContext()).load(getImageUri(getApplicationContext(), bitmap)).placeholder(R.drawable.placeholder).into(popup_img);
                }
            });

        }
    };
    thread.start();
}

Here is the method to convert Bitmap to Uri for Picasso loading

  public Uri getImageUri(Context inContext, Bitmap inImage) {
    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    inImage.compress(Bitmap.CompressFormat.JPEG, 100, bytes);
    String path = MediaStore.Images.Media.insertImage(inContext.getContentResolver(), inImage, "Title", null);
    return Uri.parse(path);
}

This is the capture method

    ImageReader.OnImageAvailableListener readerListener = new ImageReader.OnImageAvailableListener() {
            @Override
            public void onImageAvailable(ImageReader imageReader) {
                Image image = null;
                try {
                    image = reader.acquireLatestImage();
                    ByteBuffer buffer = image.getPlanes()[0].getBuffer();
                    byte[] bytes = new byte[buffer.capacity()];
                    buffer.get(bytes);
                    //CONVERTION BYTES[] TO BITMAP
                    Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);

                    //create a new folder in external storage directory
                    String lorealFolder = "coffretPics";
                    File f = new File(Environment.getExternalStorageDirectory(), lorealFolder);
                    if (!f.exists()) {
                        f.mkdirs();
                    }

                    initPopup(bitmap);
                    popAddName.show();

                    file = new File(Environment.getExternalStorageDirectory() + "/" + lorealFolder + "/" + UUID.randomUUID().toString() + ".jpg");


                    save(bytes);


                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    {
                        if (image != null)
                            image.close();
                    }
                }
            }

            private void save(byte[] bytes) throws IOException {
               /* OutputStream outputStream = null;
                try {
                    outputStream = new FileOutputStream(file);
                    outputStream.write(bytes);
                } finally {
                    if (outputStream != null) {
                        outputStream.close();
                    }
                }*/
            }
        };

Can someone please tell me what's the problem ?

Amine
  • 2,241
  • 2
  • 19
  • 41
  • 1
    You have to post a message to the UI thread and change the UI from there. There's a variety of ways to do this (runOnUIThread if you're in an activity, using a Handler to the main thread and posting a message to it, using rxjava or similar libraries, etc). Pick your favorite. – Gabe Sechan Mar 15 '19 at 15:12
  • In your case, the callback is being run on another thread because its being called from the thread it used to network. So you'll need to post to the ui thread there. Generally you want ot do all the processing on the network thread, and then do just the ui changes on the ui thread. – Gabe Sechan Mar 15 '19 at 15:14
  • Possible duplicate of [Android "Only the original thread that created a view hierarchy can touch its views."](https://stackoverflow.com/questions/5161951/android-only-the-original-thread-that-created-a-view-hierarchy-can-touch-its-vi) – Samuel Philipp Mar 15 '19 at 15:14
  • @GabeSechan Can you post an example and I will be happy to accept it. Thank you anyway Sir. – Amine Mar 15 '19 at 15:14
  • @SamuelPhilipp It's not because I'm already trying that solution and it's not working. – Amine Mar 15 '19 at 15:15
  • Just put your `Picasso.with` line out of both threads. It should solve your issue – Niki van Stein Mar 15 '19 at 15:22
  • @BasvanStein It didn't solve the problem. – Amine Mar 15 '19 at 15:48

1 Answers1

1

Updated answer:

Create a class called AppExecutors

public class AppExecutors {
    // For Singleton instantiation
    private static final Object LOCK = new Object();
    private static AppExecutors sInstance;
    private final Executor diskIO;
    private final Executor mainThread;
    private final Executor networkIO;

    private AppExecutors(Executor diskIO, Executor networkIO, Executor mainThread) {
        this.diskIO = diskIO;
        this.networkIO = networkIO;
        this.mainThread = mainThread;
    }

    public static AppExecutors getInstance() {
        if (sInstance == null) {
            synchronized (LOCK) {
                sInstance = new AppExecutors(Executors.newSingleThreadExecutor(),
                        Executors.newFixedThreadPool(3),
                        new MainThreadExecutor());
            }
        }
        return sInstance;
    }

    public Executor diskIO() {
        return diskIO;
    }

    public Executor mainThread() {
        return mainThread;
    }

    public Executor networkIO() {
        return networkIO;
    }

    private static class MainThreadExecutor implements Executor {
        private Handler mainThreadHandler = new Handler(Looper.getMainLooper());

        @Override
        public void execute(@NonNull Runnable command) {
            mainThreadHandler.post(command);
        }
    }

And get the main thread like:

        AppExecutors.getInstance().mainThread().execute(new Runnable() {
        @Override
        public void run() {
            // Do the work on UI thread here.
        }
    });

That should fix your problem.

Tahir Ferli
  • 636
  • 4
  • 16