Camera not overriding old image if orientation changes - android

I have a RecyclerView adapter with many different ViewHolders. One of the ViewHolders contains an ImageView, which needs to be able to take a picture, resize it, then display it. For modularity, I want the ViewHolder to be self-contained: it and not the parent activity should handle everything concerning the photo taking and displaying process. Also the file path is constant (it will never change). In fact, it is /storage/emulated/0/com.company.app/myst/cat.jpg. As a result, here is my implementation of the ImageView’s onClick method.
#Override
public void onClick(View v) {
final FragmentManager fm = ((MyActivity) getContext()).getSupportFragmentManager();
Fragment auxiliary = new Fragment() {
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
resizeResaveAndDisplayPhoto();
super.onActivityResult(requestCode, resultCode, data);
fm.beginTransaction().remove(this).commit();
}
};
fm.beginTransaction().add(auxiliary, "FRAGMENT_TAG").commit();
fm.executePendingTransactions();
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (null != takePictureIntent.resolveActivity(view.getContext().getPackageManager())) {
((MyActivity)view.getContext()).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));
auxFragment.startActivityForResult(takePictureIntent, Constants.REQUEST_CODE_PHOTO);
}
}
When resizeResaveAndDisplayPhoto is called it executes the following AsyncTask
public static class ResizeThenLoadImageTask extends AsyncTask<String, Void, Bitmap> {
private final WeakReference<ImageView> imageViewWeakReference;
private final WeakReference<File> fileWeakReference;
private final WeakReference<Context> weakContext;
private final int reqHeight;
private final int reqWidth;
public ResizeThenLoadImageTask(Context context, ImageView imageView, File file, int reqHeight, int reqWidth) {
weakContext = new WeakReference<Context>(context);
imageViewWeakReference = new WeakReference<>(imageView);
fileWeakReference = new WeakReference(file);
this.reqHeight = reqHeight;
this.reqWidth = reqWidth;
}
#Override
public Bitmap doInBackground(String... params) {
File file = fileWeakReference.get();
Bitmap bitmap = null;
if (null != file) {
bitmap = ImageUtils.reduceImageSize(file, reqHeight, reqWidth);
ImageUtils.saveBitmapToGivenFile(bitmap, file);
}
return bitmap;
}
#Override
public void onPostExecute(Bitmap bitmap) {
if (null != imageViewWeakReference && null != fileWeakReference) {
ImageView imageView = imageViewWeakReference.get();
File file = fileWeakReference.get();
if (null != imageView) {
if (null != bitmap) {
imageView.setImageBitmap(bitmap);
}
else {
imageView.setImageResource(R.drawable.photo);
}
imageView.postDelayed(new Runnable() {
#Override
public void run() {
if (null != weakContext.get()) {
((MyActivity) weakContext.get()).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
}
}
}, 10000);
}
}
}
}
You may notice that I lock the orientation before taking the photo and unlock it 10 seconds after displaying the photo. That trick is part of my troubleshooting. So here is the situation.
The system described above works very well. Problems happen in the following case
Say I already have a photo in the ImageView but want to replace it.
So I click on the ImageView to take a new photo.
If I rotate the device to take the new photo, then when I return the new photo displays briefly before the old photo returns.
So I lock to orientation to see what was happening. Here is what I found.
The new photo displays for as long as I lock the orientation. As soon as the orientation unlocks (10 sec), the old photo returns.
If I leave the activity and the returns, the old photo is still displaying.
If I close the app completely and then return, then I see the new photo.

When the device is rotated, the running activity is recreated as well the asyncTask attached to it, probably causing a leak.
You probably need to call the asyncTask inside of a service instead in an activity so the asyncTask is going to be attached to the life cycle of the service.

Related

Restore listener after Activity recreate (pass photo to a custom view returning from camera)

