Android IllegalStateException: Fragment not attached to Activity

dmfrey picture dmfrey · Jun 30, 2012 · Viewed 7.1k times · Source

I ran into this issue and came up with numerous posts both here and elsewhere related to this issue.

This application targets android 16, but min is set to 8. So I am using the Android Support Library to have access to newer apis.

I have an activity that loads a fragment which is a ListFragment. If you are running on a tablet in landscape mode, the first fragment, when list item is clicked, will load the second fragment in a 2 pane layout. If on a phone, a new activity loads the second fragment.

The second fragment uses a CursorLoader to access a ContentProvider as its backing data store. When this fragment is called from its own activity, there is no issue. However, when it is in 2 pane layout, the CursorLoader would fail with the above exception.

Here is the code:

public class ProgramGroupFragment extends MythtvListFragment implements LoaderManager.LoaderCallbacks<Cursor> {

    private static final String TAG = ProgramGroupFragment.class.getSimpleName();

    private ProgramCursorAdapter adapter;

    private String programGroup = "*";

    public ProgramGroupFragment() { }

    @Override
    public Loader<Cursor> onCreateLoader( int id, Bundle args ) {

        String[] projection = { BaseColumns._ID, ProgramConstants.FIELD_TITLE, ProgramConstants.FIELD_SUB_TITLE };
        String[] selectionArgs = { programGroup };

        CursorLoader cursorLoader = new CursorLoader( getActivity(), ProgramConstants.CONTENT_URI, projection, ProgramConstants.FIELD_TITLE + "=?", selectionArgs, ProgramConstants.FIELD_SUB_TITLE );

        return cursorLoader;
    }

    @Override
    public void onLoadFinished( Loader<Cursor> loader, Cursor cursor ) {
        Log.v( TAG, "onLoadFinished : enter" );

        adapter.swapCursor( cursor );

    }

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

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

        adapter = new ProgramCursorAdapter(
                getActivity().getApplicationContext(), R.layout.program_row,
                null, new String[] { ProgramConstants.FIELD_SUB_TITLE }, new int[] { R.id.program_sub_title },
                CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER );

        setListAdapter( adapter );

        getLoaderManager().initLoader( 0, null, this );
    }

    public void loadPrograms( String name ) {       
        programGroup = name;
        try {
            getLoaderManager().restartLoader( 0, null, this );
        } catch( Exception e ) {
            Log.w( TAG, e.getLocalizedMessage(), e );
        }
    }

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

        Intent i = new Intent( getActivity(), VideoActivity.class );
        i.putExtra( VideoActivity.EXTRA_PROGRAM_KEY, id );
        startActivity( i );
    }

    private class ProgramCursorAdapter extends SimpleCursorAdapter {

        public ProgramCursorAdapter( Context context, int layout, Cursor c, String[] from, int[] to, int flags ) {
            super( context, layout, c, from, to, flags );
        }

        @Override
        public View getView( int position, View convertView, ViewGroup parent ) {

            View row =  super.getView( position, convertView, parent );

            getCursor().moveToPosition( position );
            try {
                int idIndex = getCursor().getColumnIndexOrThrow( BaseColumns._ID );
                int titleIndex = getCursor().getColumnIndexOrThrow( ProgramConstants.FIELD_TITLE );
                int subTitleIndex = getCursor().getColumnIndexOrThrow( ProgramConstants.FIELD_SUB_TITLE );

                int id = getCursor().getInt( idIndex );
                String title = getCursor().getString( titleIndex );
                String subTitle = getCursor().getString( subTitleIndex );

                if( row == null ) {
                    LayoutInflater inflater = getActivity().getLayoutInflater();

                    row = inflater.inflate( R.layout.program_row, parent, false );
                }

                TextView t = (TextView) row.findViewById( R.id.program_sub_title );
                t.setText( !"".equals( subTitle ) ? subTitle : title );

            } catch( Exception e ) {
                Log.e( TAG, "getView : error", e );
            }

            return row;
        }

    }

}

The problem occurred in the method loadPrograms, specifically the line:

getLoaderManager().restartLoader( 0, null, this );

This was throwing the IllegalStateException. After looking through the documentation and numerous examples, I never once saw this issue and having to be caught in order to proceed.

I wrapped the code in a try/catch block just to see what would happen, and to my surprise, the app worked as expected.

EDIT: Here is the Activity code that establishes the 2 pane layout:

public class RecordingsActivity extends AbstractRecordingsActivity implements RecordingsFragment.OnProgramGroupListener {

    private static final String TAG = RecordingsActivity.class.getSimpleName();
    private static final int REFRESH_ID = Menu.FIRST + 2;

