-1

I am creating an app, in which one of its activities is used to display images with captions. Those images are displayed using a ViewPager (each image in one page).

I have no problem with creating the images, and displaying them with their captions. However, I am having a problem when I rotate the device. Upon device rotation, the images are deleted, however the captions are retains. I think I know the reason for that (as explained after the code snippets below), but I am unable to solve it, and hence I am asking this question.

First, here is my gallery_item.xml file that is used to display each image:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <ImageView android:id="@+id/gallery_image"
        android:layout_height="fill_parent"
        android:layout_width="fill_parent"
        android:scaleType="fitCenter"
        android:background="@color/black" />

    <TextView android:id="@+id/gallery_caption"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:gravity="right"



        android:textSize="20sp"


        android:paddingStart="@dimen/activity_horizontal_margin"
        android:paddingEnd="@dimen/activity_horizontal_margin"
        android:paddingBottom="@dimen/activity_vertical_margin"

        android:textColor="@color/white"
        android:background="@color/transparent_black_percent_50" />

</FrameLayout>

Then the activity code GalleryItemPagerActivity.java is below

public class GalleryItemPagerActivity extends Activity {

    private ViewPager mViewPager;

    private NewsItem mNewsItem;
    private int mNewsItemId;

    ImageDownloaderThread<ImageView> mImageDownloaderThread;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);


        mViewPager = new ViewPager(this);
        mViewPager.setId(R.id.galleryItemViewPager);
        setContentView(mViewPager);





        // Create a thread that will be run in the background to download images from the web for each URL
        //
        // IMPORTANT NOTE:
        // We are downloading the image in the activity instead of the fragment because we are use a pager activity
        // ... and pager activities must have the adapter in the activity rather than the fragment, which causes us to download the image here
        // ... if we try to download in the fragment, then we will have a very complicated code that will cause too many memory leaks.
        mImageDownloaderThread = new ImageDownloaderThread<ImageView>(new Handler());

        // Prepare listener to update UI when an image is downloaded
        mImageDownloaderThread.setListener(new ImageDownloaderThread.Listener() {

            // This method is used to update the UI when the image ready
            public void onImageDownloaded(Bitmap image, String url, int pos, int download_image_type) {

                // First, get the fragment by position
                GalleryItemFragment fragment = ((GalleryItemFragmentStatePagerAdapter)mViewPager.getAdapter()).getFragment(pos);

                // If the fragment exists
                if (fragment != null) {

                    // Update the image
                    ImageView imageView = (ImageView) fragment.getView().findViewById(R.id.gallery_image);
                    imageView.setImageBitmap(image);

                }

            }
        });

        // Start the background thread
        mImageDownloaderThread.start();

        // Prepare the looper for the background thread
        mImageDownloaderThread.getLooper();



        mNewsItemId = getIntent().getIntExtra(GalleryItemFragment.EXTRA_NEWS_ITEM_ID, 0);
        mNewsItem = NewsItems.get(this).getNewsItem(mNewsItemId);


        FragmentManager fm = getFragmentManager();
        GalleryItemFragmentStatePagerAdapter adapter = new GalleryItemFragmentStatePagerAdapter(fm);
        mViewPager.setAdapter(adapter);

    }




    // Destroy the background thread when we exit the app, otherwise it will stay forever
    @Override
    public void onDestroy() {
        super.onDestroy();
        mImageDownloaderThread.quit();
    }




    // I am subclassing FragmentStatePagerAdapter so I can add the method getFragment()
    // ... This method allows me to get the displayed fragment by position, and if it does not exist, a null is returned.
    private class GalleryItemFragmentStatePagerAdapter extends FragmentStatePagerAdapter {

        // A map to track current fragments.
        private Map<Integer, GalleryItemFragment> mFragmentReferenceMap = new HashMap<Integer, GalleryItemFragment>();


        // Constructor
        public GalleryItemFragmentStatePagerAdapter(FragmentManager fm) {
            super(fm);

        }


        // Implementation of get count.
        @Override
        public int getCount() {

            // The number of images to download is the number of our fragments
            int count = mNewsItem.getImages().size();
            return count;
        }


        // Fragment to display, and download the main image and author avatar
        @Override
        public Fragment getItem(int pos) {

            // Download this image
            String imageUrl = mNewsItem.getImages().get(pos).getUrl();

            // Initiate a request to download the image at the background thread
            mImageDownloaderThread.queueImage(imageUrl, pos, ImageDownloaderThread.DOWNLOAD_IMAGE_TYPE_MAIN);

            // Get the fragment
            GalleryItemFragment fragment = GalleryItemFragment.newInstance(mNewsItemId, pos);

            // Add the fragment to our map
            mFragmentReferenceMap.put(Integer.valueOf(pos), fragment);



            return fragment;

        }


        // When a fragment is deleted, we have to remove it from the map
        @Override
        public void destroyItem(ViewGroup container, int pos, Object object) {


            mFragmentReferenceMap.remove(Integer.valueOf(pos));

            // Remove the fragment from the map
            mFragmentReferenceMap.remove(Integer.valueOf(pos));
        }


        // Get the fragment by position (returns null if fragment does not exist)
        public GalleryItemFragment getFragment(int key) {
            return mFragmentReferenceMap.get(Integer.valueOf(key));
        }


    }


}