I have an activity with a fragment. Inside a fragment I have 2 custom views. A custom view contains an ImageView and a listener. Listener can transform, show a photo in ImageView and upload the photo.
I want to take a photo from camera, return it to the fragment, pass to a view through listener (then show and upload inside the view). Everything works right until the activity is destroyed after camera becomes visible. So, after returning from camera, I restore the fragment, get photo in onActivityResult and try to pass to a view by listener. But a listener is null, and i don't know what view it is attached to.
How can I pass a photo to a view after recreating an activity?
Listener:
public interface Listener {
void onPhotoObtained(#Nullable Uri uri);
}
Custom view:
public class CustomView extends RelativeLayout implements Listener {
#BindView(R.id.imageview) ImageView image;
private PhotoManager photoManager;
public void setPhotoManager(#NonNull PhotoManager photoManager) {
this.photoManager = photoManager;
}
#Override
public void onPhotoObtained(#Nullable Uri uri) {
// transform and show image
}
#OnClick(R.id.imageview)
void onPhotoButtonClicked() {
photoManager.requestPhoto(this);
}
}
Fragment:
public class MainFragment extends Fragment implements PhotoManager {
#BindView(R.id.view1) CustomView view1;
#BindView(R.id.view2) CustomView view2;
// A list of listeners to communicate with custom views.
// When a user clicks an ImageView, this fragment starts a camera to obtain a photo.
private SparseArray<Listener> listeners;
private int lastRequestId;
private Uri uri;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_main, container, false);
binder = ButterKnife.bind(this, view);
listeners = new SparseArray<>();
if (savedInstanceState == null) {
lastRequestId = 0;
uri = null;
} else {
lastRequestId = savedInstanceState.getInt(BUNDLE_REQUEST_ID);
uri = savedInstanceState.getParcelable(BUNDLE_KEY_URI);
// How to create a listener list?
}
view1.setPhotoManager(this);
view2.setPhotoManager(this);
return view;
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == ReceivingPhotoDialogFragment.CAMERA_REQUEST) {
if (resultCode == RESULT_OK) {
if (uri != null) {
// listeners become empty after fragment reinitialization
Listener listener = listeners.get(lastRequestId);
if (listener != null)
listener.onPhotoObtained(uri);
}
}
}
}
#Override
public void onSaveInstanceState(#NonNull Bundle outState) {
bundle.putInt(BUNDLE_REQUEST_ID, lastRequestId);
bundle.putParcelable(BUNDLE_KEY_URI, uri);
super.onSaveInstanceState(outState);
}
#Override
public void requestPhoto(#NonNull Listener listener) {
listeners.put(++lastRequestId, listener);
// Request new photo with lastRequestId
showCamera(lastRequestId);
}
private void showCamera(int requestId) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (intent.resolveActivity(getContext().getPackageManager()) != null) {
File file = null;
try {
file = createImageFile();
} catch (IOException e) {
e.printStackTrace();
}
uri = null;
if (file != null) {
uri = Uri.fromFile(file);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
intent.putExtra(BUNDLE_REQUEST_ID, requestId);
startActivityForResult(intent, CAMERA_REQUEST);
}
}
}
private File createImageFile() throws IOException {
// Create an image file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
File storageDir = getContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES);
return File.createTempFile(timeStamp, ".jpg", storageDir);
}
}
A second listener to initialize first:
public interface PhotoManager {
void requestPhoto(#NonNull Listener listener);
}
I still struggle with the activity lifecycle, so this may not be the best answer.
What I do is make my listener as a static variable. This allows the variable to exist in the class instead of the instance of the class which clears when destroyed.
I think, You do not need to re-initialise listener for that,
What you need is add a property named configChanges in your AndroidManifest.xml file for that activity which has this MainFragment
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:screenOrientation="portrait"/>
Add these tag configChanges and then run your code.

RecyclerView and async Loading

I have a slight problem. Need a nudge in the right direction.
I am doing a video editor like Vine and/or instagram. Where they show a timeline with screencaps from the video
It just adds more pictures depending on the videos duration. What i did for my app is that i added a recyclerView. This recyclerview has an adapter that calls the following function every time onBindViewHolder
public Bitmap getFrameFromCurrentVideo(int seconds) {
Bitmap bitmap = null;
if(mMediaMetadataRetriever != null) {
bitmap = mMediaMetadataRetriever.getFrameAtTime(seconds * 1000000, MediaMetadataRetriever.OPTION_CLOSEST);
}
return bitmap;
}
This works and it adds the proper amount of images that i want. But the problem is that it is too heavy on the UI thread. Since the recyclerView is recycling everything. It then lags up every time it has to get a frame.
So i thought that i have to do some async task and then cache the images. But what i read is that AsyncTask is not recommended for recycler views since it recycles.
So what should i do to enchance the performance? Any good idea?
This is what i did to solve my problem.
I created async task and memory cache my result.
My adapter checks if the image already exist. If it does. Then we skip doing the background work. Otherwise i do the async task and try to load the image. We also tag the view just in case the user scrolls while the task is not finished.
This helps us check if the Tag is different from what the task have. If it is the same. Then we can safely put the right image in the imageview.
Snippet from my adapter
#Override
public void onBindViewHolder(PostVideoRecyclerViewHolder holder, int position) {
holder.mImageView.getLayoutParams().width = mScreenWidth / mMaxItemsOnScreen;
holder.mImageView.setImageDrawable(null);
int second = position * 3 - 3;
String TAG = String.valueOf(second);
holder.mImageView.setTag(TAG);
Bitmap bitmap = mFragment.getBitmapFromMemCache(TAG);
if(bitmap == null) {
PostVideoBitmapWorkerTask task = new PostVideoBitmapWorkerTask(holder.mImageView, TAG, mFragment);
task.execute(second);
}
else {
holder.mImageView.setImageBitmap(bitmap);
}
}
My AsyncTask class
public class PostVideoBitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
private ImageView mImageView;
private String TAG;
private PostVideoFeedFragment mFragment;
public PostVideoBitmapWorkerTask(ImageView imageView, String TAG, PostVideoFeedFragment fragment) {
mImageView = imageView;
this.TAG = TAG;
mFragment = fragment;
}
#Override
protected Bitmap doInBackground(Integer... params) {
Bitmap bitmap = mFragment.getFrameFromCurrentVideo(params[0]);
mFragment.addBitmapToCache(TAG,bitmap);
return bitmap;
}
#Override
protected void onPostExecute(Bitmap bitmap) {
if(mImageView.getTag().toString().equals(TAG)) {
mImageView.setImageBitmap(bitmap);
}
}
}
Snippet from my fragment class
public void addBitmapToCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
I can recommend https://developer.android.com/training/displaying-bitmaps/cache-bitmap.html if you want to read up on caching
And also https://developer.android.com/reference/android/os/AsyncTask.html for reading up on asyncTask's

