Search code examples
androidparse-platformandroid-listfragmentcustom-adapterandroid-parsequeryadapter

Grow heap (frag case) on ListView Fragment using custom ParseQueryAdapter on fast scrolling


I am using Parse.com android sdk. Inside my TabsActivity.java, I have a SearchFragment which extends ListFragment in order to populate a ListView with a Custom ParseQueryAdapter. Inside my custom adapter I declare a custom list row layout called search_list_item.xml. This layout contains only a ParseImageView.

My problem is that when I fast scroll down the list my logcat gets full of

I/dalvikvm-heap﹕ Grow heap (frag case) to ...MB for byte allocation

and the the listView returns to the initial position(that means it gets back to the first item). If on the other hand I slowly scroll the list I can get at the end of the items without this error. How could I fix this??

Additionally, if I use the default ParseQueryAdapter without customizing the rows with search_list_item.xml I don't have such a problem.

Below I post some code which I think will be useful:

The code of search_list_item.xml

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <com.parse.ParseImageView
        android:id="@+id/ProfileImage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />



</RelativeLayout>

The code of TabsActivity.Java

public class TabsActivity extends Activity implements SearchFragment.OnFragmentInteractionListener, ActionBar.TabListener {

    /**
     * The {@link android.support.v4.view.PagerAdapter} that will provide
     * fragments for each of the sections. We use a
     * {@link FragmentPagerAdapter} derivative, which will keep every
     * loaded fragment in memory. If this becomes too memory intensive, it
     * may be best to switch to a
     * {@link android.support.v13.app.FragmentStatePagerAdapter}.
     */
    SectionsPagerAdapter mSectionsPagerAdapter;

    /**
     * The {@link ViewPager} that will host the section contents.
     */
    ViewPager mViewPager;

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

        // Set up the action bar.
        final ActionBar actionBar = getActionBar();
        actionBar.setLogo(R.drawable.logo_white);
        actionBar.setDisplayShowTitleEnabled(false);
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

        // Create the adapter that will return a fragment for each of the three
        // primary sections of the activity.
        mSectionsPagerAdapter = new SectionsPagerAdapter(getFragmentManager());

        // Set up the ViewPager with the sections adapter.
        mViewPager = (ViewPager) findViewById(R.id.tabs_pager);
        mViewPager.setAdapter(mSectionsPagerAdapter);

        // When swiping between different sections, select the corresponding
        // tab. We can also use ActionBar.Tab#select() to do this if we have
        // a reference to the Tab.
        mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
            @Override
            public void onPageSelected(int position) {
                actionBar.setSelectedNavigationItem(position);
            }
        });

        // For each of the sections in the app, add a tab to the action bar.

        actionBar.addTab(
                actionBar.newTab()
                        .setText("Search")
                        .setTabListener(this));

        actionBar.addTab(
                actionBar.newTab()
                        .setText("Secind")
                        .setTabListener(this));

        actionBar.addTab(
                actionBar.newTab()
                        .setText("Third")
                        .setTabListener(this)
        );

        actionBar.addTab(
                actionBar.newTab()
                        .setText("Fourth")
                        .setTabListener(this));


    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.tabs, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {

        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
        // When the given tab is selected, switch to the corresponding page in
        // the ViewPager.
        mViewPager.setCurrentItem(tab.getPosition());
    }

    @Override
    public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
    }

    @Override
    public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
    }

    /**
     * A {@link FragmentPagerAdapter} that returns a fragment corresponding to
     * one of the sections/tabs/wizard1.
     */
    public class SectionsPagerAdapter extends FragmentPagerAdapter {

        public SectionsPagerAdapter(FragmentManager fm) {
            super(fm);
        }


        @Override
        public Fragment getItem(int index) {

            switch (index) {
                case 0:
                    // Search fragment activity
              return new SearchFragment();
                case 1:
                    // Flirts fragment activity
                    return new SecondFragment();
                case 2:
                    // Explore fragment activity
                    return new ThirdFragment();
                case 3:
                    //Profile fragment activity
                    return new FourthFragment();
        }

        return null;
    }


        @Override
        public int getCount() {
            // Show 4 total wizard1.
            return 4;
        }

    }


    public void onFragmentInteraction(String id) {
        // The user selected the headline of an article from the HeadlinesFragment
        // Do something here to display that article
    }

}

The code of SearchFragment.java

public class SearchFragment extends ListFragment {