    private long requestId;
    private BroadcastReceiver requestReceiver;

    private DvrServiceHelper mDvrServiceHelper;

    @Override
    public void onCreate( Bundle savedInstanceState ) {

        super.onCreate( savedInstanceState );
        setContentView( R.layout.activity_dvr_recordings );

        RecordingsFragment recordingsFragment = (RecordingsFragment) getSupportFragmentManager().findFragmentById( R.id.fragment_dvr_program_groups );
        recordingsFragment.setOnProgramGroupListener( this );

    }

    @TargetApi( 11 )
    @Override
    public boolean onCreateOptionsMenu( Menu menu ) {
        Log.v( TAG, "onCreateOptionsMenu : enter" );

        MenuItem refresh = menu.add( Menu.NONE, REFRESH_ID, Menu.NONE, "Refresh" );
        if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ) {
            refresh.setShowAsAction( MenuItem.SHOW_AS_ACTION_IF_ROOM );
        }

        return true;
    }

    /* (non-Javadoc)
     * @see org.mythtv.client.ui.dvr.AbstractRecordingsActivity#onOptionsItemSelected(android.view.MenuItem)
     */
    @Override
    public boolean onOptionsItemSelected( MenuItem item ) {

        switch( item.getItemId() ) {
        case REFRESH_ID:

            requestId = mDvrServiceHelper.getRecordingedList();

            return true;
        }

        return super.onOptionsItemSelected( item );
    }

    @Override
    protected void onResume() {
        super.onResume();

        IntentFilter filter = new IntentFilter( DvrServiceHelper.ACTION_REQUEST_RESULT );
        requestReceiver = new BroadcastReceiver() {

            @Override
            public void onReceive( Context context, Intent intent ) {

                long resultRequestId = intent.getLongExtra( DvrServiceHelper.EXTRA_REQUEST_ID, 0 );


                if( resultRequestId == requestId ) {


                    int resultCode = intent.getIntExtra( DvrServiceHelper.EXTRA_RESULT_CODE, 0 );


                    if( resultCode == 200 ) {
                        Log.d( TAG, "Updating UI with new data" );
                    } else {
                        Log.d( TAG, "error occurred" );
                    }
                } else {
                    Log.d( TAG, "Result is NOT for our request ID" );
                }

            }
        };

        mDvrServiceHelper = DvrServiceHelper.getInstance( this );
        registerReceiver( requestReceiver, filter );
    }

    /* (non-Javadoc)
     * @see android.support.v4.app.FragmentActivity#onPause()
     */
    @Override
    public void onPause() {
        super.onPause();

        // Unregister for broadcast
        if( null != requestReceiver ) {
            try {
                unregisterReceiver( requestReceiver );
                requestReceiver = null;
            } catch( IllegalArgumentException e ) {
                Log.e( TAG, e.getLocalizedMessage(), e );
            }
        }

    }

    public void onProgramGroupSelected( String programGroup ) {
        Log.d( TAG, "onProgramGroupSelected : enter" );

        if( null != findViewById( R.id.fragment_dvr_program_group ) ) {
            FragmentManager manager = getSupportFragmentManager();

            ProgramGroupFragment programGroupFragment = (ProgramGroupFragment) manager.findFragmentById( R.id.fragment_dvr_program_group );
            FragmentTransaction transaction = manager.beginTransaction();

            if( null == programGroupFragment ) {
                Log.v( TAG, "onProgramGroupSelected : creating new programGroupFragment" );
                programGroupFragment = new ProgramGroupFragment();

                transaction
                    .add( R.id.fragment_dvr_program_group, programGroupFragment )
                    .setTransition( FragmentTransaction.TRANSIT_FRAGMENT_OPEN )
                    .addToBackStack( null )
                    .commit();
            }

            programGroupFragment.loadPrograms( programGroup );
        } else {

            Intent i = new Intent( this, ProgramGroupActivity.class );
            i.putExtra( ProgramGroupActivity.EXTRA_PROGRAM_GROUP_KEY, programGroup );
            startActivity( i );
        }

    }

}

The Listener method onProgramGroupSelected is where the single vs 2 pane layout is determined. The listener is called when the list item in the left pain is clicked.

Can anyone explain why this exception is occurring?

The full code for the project is located at https://github.com/MythTV-Android/mythtv-for-android

Answer

jayeffkay picture jayeffkay · Aug 24, 2012

I experienced something similar. For me it seemed to help to avoid using the ApplicationContext when using a context. Try create your ProgramCursorAdapter without using the getApplicationContext().