And the code for the fragment is right at GalleryItemFragment.java

public class GalleryItemFragment extends Fragment {

    public static final String EXTRA_NEWS_ITEM_ID = "com.myproject.android.news_item_id";
    public static final String EXTRA_GALLERY_ITEM_POSITION = "com.myproject.android.gallery_item_position";


    private int mNewsItemId;
    private int mGalleryItemPosition;

    private TextView mCaptionField;


    // This is used to set attach arguments to the fragment
    public static GalleryItemFragment newInstance (int news_item_id, int gallery_item_position) {
        Bundle args = new Bundle();
        args.putInt(EXTRA_NEWS_ITEM_ID, news_item_id);
        args.putInt(EXTRA_GALLERY_ITEM_POSITION, gallery_item_position);

        GalleryItemFragment fragment = new GalleryItemFragment();
        fragment.setArguments(args);

        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mNewsItemId = getArguments().getInt(EXTRA_NEWS_ITEM_ID);
        mGalleryItemPosition = getArguments().getInt(EXTRA_GALLERY_ITEM_POSITION);

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {

        // inflate our xml file first
        View v = inflater.inflate(R.layout.gallery_item, parent, false);

        mCaptionField = (TextView)v.findViewById(R.id.gallery_caption);
        mCaptionField.setText(NewsItems.get(getActivity()).getNewsItem(mNewsItemId).getImages().get(mGalleryItemPosition).getCaption());

        return v;
    }

}

Now to the problem.

As explained earlier, when I rotate the device, the activity is destroyed, but the fragment is retained (as explained in the accepted answer here).

Also, you notice that the images in my case are downloaded in the activity rather than the fragments (due to using a pager activity, which requires that its adapter be in the activity rather than the fragment).

So when the activity is destroyed, the images are gone (but the captions are retained because they are part of the retained fragments). Which leads to a black page without images, but only captions.

Now, I read different solutions (such as this one), and I tried to add the following line into AndroidManifest.xml

android:configChanges="orientation|screenSize"

But this did not help much, as the images look 'disproportionate' when sliding left and right after rotating the device, because of the screenSize property, which retains the screen size, resulting in funny looking images.

Now, I think I should try onSaveInstanceState() but I am not sure how to do so. How can I save an image and retain it using onSaveInstanceState()? And how about other variables in my activity class (such as mViewPager), do I have to save them?

Thanks.

Community
  • 1
  • 1
Greeso
  • 7,544
  • 9
  • 51
  • 77

2 Answers2

1

See, its as per design that activities or fragment will get destroyed whenever configuration of device is changed, however if you want to handle them yourselves then android do provide you facilities for the same with APIS like

onSaveInstanceState() -- you can save and restore the instance stae
onConfigurationChanged() -- you can handle the configuration changes which you have declared to handle yourself, like in your case "orientation|screenSize"

Additional to this, you can use setRetainInstance API in fragment to hold objects in fragments when activity is destroyed you can read about it here, however this api do mention not to hold objects like Bitmaps.

Now coming to your problem, this is what you should do.

  1. Dont handle any configuration changes yourself, let android handle it for you
  2. Hold any objects you want to retain, when orientation changes apart then bitmap, by above provided information.
  3. Now to handle downloaded images efficiently, implement a FileChache and before downloading images check if image is already downloaded, and use the same instead of downloading it again,this will make sure when configuration is changed, you dont get black spot for already doanloaded image.

you can check it here in my guthub link.

Techfist
  • 4,314
  • 6
  • 22
  • 32
0

Your problem is relod on onconfiguration changed, in manifest file give permission to that activity file like this:

android:configChanges="orientation|keyboard"
VINIL SATRASALA
  • 614
  • 5
  • 11