There is a simple project (full listing below).

In project,

  • There is an Activity with ListView. Each element of the list is a button - clicking on which brings up the context menu.

  • in the MainActivity class there is a tagButton field that remembers the position of the element whose button was pressed.

  • The adapter is custom, menu items are drawn inside the adapter, and the Tag field of the button is assigned the position of a specific item in the list.

  • When you rotate the screen, the adapter is saved with a fragment

Before the screen rotates, everything works (pressing the button -> saving the position in the tagButton -> menu display -> getting the correct tagButton).

But if you turn the screen, it turns out something incomprehensible to me: pressing the button -> saving the correct position in tagButton -> displaying the menu -> getting the wrong tagButton ?!

What happens between “storing the correct position in the tagButton” and “getting the wrong tagButton”?

Ps. If you wrap the tagButton into a simple class and change / get the value using the setter / getter and at the same time save an instance of the class through a fragment (when you turn the screen), then everything is fine.

Pps. Algorithm for receiving errors:

  1. Run the application.
  2. Press any button (for example, 3), a menu appears. Logs: Tag before: 3
  3. Select a menu item. In Tag after logs: 3
  4. Turn over the screen.
  5. Press any button (for example, 2), a menu appears. Logs: Tag before: 2
  6. Select a menu item. Tag after logs: -1

MainActivity code:

import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.ContextMenu; import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.ListView; import layout.SaveFragment; public class MainActivity extends AppCompatActivity { int tagButton = -1; LvAdapter adapter; SaveFragment saveStateFragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); saveStateFragment = (SaveFragment) getSupportFragmentManager().findFragmentByTag("SAVE"); if (saveStateFragment != null) { adapter = saveStateFragment.getAdapter(); } else { saveStateFragment = new SaveFragment(); getSupportFragmentManager() .beginTransaction() .add(saveStateFragment, "SAVE") .commit(); adapter = new LvAdapter(this); } tagButton = -1; ((ListView) findViewById(R.id.lv1)).setAdapter(adapter); } @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { tagButton = (int) ((Button) v).getTag(); Log.d("err", "Tag before: " + String.valueOf(tagButton)); menu.add("Получить Tag"); } @Override public boolean onContextItemSelected(MenuItem item) { Log.d("err", "Tag after: " + String.valueOf(tagButton)); return super.onContextItemSelected(item); } @Override protected void onPause() { saveStateFragment.setAdapter(adapter); super.onPause(); } } 

Adapter code:

 import android.app.Activity; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.Button; public class LvAdapter extends BaseAdapter { int count = 100; Context ctx; LayoutInflater lInflater; public LvAdapter (Context context) { ctx = context; lInflater = (LayoutInflater) ctx .getSystemService(Context.LAYOUT_INFLATER_SERVICE); } @Override public int getCount() { return count; } @Override public Integer getItem(int i) { return i; } @Override public long getItemId(int i) { return i; } @Override public View getView(int i, View convertView, ViewGroup viewGroup) { View view = convertView; if (view == null) { view = lInflater.inflate(R.layout.item, viewGroup, false); } final Button btn = (Button) view.findViewById(R.id.btn); btn.setText(String.valueOf(i)); btn.setTag(i); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Activity activity = (Activity) ctx; activity.registerForContextMenu(btn); activity.openContextMenu(btn); } }); return view; } } 

Fragment code:

 import android.os.Bundle; import android.support.v4.app.Fragment; import biz.temp.LvAdapter; public class SaveFragment extends Fragment { LvAdapter adapter; public LvAdapter getAdapter() { return adapter; } public void setAdapter(LvAdapter adapter) { this.adapter = adapter; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); } } 

Activity Markup:

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal"> <ListView android:id="@+id/lv1" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout> 

