Search code examples
androidlistviewandroid-contentprovidersimplecursoradapterandroid-listfragment

Listview populates, but the adapter claims to be empty


So I'm developing an app over the summer. It stores items in the database, displays them in a listview (otherwise makes Toast telling the user its empty), and allows users to see details by clicking on one. Eventually I plan on having search criteria narrow down the list, but I'm not there yet.

My problem is that the listview seems to be populating, but the app makes Toast....

ResultsActivity.java

public class ResultsActivity extends FragmentActivity implements ResultsFragment.OnPlantSelectedListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.fragment_results);
    }// method: FragmentActivity.onCreate() 

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.results, menu);
        return true;
    }// method: FragmentActivity.onCreateOptionsMenu()

    /* Custom methods */

    //@Override
    public void onPlantSelected(long id){
        Intent intent = new Intent(this, PlantViewActivity.class);
        //TODO confirm id
        intent.putExtra(DBContentProvider.KEY_PLANT_ID, id);
        startActivity(intent);      
    }// method: ResultsFragment.OnPlantSelectedListener.onPlantSelected(long)
}// class: ResultsActivity extends ListActivity

ResultsFragment.java

public class ResultsFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Cursor>{
    private static OnPlantSelectedListener emptyPlantSelectedListener = new OnPlantSelectedListener(){
        @Override
        public void onPlantSelected(long id){}
    };
    private static OnPlantSelectedListener plantSelectedListener = emptyPlantSelectedListener;

    private static final String STATE_ACTIVATED_POSITION = "activated_position";
    private int activatedPosition = ListView.INVALID_POSITION;
    private SimpleCursorAdapter adapter;

    public ResultsFragment(){}

    /* Fragment Lifespan */
    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);

        String[] dbPlantColumns = {DBContentProvider.KEY_GENUS_TEXT, DBContentProvider.KEY_SPECIES, 
                DBContentProvider.KEY_COMMON_NAME_GROUP};
        int[] fragPlantFields = {R.id.result_item_txt_genus, R.id.result_item_txt_species, 
                R.id.result_item_txt_common_name};
        getLoaderManager().initLoader(0x01, null, this);//null can be replaced by a Bundle of search criteria
        adapter = new SimpleCursorAdapter(getActivity().getApplicationContext(), R.layout.list_item_results, 
                null, dbPlantColumns, fragPlantFields, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
        setListAdapter(adapter);
        if(adapter.isEmpty())//This returns true
            Toast.makeText(getActivity().getApplicationContext(), "Nothing returned from the database", 
                    Toast.LENGTH_LONG).show();
    }// method: Activity.onCreate(Bundle)

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState){
        super.onViewCreated(view, savedInstanceState);
        if (savedInstanceState != null && savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)){
            setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION));
        }
    }// method: ListFragment.onViewCreated(View, Bundle)

    @Override
    public void onAttach(Activity activity){
        super.onAttach(activity);
        if (!(activity instanceof OnPlantSelectedListener)){
            throw new IllegalStateException("Activity must implement fragment's listener.");
        }
        plantSelectedListener = (OnPlantSelectedListener) activity;
    }// method: ListFragment.onAttach(Activity)

    @Override
    public void onDetach(){
        super.onDetach();
        plantSelectedListener = emptyPlantSelectedListener;
    }// method: ListFragment.onDetach()

    @Override
    public void onSaveInstanceState(Bundle outState){
        super.onSaveInstanceState(outState);
        if (activatedPosition != ListView.INVALID_POSITION){
            outState.putInt(STATE_ACTIVATED_POSITION, activatedPosition);
        }
    }// method: ListFragment.onSaveInstanceState(Bundle)

    /* LoaderManager abstract methods */    
    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args){
        //TODO parse args for searching. Second null is String[]; index 0-17 are search arguments.
        String[] arguments = new String[18];
        String[] dbPlantColumns = {DBContentProvider.TABLE_PLANT + "." + DBContentProvider.KEY_PLANT_ID, 
                DBContentProvider.KEY_FAMILY_TEXT, DBContentProvider.KEY_GENUS_TEXT, DBContentProvider.KEY_SPECIES, 
                DBContentProvider.KEY_COMMON_NAME_GROUP, DBContentProvider.KEY_GROWTH_FORM_TEXT};
        CursorLoader cursorLoader = new CursorLoader(getActivity(), Uri.parse(DBContentProvider.CONTENT_URI_STRING + 
                "search"), dbPlantColumns, null, arguments, null);
        return cursorLoader;
    }// method: LoaderCallbacks.onCreateLoader(int, Bundle)

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor){
        adapter.swapCursor(cursor);
    }// method: LoaderCallbacks.onLoadFinished(Loader<Cursor>, Cursor)

    @Override
    public void onLoaderReset(Loader<Cursor> loader){
        adapter.swapCursor(null);
    }// method: LoaderCallbacks.onLoaderReset(Loader<Cursor>)

    /* ListFragment response methods */
    @Override
    public void onListItemClick(ListView listView, View view, int position, long id){
        super.onListItemClick(listView, view, position, id);

        plantSelectedListener.onPlantSelected(id /*Get id of plant at this position*/);
    }// method: ListFragment.onListItemClick(Listview, View, int, long)

    public void setActivateOnItemClick(boolean activateOnItemClick){
        getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
    }// method: setActivateOnItemClick(boolean)

    private void setActivatedPosition(int position){
        if(position == ListView.INVALID_POSITION){
            getListView().setItemChecked(activatedPosition, false);
        } 
        else{
            getListView().setItemChecked(position, true);
        }
        activatedPosition = position;
    }//method: setActiviatedPosition(int)

    /**
     * OnPlantSelectedListener is used by the fragment and the activity to 
     * make sure that the selection of a plant is dealt with correctly.
     */
    public interface OnPlantSelectedListener{
        public void onPlantSelected(long id);
    }// interface: OnPlantSelectedListener
}// class: ResultsFragment extends ListFragment

