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?