The layout of the list item:

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/btn"/> </LinearLayout> 
  • one
    I guess the problem is in the adapter. You can easily have a link to the context, moreover, each time you press a button, you create a new link, and only in this case the context menu is lost. And this seems strange to me. When using, for example, RecyclerView you simply would have had enough of ViewHolder get a button from each element, and tag it there, without forwarding and re-assigning links. - Silento

2 answers 2

After turning, your adapter remains old, because for some reason it is saved in the retain fragment, but at the same time it keeps a link to the copy of the old activit and works with it. It's amazing that everything here doesn't crash when the methods of the old activit are called.

So here are some tips:

  1. NEVER save the link to the activation, the fragment, the context (except Aplication) and the service to a class that will live longer than these components if it does not delete this link if that component is deactivated. Otherwise strange colors are the best that you provided. At worst, kill hours if not days in search of a problem because of which the application behaves like something strange.
  2. setRetainInstance came up with the devil, saving the state through such fragments is fraught with many problems. To save the state, there is onSaveInstanceState, if there is a lot of data to save, then most likely they need to be saved to disk in SharedPrefereces, a database or just to a file. setRetainInstance made some sense when working with asink tasks, but they themselves are very inconvenient and on different versions of the android behave very differently, so that experienced developers have not used them for a long time because there are a lot of proven, popular and very convenient alternatives.
  • Yes. Indeed, the problem was in Activity. I rewrote the adapter class and the Activity. Now they interact through the interface. As far as I understand this is the right and safe way to interact? - AndreyEKB
  • And the correctness and safety of this method is completely dependent on the implementation) - xkor

Rewrote class Activity and Adapter. In particular, they now interact through the onSetButtonClickListener interface. With this approach, everything works ...

Adapter Class:

  public class LvAdapter extends BaseAdapter { public interface onSetButtonClickListener { public void setButtonClickListener(Button btn); } onSetButtonClickListener iActivity; public void setSetClickListener(onSetButtonClickListener iActivity) { this.iActivity = (onSetButtonClickListener) iActivity; } int count = 100; Context ctx; LayoutInflater lInflater; public LvAdapter (Context context) { ctx = context; lInflater = (LayoutInflater) ctx .getSystemService(Context.LAYOUT_INFLATER_SERVICE); } @Override public int getCount() { return count; } @Override public Integer getItem(int i) { return i; } @Override public long getItemId(int i) { return i; } @Override public View getView(int i, View convertView, ViewGroup viewGroup) { View view = convertView; if (view == null) { view = lInflater.inflate(R.layout.item, viewGroup, false); } final Button btn = (Button) view.findViewById(R.id.btn); btn.setText(String.valueOf(i)); btn.setTag(i); iActivity.setButtonClickListener(btn); return view; } } 

Activity class:

  public class MainActivity extends AppCompatActivity implements LvAdapter.onSetButtonClickListener { int tagButton = -1; LvAdapter adapter; SaveFragment saveStateFragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); saveStateFragment = (SaveFragment) getSupportFragmentManager().findFragmentByTag("SAVE"); if (saveStateFragment != null) { adapter = saveStateFragment.getAdapter(); } else { saveStateFragment = new SaveFragment(); getSupportFragmentManager() .beginTransaction() .add(saveStateFragment, "SAVE") .commit(); adapter = new LvAdapter(this); } tagButton = -1; adapter.setSetClickListener(this); ((ListView) findViewById(R.id.lv1)).setAdapter(adapter); } @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { tagButton = (int) ((Button) v).getTag(); Log.d("err", "Tag before: " + String.valueOf(tagButton)); menu.add("Получить Tag"); } @Override public boolean onContextItemSelected(MenuItem item) { Log.d("err", "Tag after: " + String.valueOf(tagButton)); return super.onContextItemSelected(item); } @Override protected void onPause() { saveStateFragment.setAdapter(adapter); super.onPause(); } @Override public void setButtonClickListener(Button btn) { registerForContextMenu(btn); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { openContextMenu(view); } }); } }