I have RecyclerView which populates the feeds. In top of the recyclerview there is portion for feeds to be inserted

I have RecyclerView which populates the feeds. In top of the RecyclerView there is portion for feeds to be posted like status and photos from the gallery.
To post status I made Adapter which extends RecyclerView.Adapter. In this adapter I can perform some onClickListener. But the problem I'm facing is with choosing image from gallery. How can I choose Image from gallery with the button click inside Adapter?
Below method can be used for this purpose but it works in Activity, but I need this method in Adapter because the onClickListener is inside the ViewHolder in Adapter.
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == PICK_IMAGE_REQUEST && resultCode == RESULT_OK && data != null && data.getData() != null) {
filePath = data.getData();
try {
bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), filePath);
imageView.setImageBitmap(bitmap);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Any Help will be very appropriated.
Either your Activity can hold a reference of your adapter and you create a method to call in your Adapter, or (and I think it's best) broadcast an event from your activity containing the path and listen to it with your adapter.
You can use LocalBroadcastManager to send your event (via an Intent), with the path as extra. And add a BroadcastReceiver to your Adapter to listen to it using onAttachedToRecyclerView to add your receiver, and onDetachedFromRecyclerView to remove it.
Then you only have to bind your button like and image like that:
private Bitmap bitmap; // This variable will store your bitmap data
#Override
public void onBindViewHolder(final RecyclerViewAdapter.ViewHolder holder, final int position)
{
// Do that only if it's the post view
holder.uploadButton.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v)
{
// TODO start your media selection view here
}
});
if( bitmap != null )
{
holder.imageView.setImageBitmap(bitmap);
}
}
And when you get the callback from the BroadcastReceiver, just get the image like you would do and store it as a data:
#override
public void onReceive (Context context, Intent intent)
{
Uri filePath = getThePathFromTheIntent(intent); // TODO implement that one
bitmap = MediaStore.Images.Media.getBitmap(context.getContentResolver(), filePath);
}
Don't forget to clear the bitmap when done use it for memory management purpose.

Image on ImageView lost after Activity is destroyed

