0

I have a FragmentActivity that hosts two Fragments that are switched out dynamically via a MenuItem button. When the activity is first accessed fragment 1 is shown. Then the user can push the button to show Fragment 2 (PresetsFragment). Fragment 2 is a ListView that the user can add and delete items to/from. This all works just fine until the screen is rotated. Upon rotation fragment 2's listview is shown correctly but if the user tries to add or delete an item a NPE is thrown. My thinking is that even though the listview is being shown correctly in the new fragment it's using the old fragment 2's data which is somehow not valid for access after the orientation change. I'll try to post the relevant code.

Activity:

public class LiveVideoActivity extends FragmentActivity implements
    AddPresetDialogFragment.AddPresetDialogListener,
    DeletePresetDialogFragment.DeletePresetDialogListener{

private PresetsFragment currentPresetsFragment;

public LiveVideoActivity() {
    Log.d(TAG, "CONSTRUCTOR called.");
    m_backPressed = false;
    m_instanceStateSaved = false;

    m_viewCtrl = LiveVideoViewControlSingleton.getInstance();
    m_viewCtrl.setParentActivity(this);

    m_layoutMgr = LayoutManagerSingleton.getInstance();

    m_devCfgCtrl = DeviceCfgControllerSingleton.getInstance();
    m_devCfgCtrl.setParentActivity(this);

    m_recMgr = RecordingManagerSingleton.getInstance();
    m_recMgr.setParentActivity(this);
    m_recMgr.addViewCtrlCallback(m_viewCtrl);

    m_localStorageManager = new LocalStorageManager();
}

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

        if (orientation == Configuration.ORIENTATION_PORTRAIT) {
            setContentView(R.layout.live_video_activity);

            if(savedInstanceState == null){
                final Fragment deviceControls = new DeviceControlsFragment();
                FragmentTransaction transaction = getFragmentManager().beginTransaction();
                transaction.replace(R.id.framelayout_live_video_fragment_container, deviceControls);             
                transaction.commit();
            }

        }
    else {  // Landscape orientation

            setContentView(R.layout.live_video_activity_land);

            if(savedInstanceState == null) {
                final Fragment deviceControls = new DeviceControlsFragment();
                FragmentTransaction transaction = getFragmentManager().beginTransaction();
                transaction.replace(R.id.framelayout_live_video_fragment_container, deviceControls);
                currentFrameLayoutSelection = "Device";
                transaction.commit();
            }
        }
    }