activity_results.xml

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context=".ResultsActivity"
    android:id="@+id/Results_Lin"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <!-- @android:id/list is the necessary id for the activity to find it. -->
    <ListView 
        android:id="@android:id/list" 
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="5dp">
    </ListView>
    <TextView
        android:id="@+id/results_empty"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/txt_no_results"/>
</LinearLayout>

fragment_results.xml

<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:name="edu.dordt.grassrootsplantid.ResultsFragment"
    android:id="@+id/Result_Fragment"/>

list_item_results.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/Result_Item_Lin"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="5dp">

    <TextView
        android:id="@+id/result_item_txt_lbl_common_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/lbl_common_name"/>
    <TextView
        android:id="@+id/result_item_txt_common_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/result_item_txt_lbl_common_name"/>

    <TextView 
        android:id="@+id/result_item_txt_lbl_scientific_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/result_item_txt_lbl_common_name"
        android:text="@string/lbl_scientific_name"/>
    <TextView
        android:id="@+id/result_item_txt_genus"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/result_item_txt_lbl_scientific_name"
        android:layout_below="@id/result_item_txt_lbl_common_name"/>
    <TextView
        android:id="@+id/result_item_txt_species"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/result_item_txt_genus"
        android:layout_below="@id/result_item_txt_lbl_common_name"
        android:layout_marginLeft="5dp"/>
</RelativeLayout>

I probably misunderstand how to use fragments, but it was necessary in order to use the tutorial I was following for using Content Providers. I didn't put in the Content Provider here, but I'm pretty sure that's working (well...broken somewhere else, but I think I can handle that one). If someone wants to see it anyway, I can edit my question.

If it's at all relevant, the list items have their data and are clickable, but they appear greyed out as though they are unclickable. Perhaps that's a styling issue, but I would figure they would use the style shared by the rest of the app.

But to reiterate my problem, the listview is populating, but the adapter claims to be empty. I'm not sure how big of an issue it is/will be, but I would like either a way to get them to agree or an explanation of why they might not need to.


Solution

  • or an explanation of why they might not need to.

    They seem not to agree because adapter is indeed empty at the time isEmpty is called. Android's Loaders do the work on a separate thread (asynchronously).

    I suggest setting empty view instead of using toast.