SetUp:
Activity with one fragment which is instantiated with button click. In fragment's constructor Bundle
is used. In Bundle
a String (surname)
and an ArrayList<String> (fornames)
are set. Fragment gets detached through callback.
Problem:
When fragment is detached the String (surname)
gets destroyed as expected, but the ArrayList
persists. In consequence the former ArrayList
entries appears when a new instance of the fragment is called. The callback isn't the problem. The behavior appears without the callback, too.
I have checked the variables (surname = Black
and fornename = Joe
) with Log.d
at the points fragment constructor (FRAG_CONSTRUCTOR
), fragment getArguments()
in onCreate
method (FRAG_ARGS_ONCREATE
) and in fragments callback (FRAG_CALLBACK
).
The surname Black
isn't as expected shown in LogCat
, but the forname Joe
persists.
Activity:
public class MainActivity extends AppCompatActivity implements FragRecycler.FragRecyclerCallBackListener {
private Button button;
private String surname;
private ArrayList<String> fornames;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fornames = new ArrayList<String>();
button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
addFragmentWithTransition(R.id.container, FragRecycler.newInstance(surname, fornames), "FRAG_RECYCLER");
}
});
}
public void addFragmentWithTransition(int containerViewId, Fragment fragment, String fragmentTag) {
getSupportFragmentManager()
.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.add(containerViewId, fragment, fragmentTag)
.addToBackStack(fragmentTag)
.commit();
}
@Override
public void onFragRecyclerCallback() {
Log.d("FRAG_CALLBACK", "forname: " + fornames + " surname: " + surname);
getSupportFragmentManager().popBackStack();
}
@Override
public void onBackPressed() {
if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
getSupportFragmentManager().popBackStack();
} else {
super.onBackPressed();
}
}
}
Fragment:
public class FragRecycler extends Fragment {
private View v;
private Toolbar toolbar;
private TextInputEditText vSurname;
private RecyclerView rvForenames;
private AdapterForName adapter;
private FragRecyclerCallBackListener callback;
public interface FragRecyclerCallBackListener {
void onFragRecyclerCallback();
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof AppCompatActivity){
try {
callback = (FragRecyclerCallBackListener) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString() + " must implement FragRecyclerCallBackListener");
}
}
}
@Override
public void onDetach() {
super.onDetach();
callback = null;
}
public static FragRecycler newInstance(String surname, ArrayList<String> fornames) {
Log.d("FRAG_CONSTRUCTOR", "forname: " + fornames + " surname: " + surname);
FragRecycler p = new FragRecycler();
Bundle b = new Bundle();
b.putString("SURNAME", surname);
b.putStringArrayList("FORNAMES", fornames);
p.setArguments(b);
return p;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.d("FRAG_ARGS_ONCREATE", "forname: " + getArguments().getStringArrayList("FORNAMES") + " surname: " + getArguments().getString("SURNAME"));
v = inflater.inflate(R.layout.frag_recycler, container, false);
toolbar = (Toolbar) v.findViewById(R.id.toolbar);
vSurname = (TextInputEditText) v.findViewById(R.id.surname);
rvForenames = (RecyclerView) v.findViewById(R.id.rv_forenames);
vSurname.setText(getArguments().getString("SURNAME"));
adapter = new AdapterForName("Forename", getArguments().getStringArrayList("FORNAMES"));
rvForenames.setAdapter(adapter);
rvForenames.setLayoutManager(new LinearLayoutManager(getContext()));
toolbar.setNavigationIcon(ContextCompat.getDrawable(getContext(), R.drawable.ic_clear));
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
callback.onFragRecyclerCallback();
}
});
return v;
}
class AdapterForName extends RecyclerView.Adapter<AdapterForName.ViewHolder> {
private ArrayList<String> names;
private String callType;
public AdapterForName(String callType, ArrayList<String> names) {
this.callType = callType;
this.names = names;
if (names.size() == 0) {
addEmptyEntryToList();
} else {
if (!(names.get(names.size() - 1).trim().length() < 1)) {
addEmptyEntryToList();
}
}
}
protected void addEmptyEntryToList() {
names.add("");
notifyDataSetChanged();
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.name, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
if (position == 0) {
holder.inputLayout.setHint(callType);
} else {
holder.inputLayout.setHint(String.valueOf(position + 1) + " th." + " " + callType);
}
holder.input.setText(names.get(position));
holder.input.setTag(position);
holder.input.addTextChangedListener(new TextWatcher() {
public void afterTextChanged(Editable s) {
final String inputText = s.toString().trim();
names.set(holder.getAdapterPosition(), inputText);
}
public void beforeTextChanged(CharSequence s, int start,
int count, int after) {
}
public void onTextChanged(CharSequence s, int start,
int before, int count) {
}
});
holder.input.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View view, boolean b) {
if (!b) { // inputEditText hat keinen Focus mehr.
if (holder.input.getText().toString().trim().length() > 0){
int count = 0;
for (int i = 0; i < names.size(); i++) {
if (names.get(i).trim().length() == 0) {
count = count + 1;
}
}
if (count == 0) {
names.add("");
notifyDataSetChanged();
}
}
if (holder.input.getText().toString().trim().length() == 0){
int count = 0;
for (int i = 0; i < names.size(); i++) {
if (names.get(i).trim().length() == 0) {
count = count + 1;
}
}
if (count > 0) {
names.remove(holder.getAdapterPosition());
notifyDataSetChanged();
}
}
}
}
});
}
public ArrayList<String> getList() {
ArrayList<String> trimmedList = new ArrayList<>();
for (int i = 0; i < names.size(); i++) {
if (names.get(i).trim().length() > 0) {
trimmedList.add(names.get(i));
}
}
return trimmedList;
}
@Override
public int getItemCount() {
return names.size();
}
class ViewHolder extends RecyclerView.ViewHolder {
public TextInputLayout inputLayout;
public TextInputEditText input;
public ViewHolder(View itemView) {
super(itemView);
inputLayout = (TextInputLayout) itemView.findViewById(R.id.input_layout);
input = (TextInputEditText) itemView.findViewById(R.id.input);
}
}
}
}
LogCat:
08-05 21:42:34.387 17055-17055/com.example.user.recyclertest W/art: Before Android 4.1, method int android.support.v7.widget.ListViewCompat.lookForSelectablePosition(int, boolean) would have incorrectly overridden the package-private method in android.widget.ListView
08-05 21:42:36.625 17055-17055/com.example.user.recyclertest D/FRAG_CONSTRUCTOR: forname: [] surname: null
08-05 21:42:36.643 17055-17055/com.example.user.recyclertest D/FRAG_ARGS_ONCREATE: forname: [] surname: null
08-05 21:42:41.852 17055-17055/com.example.user.recyclertest W/IInputConnectionWrapper: finishComposingText on inactive InputConnection
08-05 21:42:41.857 17055-17055/com.example.user.recyclertest W/IInputConnectionWrapper: finishComposingText on inactive InputConnection
08-05 21:42:45.200 17055-17055/com.example.user.recyclertest D/FRAG_CALLBACK: forname: [Joe] surname: null
08-05 21:42:45.486 17055-17055/com.example.user.recyclertest W/IInputConnectionWrapper: finishComposingText on inactive InputConnection
08-05 21:42:45.487 17055-17055/com.example.user.recyclertest W/IInputConnectionWrapper: finishComposingText on inactive InputConnection
08-05 21:43:03.441 17055-17055/com.example.user.recyclertest D/FRAG_CONSTRUCTOR: forname: [Joe, ] surname: null
In your Activity's onCreate()
method, you write
fornames = new ArrayList<String>();
which assigns a new (empty) ArrayList
instance to your Activity's forenames
instance variable. You then write
FragRecycler.newInstance(surname, fornames)
which causes fornames
to be added to a new Fragment's "arguments" Bundle
b.putStringArrayList("FORNAMES", fornames);
which eventually is passed to your Adapter's constructor
adapter = new AdapterForName("Forename", getArguments().getStringArrayList("FORNAMES"));
where your Adapter assigns it to its names
instance variable:
this.names = names;
Your program then goes on to modify the Adapter's names
list as the user uses your app.
The key thing to realize here is that all of these bits of code are talking about the same ArrayList
instance. So when your app adds names or removes names from your Adapter's names
list, it is also adding and removing names from your Activity's fornames
list. This is happening because these two variables are pointing to the same object.
If you want to make sure that your Fragment can't modify your Activity's fornames
list instance, you should change your Fragment's newInstance()
method as follows. Replace this
b.putStringArrayList("FORNAMES", fornames);
with this:
List<String> fornamesCopy = new ArrayList<>(fornames);
b.putStringArrayList("FORNAMES", fornamesCopy);
The new
keyword means that now your Fragment has a different ArrayList instance than your Activity, so modifying this one won't affect the other. And the particular constructor used here will make sure that this new ArrayList instance still holds all the same values as the original.