@Override
public boolean onOptionsItemSelected(MenuItem item) {

    final Bundle bundle = new Bundle();
    final Intent serverSetupScreen = new Intent(getApplicationContext(), ConfigurationViewPagerActivity.class);
    final Intent RecordingsScreen = new Intent(getApplicationContext(), RecordingsViewPagerActivity.class);
    final Fragment deviceControls = new DeviceControlsFragment();
    final Fragment addPresetDialog = new AddPresetDialogFragment();
    final FragmentTransaction transaction = getFragmentManager().beginTransaction();

    switch (item.getItemId()) {

        case R.id.actionItem_takeSnapshot:
            Toast toast = Toast.makeText(getApplicationContext(), "take snapshot", Toast.LENGTH_SHORT);
            toast.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, 0);
            toast.show();
            return true;


        case R.id.dropdown_arrow_for_overlay_live_video:
            if (layout.getVisibility() == View.VISIBLE) {
                layout.setVisibility(View.GONE);
                overlayArrowLiveVideo.setIcon(R.drawable.ic_arrow_drop_up_white_36dp);
                isOverlayVisible = false;
                Log.e("for LV","GONE");
            } else {
                layout.setVisibility(View.VISIBLE);
                overlayArrowLiveVideo.setIcon(R.drawable.ic_arrow_drop_down_white_36dp);
                isOverlayVisible = true;
                Log.e("for LV", "VISIBLE");
            }
            return true;


        case R.id.presets_overflow_live_video:
            if (currentPresetsFragment == null){
                currentPresetsFragment = new PresetsFragment();
            }

            currentFrameLayoutSelection = "Presets";
            isLiveVideo = false; //set boolean to read in onPrepareOptionsMenu() to set correct menu resource

            invalidateOptionsMenu();

            ab.setTitle("Presets");
            actionbarTitle = "Presets";

            if (orientation == Configuration.ORIENTATION_PORTRAIT) {
                recLocalBtn.setVisibility(View.INVISIBLE);
                recRemoteBtn.setVisibility(View.INVISIBLE);
            }

            transaction.replace(R.id.framelayout_live_video_fragment_container, currentPresetsFragment);
            transaction.addToBackStack("Presets").commit();
            return true;


        case R.id.done_with_presets_action:
            ab.setTitle("Live Video");
            actionbarTitle = "Live Video";

            currentFrameLayoutSelection = "Live Video";
            isLiveVideo = true; //set boolean to read in onPrepareOptionsMenu() to set correct menu resource
            if (orientation == Configuration.ORIENTATION_PORTRAIT) {
                recLocalBtn.setVisibility(View.VISIBLE);
                recRemoteBtn.setVisibility(View.VISIBLE);
            }
            transaction.replace(R.id.framelayout_live_video_fragment_container, deviceControls);
            transaction.addToBackStack("DeviceControls").commit();


            invalidateOptionsMenu();
            return true;


        case R.id.add_preset_action:
            Toast toast1 = Toast.makeText(getApplicationContext(), "Add Preset", Toast.LENGTH_SHORT);
            toast1.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, 0);
            toast1.show();

            transaction.add(addPresetDialog, "Add Preset");
            transaction.addToBackStack(null).commit();

            return true;


        case R.id.device_setup_overflow_live_video:
            bundle.putInt("Starting Position", 0);
            serverSetupScreen.putExtras(bundle);
            serverSetupScreen.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            getApplicationContext().startActivity(serverSetupScreen);
            return true;

        case R.id.server_config_overflow_live_video:
            bundle.putInt("Starting Position", 1);
            serverSetupScreen.putExtras(bundle);
            serverSetupScreen.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            getApplicationContext().startActivity(serverSetupScreen);
            return true;

        case R.id.scheduler_overflow_live_video:
            bundle.putInt("Starting Position", 2);
            serverSetupScreen.putExtras(bundle);
            serverSetupScreen.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            getApplicationContext().startActivity(serverSetupScreen);
            return true;

        case R.id.videos_overflow_live_video:
            bundle.putInt("Starting Position", 2);
            bundle.putInt("Calling Screen", 1);
            RecordingsScreen.putExtras(bundle);
            RecordingsScreen.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            getApplicationContext().startActivity(RecordingsScreen);
            return true;


        case R.id.snapshots_overflow_live_video:
            bundle.putInt("Starting Position", 3);
            bundle.putInt("Calling Screen", 1);
            RecordingsScreen.putExtras(bundle);
            RecordingsScreen.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            getApplicationContext().startActivity(RecordingsScreen);
            return true;

        case R.id.status_overflow_live_video:
            ServerStatusDialogFragment statusDialog = new ServerStatusDialogFragment();
            FragmentManager fm = getFragmentManager();
            statusDialog.show(fm, "statusDialog");
            return true;

        default:
            return true;
    }
}

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putBoolean("isLiveVideo", isLiveVideo);
    outState.putString("currentFrameLayoutSelection", currentFrameLayoutSelection);
    outState.putString("actionbarTitle", actionbarTitle);
    m_instanceStateSaved = true;
}


@Override
public void onAddPresetDialogPositiveClick(String preset_name, String preset_description) {
    currentPresetsFragment.onAddPresetDialogPositiveClick(preset_name, preset_description);
}

@Override
public void onDeletePresetDialogPositiveClick(int position) {
    currentPresetsFragment.onDeletePresetDialogPositiveClick(position);
}
}



Fragment #2:

public class PresetsFragment extends Fragment implements
    AddPresetDialogFragment.AddPresetDialogListener,
    DeletePresetDialogFragment.DeletePresetDialogListener{

private List<PresetItem> items = new ArrayList<>();
ListView presetListview;
private PresetsListviewAdapter listAdapter;

private Context mContext;

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

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mContext = getActivity();

    View rootView = inflater.inflate(R.layout.presets_fragment, container, false);

    String fileName = "Preset List";

    String line = null;

    try {
        InputStream inputStream = getActivity().openFileInput(fileName);

        if (inputStream != null) {
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

            while ((line = bufferedReader.readLine()) != null) {
                try {
                    PresetItem presetItem = new PresetItem(line);
                    items.add(presetItem);
                } catch (JSONException je) {
                    je.printStackTrace();
                }
            }

            inputStream.close();

        }

    } catch (FileNotFoundException e) {
        Log.e("preset frag", "File not found: " + e.toString());
    } catch (IOException e) {
        Log.e("preset frag", "Can not read file: " + e.toString());
    }

    File f = new File(getActivity().getFilesDir().getPath());
    File file[] = f.listFiles();
    Log.d("Files", "Size: " + file.length);
    for (int i = 0; i < file.length; i++) {
        Log.d("Files", "FileName:" + file[i].getName());
    }

    presetListview = (ListView) rootView.findViewById(R.id.listview_presets);
    listAdapter = new PresetsListviewAdapter(getActivity(), R.layout.preset_row_layout, items);
    presetListview.setAdapter(listAdapter);

    return rootView;
}