    private OnFragmentInteractionListener mListener;
    private CustomDogAdapter mainAdapter;

    /**
     * Mandatory empty constructor for the fragment manager to instantiate the
     * fragment (e.g. upon screen orientation changes).
     */
    public SearchFragment() {
    }

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


        mainAdapter = new CustomAdapter(this.getActivity());


        // Set the ListActivity's adapter to be the PQA
        mainAdapter.setAutoload(false);
        mainAdapter.loadObjects();
        setListAdapter(mainAdapter);


    }



    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mListener = (OnFragmentInteractionListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString()
                + " must implement OnFragmentInteractionListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }


    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        super.onListItemClick(l, v, position, id);

        if (null != mListener) {
            // Notify the active callbacks interface (the activity, if the
            // fragment is attached to one) that an item has been selected.
        }
    }

    /**
    * This interface must be implemented by activities that contain this
    * fragment to allow an interaction in this fragment to be communicated
    * to the activity and potentially other fragments contained in that
    * activity.
    * <p>
    * See the Android Training lesson <a href=
    * "http://developer.android.com/training/basics/fragments/communicating.html"
    * >Communicating with Other Fragments</a> for more information.
    */
    public interface OnFragmentInteractionListener {
        // TODO: Update argument type and name
        public void onFragmentInteraction(String id);
    }



}

The code of CustomAdapter.java

public class CustomAdapter extends ParseQueryAdapter<ParseObject> {

    public CustomAdapter(Context context) {
        // Use the QueryFactory to construct a PQA that will only show
        // Todos marked as high-pri
        super(context, new ParseQueryAdapter.QueryFactory<ParseObject>() {
            public ParseQuery create() {
                ParseQuery query = new ParseQuery("Photo");
                query.whereEqualTo("imageName", "profileImage");
//                query.setCachePolicy(ParseQuery.CachePolicy.CACHE_ELSE_NETWORK);
                return query;
            }
        });
    }

    // Customize the layout by overriding getItemView
    @Override
    public View getItemView(ParseObject object, View v, ViewGroup parent) {
        if (v == null) {
            v = View.inflate(getContext(), R.layout.search_list_item, null);
        }

        super.getItemView(object, v, parent);

        // Add and download the image
        ParseImageView image = (ParseImageView) v.findViewById(R.id.profileImage);
        ParseFile photoFile = object.getParseFile("imageFile");
        if (photoFile != null) {
            image.setParseFile(photoFile);
            image.loadInBackground();
//                    (new GetDataCallback() {
//                @Override
//                public void done(byte[] data, ParseException e) {
//                    // nothing to do
//                }
//            });

        }


//        // Add the title view
//        TextView titleTextView = (TextView) v.findViewById(R.id.text1);
//        titleTextView.setText(object.getString("title"));
//
//        // Add a reminder of how long this item has been outstanding
//        TextView timestampView = (TextView) v.findViewById(R.id.timestamp);
//        timestampView.setText(object.getCreatedAt().toString());
        return v;
    }

}

I have studied a lot of times and followed these documentations from Parse.com

1) UI-ParseQueryAdapter

2) MealSpotting

Thank you very much for your time and sorry for the long post, just tried to be as explanatory as possible.


Solution

  • After some more general searching abot list views and their performance I stepped across a blog post which has a very interesting last paragraph which says:

    Never set the height of a ListView to wrap_content. If you have all your data locally available, it might not seem so bad, but it becomes particularly troublesome when you don’t. If you use wrap_content on your ListView, this is what happens: The first getView call is done, convertView is null, and position 0 is loaded. Now, position 1 is loaded, but it is passed the View you just generated for position 0 as its convertView. Then position 2 is loaded with that same View, and so on. This is done to lay out the ListView since it has to figure out how tall it should be and you didn’t explicitly tell it. Once it has run through all of those positions, that View is passed back to position 0 for yet another getView call, and then position 1 and on are loaded with getView and no convertView. You’re going to end up seeing getView called two or three times as often as you would have expected. Not only does that suck for performance, but you can get some really confusing issues.

    source: Read the last paragraph

    I don't understand it exactly as I am new to development, but after reading that I decided to change layout height and width with predefined dp values and set android:scaleType="centerCrop". This solved the grow heap issue and scrolling became really smooth.

    I hope that someone finds it helpful as I did, and if anyone can explain it more technically I would be very interested in reading it. Thank you very much in advance!