I am trying to make an app where I can let a user select a picture to display on their profile. I am able to browse and set their selected image on imageview. But the image is lost once the the activity is destroyed. I tried to implement onSaveInstanceState but still it's the same. I'm wondering if I am using it correctly. I hope you can help a newbie like me. Thanks in advance. Here's the code that I'm using:
public class AccountFragment extends Fragment implements OnClickListener {
private LoginDataBaseAdapter loginDataBaseAdapter;
Bitmap image;
Bitmap bitmap;
String picture_location;
TextView textTargetUri;
ImageView targetImage;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_account, container, false);
textTargetUri = (TextView) rootView.findViewById(R.id.targeturi);
targetImage=(ImageView) rootView.findViewById(R.id.profpic);
targetImage.setOnClickListener(new ImageView.OnClickListener(){
#Override
public void onClick(View arg0) {
Intent intent = new Intent(Intent.ACTION_PICK,
android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(intent, 0);
}});
if (savedInstanceState != null) {
//if there is a bundle, use the saved image resource (if one is there)
image = savedInstanceState.getParcelable("BitmapImage");
targetImage.setImageBitmap(image);
textTargetUri.setText(savedInstanceState.getString("path_to_picture"));
}
return rootView;
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState){
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putParcelable("BitmapImage", bitmap);
savedInstanceState.putString("path_to_picture", picture_location);
}
#Override
public void onActivityResult( int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == Activity.RESULT_OK){
Uri targetUri = data.getData();
picture_location = targetUri.toString();
textTargetUri.setText(targetUri.toString());
Bitmap bitmap;
try {
bitmap = BitmapFactory.decodeStream(getActivity().getContentResolver().openInputStream(targetUri));
targetImage.setImageBitmap(bitmap);
}
catch (FileNotFoundException e){
e.printStackTrace();
}
}
}
#Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
}}
By the way, you may have noticed that instead of using the onRestoreInstanceState after oncreate, I tried to use the different approach. I found an answer from another question that you can also implement it inside the oncreate. I used it since whenever I declare the function onRestoreInstanceState I am being asked to remove the #Override annotation.
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState){
image = savedInstanceState.getParcelable("BitmapImage");
targetImage.setImageBitmap(image);
textTargetUri.setText(savedInstanceState.getString("path_to_picture"));
}
Using onSaveInstanceState and onCreate/onRestoreInstanceState is for short term activity state preservation - but not to be used for persistent storage of the application's data.
You can read about onSaveInstanceState here
You can read about persistent storage here
codeMagic suggested using SharedPrefs (see persistent storage link) for your long-term persistent storage. If you wanted to do this, I would suggest saving the image URI (the link has a good example of how to do so) in you onActivityResult method, and then call a method to read the SharedPref and load the image that you can call from onCreate as well as from onActivityResult.
You may also want to store your own copy of the image/bitmap in your application's own internal storage (see persistent storage link).
if you are not finishing the activity, you can use onSavedInstance() to store the picture_location value and bind it back either in onCreate(SavedInst)/onRestore() from the picture_location value.
In case of Bitmaps Instance State is not the suggested way to persist info about the selected image.
You can find the explanation here: Handling configuration Changes
I blogged extensively about it here: Retain selected Image during Screen Rotation
Below I paste my implementation of the illustrated solution:
1 - Create a Fragment and configure it to be retained in memory
import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.v4.app.Fragment;
public class ImageRetainingFragment extends Fragment {
private Bitmap selectedImage;
#Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// retain this fragment
setRetainInstance(true);
}
public void setImage(Bitmap selectedImage) {
this.selectedImage = selectedImage;
}
public Bitmap getImage() {
return this.selectedImage;
}
}
2 - Use it in your Activity
private static final String FRAGMENT_NAME = "imageFragment";
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
....
initializeImageRetainingFragment();
tryLoadImage();
}
private void initializeImageRetainingFragment() {
// find the retained fragment on activity restarts
FragmentManager fragmentManager = getSupportFragmentManager();
this.imageRetainingFragment = (ImageRetainingFragment) fragmentManager.findFragmentByTag(FRAGMENT_NAME);
// create the fragment and bitmap the first time
if (this.imageRetainingFragment == null) {
this.imageRetainingFragment = new ImageRetainingFragment();
fragmentManager.beginTransaction()
// Add a fragment to the activity state.
.add(this.imageRetainingFragment, FRAGMENT_NAME)
.commit();
}
}
private void tryLoadImage() {
if (this.imageRetainingFragment == null) {
return;
}
Bitmap selectedImage = this.imageRetainingFragment.getImage();
if (selectedImage == null) {
return;
}
ImageView selectedImageView = (ImageView)findViewById(R.id.selectedImage);
selectedImageView.setImageBitmap(selectedImage);
}
1)// manifest.xml
2)//public class MainActivity extends AppCompatActivity implements LocationListener{...
SharedPreferences.Editor editor;
3)//protected void onCreate(Bundle savedInstanceState){ ...
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) !=
PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 3);
}
4)//SHARED PREFERENCES
SharedPreferences pref = getApplicationContext().getSharedPreferences("MyPref", getApplicationContext().MODE_PRIVATE);
editor = pref.edit();
5)//SET IMAGE PATH
if (pref.getString("mydraw", null) != null) {
img6.setImageURI(Uri.parse(pref.getString("mydraw", null)));
} else {
//set default image
img6.setImageResource(R.drawable.poseidon);
}
6)//protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {...
if (requestCode == 100) {
if (resultCode == RESULT_OK) {
img6.setImageURI(data.getData());
//save URI as string
editor.putString("mydraw", data.getData().toString());
editor.commit(); // commit changes
}
}