@Override
public void onPause(){
    super.onPause();
    listAdapter.clear();
}

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
}


@Override
public void onAddPresetDialogPositiveClick(String preset_name, String preset_description) {
    PresetItem newPresetItem = new PresetItem(preset_name, preset_description);
    listAdapter.add(newPresetItem);

    try {
        FileUtil.write("Preset List", newPresetItem.toJson(), getActivity());
    } catch (JSONException je) {
        je.printStackTrace();
    }
}

@Override
public void onDeletePresetDialogPositiveClick(int position) {
    listAdapter = new PresetsListviewAdapter(mContext, R.layout.preset_row_layout, items);  

    PresetItem deletePresetItem = listAdapter.getItem(position);

    String dir = getActivity().getFilesDir().getAbsolutePath();

    String originalFileName = "Preset List"; // The name of the original file to open.
    String tempFileName = "Temp Preset List"; // Name of the temp file to hold original file contents

    File originalFile = new File(dir, originalFileName);
    if (!originalFile.canRead()) {
        Log.e("Original File", "cannot be read.");
    }

    File tempFile = new File(dir, tempFileName);
    try {
        if (!tempFile.exists()) {
            tempFile.createNewFile();
        }
    } catch (IOException ie) {
        ie.printStackTrace();
    }
    if (!tempFile.canRead()) {
        Log.e("Temp File", "cannot be read.");
    }

    String line = null; // This will reference one line at a time

    try {
        InputStream inputStream = getActivity().openFileInput(originalFileName);

        if (inputStream != null) {
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

            while ((line = bufferedReader.readLine()) != null) {
                Log.e("In onDelete", line);
                String strDeletePresetItem = deletePresetItem.toJson();
                Log.e("strDeletePresetItem", strDeletePresetItem);
                Log.e("line", line);

                if (!line.trim().equals(strDeletePresetItem)) {
                    Log.e("In file write", "");
                    FileUtil.write(tempFileName, line, getActivity());
                }
            }

            inputStream.close();


            Boolean TF = getActivity().deleteFile(originalFileName);
            Log.e("Original File Deleted", TF.toString());

            Boolean tempFileRename = tempFile.renameTo(originalFile);
            Log.e("tempFile renamed", tempFileRename.toString());

            File f = new File(getActivity().getFilesDir().getPath());

            if (!f.canRead()) {
                System.out.println("File cannot be read.");
            }

            File file[] = f.listFiles();
            Log.d("Files", "Size: " + file.length);
            for (int i = 0; i < file.length; i++) {
                Log.d("Files", "FileName:" + file[i].getName());
            }

        } else {
            System.out.println("inputStream is null");
        }

    } catch (FileNotFoundException e) {
        Log.e("In SF onDelete", "File not found: " + e.toString());
    } catch (IOException e) {
        Log.e("In SF onDelete", "Can not read file: " + e.toString());
    } catch (JSONException je) {
        je.printStackTrace();
    }

    listAdapter.remove(deletePresetItem);
}
}



Delete Dialog:

public class DeletePresetDialogFragment extends DialogFragment {

private DeletePresetDialogListener mListener;

// Override the Fragment.onAttach() method to instantiate the DeletePresetDialogListener
@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    // Verify that the host activity implements the callback interface
    try {
        // Instantiate the DeletePresetDialogListener so we can send events to the host
        mListener = (DeletePresetDialogListener) activity;

    } catch (ClassCastException e) {
        // The activity doesn't implement the interface, throw exception
        throw new ClassCastException(activity.toString()
                + " must implement DeletePresetDialogListener");
    }
}

@Override
public Dialog onCreateDialog(final Bundle savedInstanceState) {
    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

    builder.setTitle("Delete Preset");

    LayoutInflater inflater = getActivity().getLayoutInflater();
    final View dialogView = inflater.inflate(R.layout.delete_preset_dialog, null);

    builder.setView(dialogView)
            .setPositiveButton("Delete", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int id) {

                    Bundle b = getArguments();

                    //***** ERROR HERE BUT I THINK IT'S JUST COMING THROUGH THE INTERFACE *****//
                    mListener.onDeletePresetDialogPositiveClick(b.getInt("position"));
                }


            })
            .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int id) {
                    DeletePresetDialogFragment.this.getDialog().cancel();
                }
            });
    return builder.create();


}

