I am having an issue with an app I am creating.
I created a series of Fragments within a single main Activity.
In each Fragment is either a form for data collection, or a ListView for displaying the colelcted data.
The data manipulation is controlled by the listener methods of several widgets and the data model behind the scenes.
Basically I am using the data collection Fragment to store data in an SQLite database, which is then queried upon travel to a display fragment. The display fragments have buttons for returning to the collection fragments to add, edit, or delete a selected item. In the case of an add/edit button press I would in certain states send a bundle which could be filled differently based on the needs of the user. Typically a bundle contains information sent from a parent context to a child context upon which the child unpacks the bundle and places data in text fields and/or sets spinners containing static data.
I tried using an over complicated approach where I created a new Bundle directly inside whatever widget listener was being used.
But I found that it was creating unnecessary complexity in the following Fragment where I would need to test for keys contained within the Bundles from several different possible origins.
The thought was that inside the beginning of any Fragment I would call
Bundle bundle = this.getArguments()
Which would set bundle
to whichever Bundle was passed in:
fragment.setArguments(*bundle based on conditions*)
so from this flow I had to create an incredibly long and hard to read succession of if-conditionals looking for specific keys based on bundle.isEmpty()
which of course throws a NullPointerException if you're arriving at that Fragment for the first time and no bundle has been sent yet.
Then to compound the nightmare I threw all of this in a try-catch to handle the bundle.isEmpty() null pointer, which, I tried to use as an indicator of current state (/sigh... I know).
What I would like to do is use Bundle savedInstanceState
for any traffic of info/state to a new Fragment.
So ideally I would only ever have to access one Bundle and test for containing keys.
Then it seems that I would be able to test for whether or not savedInstanceState
is null and set it somehow.
The problem is that I have these widgets in my Fragments which I am using to interact with my model and possibly navigate to the next necessary Fragment.
If I use savedInstanceState
within an onClick
listener method of one of the widgets, I am forced to make it final
which promptly removes my ability to use savedInstanceState
in any other scope but the initial usage of it or at least that is my understanding.
This causes NullPointerException errors when I try to access savedInstanceState
outside of the first scope.
If I remove final
the IDE tells me (correctly so) that I cannot access savedInstanceState
from within an inner class, which in my case is several widgets to which I have attached listener methods.
I have read:
Are the bundles in android permanent in saving activity states ?
How to use the anonymous inner class?
How to use Outer Method's input in Anonymous Inner Class?
Here is a code example of two Fragments reliant upon sending Bundles to each other;
The display and select class:
public class Row_main extends Fragment {
Spinner location_spinner;
Button add_row_btn,edit_row_button,delete_row_btn;
ListView row_list_holder;
StorageHandler sh;
List<Row> row_list;
ArrayAdapter<CharSequence> ls_adapter;
String [] location_test_values = {"test_location"};
boolean has_items;
public Row_main()
{
//default constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
final Bundle savedInstanceState) {
if(savedInstanceState == null)
{
//this will satisfy if arriving here from navigation menu.
//I would like to set savedInstanceState here if null.
savedInstanceState = new Bundle();
//but I get error: 'cannot assign a value to final variable'
}
getActionBar().setTitle("Row");
//Create view
final View v = inflater.inflate(R.layout.row_main_layout, container, false);
add_row_btn = (Button) v.findViewById(R.id.add_row_btn);
edit_row_button = (Button) v.findViewById(R.id.edit_row_btn);
delete_row_btn = (Button) v.findViewById(R.id.delete_row_btn);
add_row_btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
addRow fragment = new addRow();
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
fragment.setArguments(savedInstanceState);
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragment);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
getActionBar().setTitle("Add Row");
}
});
edit_row_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
for(int i=0; i<row_list.size(); i++)
{
if(row_list.get(i).isSelected())
{
row_list.get(i).setEdited(true);
addRow fragment = new addRow();
savedInstanceState.putStringArrayList("current_row",row_list.get(i).getList());
fragment.setArguments(savedInstanceState);
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragment);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
getActionBar().setTitle("Add Row");
}
}
}
});
return v;
}
private ActionBar getActionBar() {
return ((AppCompatActivity) getActivity()).getSupportActionBar();
}
and here is the data collect form class:
public class addRow extends Fragment {
IdGenerator generator;
String new_id;
AlertDialog.Builder alertDialogBuilder, fmdDialogBuilder;
EditText row_name,
rack_name,
tile_location,
tile_name,
separation,
bf_offset;
Spinner object_type,
direction,
rotation;
ArrayAdapter<CharSequence> objType_adapter,
direction_adapter,
rotation_adapter;
String[] objtype_values = {"Rack","FMD"};
String[] direction_values = {"Right","Left"};
String[] rotation_values = {"0","90","180","270"};
int RACK = 0,FMD = 1,RIGHT = 0,LEFT = 1;
Row current_row;
ArrayList<String> list_to_unpack;
Button save_row_btn,
add_btn,
cancel_btn;
public addRow() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
final Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v = inflater.inflate(R.layout.add_row_layout, container, false);
generator = new IdGenerator();
new_id = generator.randomString(5);
if(savedInstanceState != null)
{
//Bundle passed, edit button was used, unpack
if (savedInstanceState.containsKey("current_row")) {
Log.d("BUNDLE-CONTENTS", "" + savedInstanceState.size());
list_to_unpack = savedInstanceState.getStringArrayList("current_row");
current_row.setId(list_to_unpack.get(0));
current_row.setRow_name(list_to_unpack.get(1));
current_row.setObject_type(list_to_unpack.get(2));
current_row.setRack_name(list_to_unpack.get(3));
current_row.setTile_location(list_to_unpack.get(4));
current_row.setTile_name(list_to_unpack.get(5));
current_row.setDirection(list_to_unpack.get(6));
current_row.setRotation(list_to_unpack.get(7));
current_row.setSeparation(list_to_unpack.get(8));
current_row.setBf_offset(list_to_unpack.get(9));
current_row.setEdited(true);
row_name.setText(current_row.getRow_name());
if (current_row.getObject_type().equalsIgnoreCase("Rack")) {
object_type.setSelection(RACK);
} else {
object_type.setSelection(FMD);
}
rack_name.setText(current_row.getRack_name());
tile_location.setText(current_row.getTile_location());
tile_name.setText(current_row.getTile_name());
if (current_row.getDirection().equalsIgnoreCase("Right")) {
direction.setSelection(RIGHT);
} else {
direction.setSelection(LEFT);
}
switch(current_row.getRotation())
{
case "0": rotation.setSelection(0); break;
case "90": rotation.setSelection(1); break;
case "180": rotation.setSelection(2); break;
case "270": rotation.setSelection(3); break;
}
separation.setText(current_row.getSeparation());
bf_offset.setText(current_row.getBf_offset());
}
}
row_name = (EditText) v.findViewById(R.id.rowName_value);
rack_name = (EditText) v.findViewById(R.id.rackName_value);
tile_location = (EditText) v.findViewById(R.id.tileLoc_value);
tile_name = (EditText) v.findViewById(R.id.tileName_value);
separation = (EditText) v.findViewById(R.id.separation_value);
bf_offset = (EditText) v.findViewById(R.id.bfOffset_value);
object_type = (Spinner) v.findViewById(R.id.obj_type);
direction = (Spinner) v.findViewById(R.id.direction);
rotation = (Spinner) v.findViewById(R.id.rotation_spinner);
objType_adapter = new ArrayAdapter<CharSequence>(this.getActivity(),
android.R.layout.simple_spinner_dropdown_item, objtype_values);
direction_adapter = new ArrayAdapter<CharSequence>(this.getActivity(),
android.R.layout.simple_spinner_dropdown_item, direction_values);
rotation_adapter = new ArrayAdapter<CharSequence>(this.getActivity(),
android.R.layout.simple_spinner_dropdown_item, rotation_values);
object_type.setAdapter(objType_adapter);
direction.setAdapter(direction_adapter);
rotation.setAdapter(rotation_adapter);
save_row_btn = (Button) v.findViewById(R.id.save_row_btn);
add_btn = (Button) v.findViewById(R.id.add_btn);
cancel_btn = (Button) v.findViewById(R.id.cancel_btn);
alertDialogBuilder = new AlertDialog.Builder(getActivity());
fmdDialogBuilder = new AlertDialog.Builder(getActivity());
alertDialogBuilder.setMessage("Add Rack or FMD:");
fmdDialogBuilder.setMessage("Add Power Providing or Power Consuming FMD:");
alertDialogBuilder.setNegativeButton("Rack", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//if user selects power providing fmd
//send id with it
//StorageHandler sh = new StorageHandler(getActivity());
Row row = new Row(
new_id,
row_name.getText().toString(),
object_type.getSelectedItem().toString(),
rack_name.getText().toString(),
tile_location.getText().toString(),
tile_name.getText().toString(),
direction.getSelectedItem().toString(),
rotation.getSelectedItem().toString(),
separation.getText().toString(),
bf_offset.getText().toString());
ArrayList<String> rowList = row.getList();
for(String i : rowList)
{
Log.d("ADD ROW BTN","rowList[i]=" + i);
}
savedInstanceState.putStringArrayList("current_row", rowList);
addRack fragment = new addRack();
fragment.setArguments(savedInstanceState);
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragment);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
getActionBar().setTitle("Add Rack to: " + row.getRow_name());
}
});
alertDialogBuilder.setPositiveButton("FMD", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//todo choice between consuming and providing
fmdDialogBuilder.show();
}
});
fmdDialogBuilder.setPositiveButton("Providing", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Row row = new Row(
new_id,
row_name.getText().toString(),
object_type.getSelectedItem().toString(),
rack_name.getText().toString(),
tile_location.getText().toString(),
tile_name.getText().toString(),
direction.getSelectedItem().toString(),
rotation.getSelectedItem().toString(),
separation.getText().toString(),
bf_offset.getText().toString());
ArrayList<String> obj_as_list = row.getList();
Log.d("newId", obj_as_list.get(0));
Log.d("row_name", obj_as_list.get(1));
Log.d("object_type", obj_as_list.get(2));
Log.d("rack_name", obj_as_list.get(3));
Log.d("objaslistsize", ""+obj_as_list.size());
savedInstanceState.putStringArrayList("current_row", obj_as_list);
addPowerProvidingFMD fragment = new addPowerProvidingFMD();
fragment.setArguments(savedInstanceState);
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragment);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
getActionBar().setTitle("add FMD to: " + row_name.getText().toString());
}
});
fmdDialogBuilder.setNegativeButton("Consuming", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Row row = new Row(
new_id,
row_name.getText().toString(),
object_type.getSelectedItem().toString(),
rack_name.getText().toString(),
tile_location.getText().toString(),
tile_name.getText().toString(),
direction.getSelectedItem().toString(),
rotation.getSelectedItem().toString(),
separation.getText().toString(),
bf_offset.getText().toString());
ArrayList<String> obj_as_list = row.getList();
savedInstanceState.putStringArrayList("current_row", obj_as_list);
addPowerConsumingFMD fragment = new addPowerConsumingFMD();
fragment.setArguments(savedInstanceState);
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragment);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
getActionBar().setTitle("add FMD to: " + row_name.getText().toString());
}
});
save_row_btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//save row info to database
StorageHandler sh = new StorageHandler(getActivity());
if(current_row.isEdited())
{
//update row
String current_id = current_row.getId();
Log.d("*update row*", "id=" + current_id);
//get updated values from fields, send to database
Row row = new Row(current_id, row_name.getText().toString(), object_type.getSelectedItem().toString(),
rack_name.getText().toString(), tile_location.getText().toString(),
tile_name.getText().toString(), direction.getSelectedItem().toString(),
rotation.getSelectedItem().toString(), separation.getText().toString(),
bf_offset.getText().toString());
sh.updateRow(row);
sh.close();
}
else
{
//save new row
Log.d("*save row*", "id=" + new_id);
//save row to database
sh.addRow(new Row(new_id, row_name.getText().toString(), object_type.getSelectedItem().toString(),
rack_name.getText().toString(), tile_location.getText().toString(),
tile_name.getText().toString(), direction.getSelectedItem().toString(),
rotation.getSelectedItem().toString(), separation.getText().toString(),
bf_offset.getText().toString()));
sh.close();
}
Row_main fragment = new Row_main();
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragment);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
getActionBar().setTitle("Row");
}
});
add_btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
alertDialogBuilder.show();
}
});
cancel_btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Row_main fragment = new Row_main();
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragment);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
getActionBar().setTitle("Row");
}
});
return v;
}
private ActionBar getActionBar() {
return ((AppCompatActivity) getActivity()).getSupportActionBar();
}
I would like to use Bundles here since it is not important to restore anything from a Bundle if the user leaves the app or presses the back Button.
Am I going about this right? what should I do about the final Bundle situation in onClickListener
methods? I absolutely need to pass Bundles from them the way I have things setup now and I would like to use the same Bundle if I can.
I have a deadline to consider so a complete overhaul might be out of the question.
Any input is greatly appreciated.