Experiencing choppyness when loading Bitmaps into ImageViews from AsyncTask. Looking for improvement pointers

I'm experiencing some choppyness when loading bitmaps into ImageViews from an AsyncTask.
I have a fragment in which I display information loaded from SQLite, and said information often times have photos attached. When my fragment is launched, it takes up to a second before it is displayed (the app appears to hang for a bit),
probably because of the heavy data loading, which tells me I might be doing something wrong.
Here is a stripped down version of my implementation:
public class InformationFragment extends Fragment {
private ArrayList<MyPhoto> mPhotos;
private LinearLayout mPhotoContainer;
private View mView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// ....
mView = inflater.inflate(R.layout.fragment_information, container, false);
mPhotoContainer = (LinearLayout) mView.findViewById(R.id.fragment_information_photos_container);
return mView;
}
#Override
public void onResume() {
super.onResume();
loadInformation();
}
private void loadInformation() {
// Loads information from database and puts it into TextViews and such.
// Relatively performance heavy operations, should perhaps run off main, but it's not the cause
// of my problems as it was pretty smooth before I implemented photo attachments
}
private ArrayList<MyPhoto> getPhotos() {
// Loads photos from database. Not sure but could be pretty performance heavy, you tell me :)
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if(mPhotoContainer.getChildCount() == 0) {
mPhotos = getPhotos();
for (MyPhoto p : mPhotos) {
addImageViewForPhoto(p);
}
}
}
private void addImageViewForPhoto(MyPhoto p) {
final ImageView iv = new ImageView(getActivity());
mPhotoContainer.addView(iv);
new MyPhotoLoaderTask(iv).execute(p.getBytes());
}
}
The following is the MyPhotoLoaderTask class
public class MyPhotoLoaderTask extends AsyncTask<byte[], Void, Bitmap> {
private final WeakReference<ImageView> mWeakImageView;
public MyPhotoLoaderTask(ImageView iv) {
mWeakImageView = new WeakReference<ImageView>(iv);
}
#Override
protected Bitmap doInBackground(byte[]... params) {
return MyPhotoUtils.createBitmap(params[0], 100, 100);
}
#Override
protected void onPostExecute(final Bitmap result) {
if(mWeakImageView != null && result != null) {
final ImageView iv = mWeakImageView.get();
iv.setImageBitmap(result);
}
}
}
And lastly, the MyPhotoUtils.createBitmap() method
public static Bitmap createBitmap(byte[] bytes, int reqWidth, int reqHeight) {
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(bytes, 0, bytes.length, opts);
opts.inSampleSize = getInSampleSize(opts, reqWidth, reqHeight);
opts.inJustDecodeBounds = false;
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, opts);
}
I don't mind the fact that loading the images takes a while, but I would love for the fragment to load with all of the text information already present,
and then have the images appear one by one after the user has begun interaction with the fragment. The current situation is pretty bad in my opinion, and I fear it will become even worse if the user will attach even more photos to the information.

Categories

Resources