public interface DeletePresetDialogListener {
    void onDeletePresetDialogPositiveClick(int position);
}
}

The error in the IDE is at the mListener in the middle of the Delete Dialog, however I'm pretty sure that is because the method onDeletePresetDialogPositiveClick is being called on a null fragment. What I don't understand is why the UI (listview) is rendered correctly in the fragment however trying to access its data/methods it's presumably null. Is there something I'm doing that is preventing the activity from re-using or creating a new fragment that I can then access a valid listview and Delete/Add methods?

shoota
  • 111
  • 11
  • I didn't fully grasp your code - maybe you could make it easier by creating a minimal piece of code that shows the error and omit the rest of details. But i made two observations: 1. You perform part of your initialization of your Activity not in onCreate, but a constructor. While not explicitly forbidden, this is at least unusual. Any particular reasons for this? 2. onSaveInstanceState doesn't save currentPresetsFragment. So, after rotating the device, this will be null until possibly being set in onOptionsItemSelected. – TAM Mar 23 '16 at 17:10
  • @TAM The constructor was written by someone with a lot of C background, that may explain that. Now, you've got me curious about onSaveInstanceState. 1) I was under that impression that Android is supposed to manage fragments' lifecycles and saving/restoring. In fact, I do almost the exact same thing as what you see here in a different part of my app and I don't have to manage the fragments myself. 2) BUT, I'm willing to manage them myself. How do you save a fragment into onSavedInstanceState, I've searched that but couldn't find anything. – shoota Mar 23 '16 at 17:46

2 Answers2

0

i noticed a missing closing bracket in your LiveVideoActivity's onCreate method, you have closed the if (serverItem != null) { block but not the onCreate method. how could you compile the app?

another think (i didn't go deep, i want just to help you remembering) is the onDeletePresetDialogPositiveClick method. both the PresetsFragment (Fragment2) and LiveVideoActivity are implementing the DeletePresetDialogFragment.DeletePresetDialogListener and of course the method onDeletePresetDialogPositiveClick. i think you're expecting the main's (LiveVideoActivity) one to be executed while the PresetsFragment's is being executed and you're manipulating a null object inside. (i am not sure of your expectation because i did not go so deep on your code, i just suppose).

younes zeboudj
  • 856
  • 12
  • 22
  • Yes, all of that is correct, take this as more of psuedo-code. I tried to strip out what wasn't necessary so it wasn't so verbose. – shoota Mar 23 '16 at 17:42
  • ok, i thought so. i noticed your test: `if (serverItem != null)` , you're not setting any contentView in the else block, does this mean if the `serverItem` is null so nothing is shown ? – younes zeboudj Mar 23 '16 at 17:48
  • I edited that stuff out just now, clearly it's confusing and not pertinent to the question. – shoota Mar 23 '16 at 17:54
  • yes, i am just trying to understand your code, because any instruction may cause the error if it is shown, try to minimize the code to limit the possible cases. – younes zeboudj Mar 23 '16 at 18:00
  • okey, we can debug it otherwise, check if the Bundle got here :`Bundle b = getArguments();` contains the "position" key. because it is a NPE it can be caused by `mListener` since it may not be set on the `onAttach` be cause on the `try/catch` block, even we know that the activity will implements `DeletePresetDialogListener` (`LiveVideoActivity`) – younes zeboudj Mar 23 '16 at 18:02
  • It does, I took the checking code out here. mListener is also not null. – shoota Mar 23 '16 at 18:03
  • 1
    ok. it can be the `onDeletePresetDialogPositiveClick` method called on the `mListener` because it calls the one of the `LiveVideoActivity` which use `currentPresetsFragment` object, can this object be null? (it is the current shown Fragment , so probably no) but try to Debug it to confirm that it is not null. – younes zeboudj Mar 23 '16 at 18:10
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/107167/discussion-between-shoota-and-youzking). – shoota Mar 23 '16 at 18:10
0

I figured it out with @youzking 's help. So of course currentPresetsFragment is null after orientation change because it's only initialized on the menuitem button click and the activity is rebuilt on orientation change. So in that onItemClick I added a tag for the fragment and then checked if currentPresetsFragment is null in onDeletePresetDialogPositiveClick in the activity. If it is then lookup the current ACTIVE presets frag using the tag. I followed this post. get currently displayed fragment

Community
  • 1
  • 1
shoota
  • 111
  • 11