I'm trying to make an app where I've a RecyclerView with the options to remove the objects and add random numbers. What I'm trying to do is show a fragment when any member of the recycler view list is clicked. I'm getting the error " java.lang.IllegalStateException: Activity has been destroyed".
I'm pretty shure I'm doing something very wrong.
What I tried to do is putting a call to the change fragment method on my AnimalsAdapter class. I'm letting the code below and a github link.
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.berna.recyclerviewtp2, PID: 18705
java.lang.IllegalStateException: Activity has been destroyed
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:2114)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:683)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:637)
at com.example.berna.recyclerviewtp2.MainActivity.changeFragment(MainActivity.java:148)
at com.example.berna.recyclerviewtp2.AnimalsAdapter$1.onClick(AnimalsAdapter.java:81)
at android.view.View.performClick(View.java:6597)
at android.view.View.performClickInternal(View.java:6574)
at android.view.View.access$3100(View.java:778)
at android.view.View$PerformClick.run(View.java:25885)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
My Code Main class changeFragment:
public void changeFragment(View view){
Fragment fragment;
FragmentOne fragmentOne = new FragmentOne();
fragment = fragmentOne;
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragment,fragment);
ft.addToBackStack(null);
ft.commit();
FragmentOne class code:
public class FragmentOne extends Fragment {
#Override
public View onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstaceState){
//Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_blank,container,false);
}
}
Click Listener code:
// Set a click listener for TextView
holder.mTextView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
new MainActivity().changeFragment(view);
//String animal = mDataSet.get(position);
//Toast.makeText(mContext,animal,Toast.LENGTH_SHORT).show();
}
});
What I have to do to achieve the fragment when pressing the recyclerview member?
Original code I'm using to try what I'm trying
Github link for complete project
You should never instantiate your activity this way: new MainActivity().changeFragment(view);, it will never initialise properly. So it's either you delegate the listener, or find another work around for the callback.
For example, create an interface for callback:
interface ItemClickListener {
void onItemClick(View v);
}
Let your MainActivity implements the interface:
public class MainActivity extends Activity implements ItemClickListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ...
mAdapter = new AnimalsAdapter(mContext, animalsList, this);
// ...
}
#Override
public void onItemClick(View view) {
changeFragment(view);
}
}
Then allow your adapter to take ItemClickListener as a parameter:
private ItemClickListener callback;
public AnimalAdapter(Context context, List<String> data, ItemClickListener callback) {
this.callback = callback;
And let your holder.mTextView to forward the callback (back to activity):
holder.mTextView.setOnClickListener(callback::onItemClick);
You should create fragment after activity created, that is a basic knowledge about activity life cycle.
holder.mTextView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent intent = new Intent(your_current_activity, MainActivity.class);
intent.putExtras(extras);
startActivity(intent);
}
});
then replace fragment in onCreate() of MainActivity
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.your_activity_layout);
changeFragment(null); //because we don't using parameter view
}
If you have any problem else, please comment below.
You need two changes in your code.
1. Create an interface for click listener.
2. If you want to replace fragment then you need one more Layout in XML
We solve the first issue.
1. Create an interface for click listener.
Below are some change you need to do in your adapter,
Create Interface in your adapter
public interface RecyclerItemInteraction {
void onChangeFragmentClick();
}
The second change will pass this interface.
Deeclare gloabal variable
private RecyclerItemInteraction mInteraction;
public AnimalsAdapter(Context context,List list,RecyclerItemInteraction interaction){
mDataSet = list;
mContext = context;
mInteraction = interaction;
}
Replace this below line
new MainActivity().changeFragment(view); : Remove it
if (mInteraction!=null)mInteraction.onChangeFragmentClick(); add this line.
Now Go to your MainActivity and add below line
mAdapter = new AnimalsAdapter(mContext, animalsList,this);
You can see that add third parameter this.
Now override this method and enjoy your day.
#Override
public void onChangeFragmentClick() {
Fragment fragment;
FragmentOne fragmentOne = new FragmentOne();
fragment = fragmentOne;
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.container,fragment);
ft.addToBackStack(null);
ft.commit();
}
If you want to replace fragment then you need one more Layout in XML
Add this line to your main_activity.xml layout.
Related
I've been refactoring a previous project to use fragments instead of creating separate activities as a school assignment. I've been trying to find out where this error is for close to an hour now and no luck. I get context missing when I add this line canvas.setColor(color, position);
Here's the Main activity:
public class MainActivity extends AppCompatActivity implements PaletteFragment.SpinnerSelectedInterface {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
PaletteFragment palette = PaletteFragment.newInstance();
Bundle bundle = new Bundle();
bundle.putStringArray(KeyData.PASS_COLOR, getResources().getStringArray(R.array.colors));
bundle.putStringArray(KeyData.PASS_POSITION, getResources().getStringArray(R.array.colorNames));
palette.setArguments(bundle);
getSupportFragmentManager().beginTransaction().add(R.id.palette_fragment, palette).commit();
}
#Override
public void setCanvasColor(String color, int position) {
CanvasFragment canvas = CanvasFragment.newInstance(null);
getSupportFragmentManager().beginTransaction().add(R.id.canvas_fragment, canvas).addToBackStack(null).commit();
canvas.setColor(color, position);
}
}
And here's the fragment:
public class CanvasFragment extends Fragment {
private TextView displayColor;
private View background;
public CanvasFragment() {
// Required empty public constructor
}
public static CanvasFragment newInstance(Bundle bundle) {
CanvasFragment fragment = new CanvasFragment();
fragment.setArguments(bundle);
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_canvas, container, false);
displayColor = (TextView) v.findViewById(R.id.displayColor);
background = (View) v.findViewById(R.id.canvas_fragment);
return v;
}
public void setColor(String color, int position){
String[] names = getResources().getStringArray(R.array.colorNames);
displayColor.setText(names[position]);
background.setBackgroundColor(Color.parseColor(color));
}
I've tried override onAttach and onDetach on the canvas fragment and adding a message listener, but still get the error as well. Would appreciate anything that could steer me in the right direction.
Error
java.lang.IllegalStateException: Fragment CanvasFragment{69925d5 (240edefb-318c-4983-bd15-cf45142e849a) id=0x7f080047} not attached to a context.
When you call
getSupportFragmentManager()
.beginTransaction()
.add(R.id.canvas_fragment, canvas)
.addToBackStack(null)
.commit();
You're using commit(). As per its Javadoc:
Schedules a commit of this transaction. The commit does not happen immediately; it will be scheduled as work on the main thread to be done the next time that thread is ready.
So when you immediately call canvas.setColor(color, position) directly afterwards, the Fragment is not attached and it doesn't yet have a Context associated with it.
If you want the Fragment to be immediately added, you want to use commitNow(), which forces the transaction to happen immediately. This ensures that it'll be done before your setColor method is called:
getSupportFragmentManager()
.beginTransaction()
.add(R.id.canvas_fragment, canvas)
.addToBackStack(null)
.commitNow();
This one is a bit hard to explain and demonstrate. I will try my best.
I have two fragments ItemListFragment and ItemViewFragment : a fragment with a recycleview and listing inside the recycleview and a fragment displaying a single item respectively.
In the ItemListFragment, there is RecyclerViewClickListener to handle clicks on items of the list.
The implementation is as followed:
public class ItemListFragment extends Fragment {
private OnFragmentListClickListener onClickListener = null;
public interface OnFragmentListClickListener {
void OnFragmentListClick(ItemModel Item);
}
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
class RecyclerViewClickListenerImpl implements RecyclerViewClickListener {
#Override
public void onClick(View view, int position) {
if (onClickListener != null) {
onClickListener.OnFragmentListClick(adapter.getItem(position));
}
}
adapter = new ItemListAdapter(getActivity(), ItemModelList, new RecyclerViewClickListenerImpl());
mRecyclerView = getActivity().findViewById(R.id.recycler_view);
RecyclerView.LayoutManager mLayoutManager = new GridLayoutManager(getActivity(), 2);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
mRecyclerView.setAdapter(adapter);
.
.
.
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
Toast.makeText(getActivity(), "onAttach", Toast.LENGTH_SHORT).show();
if (context instanceof OnFragmentListClickListener) {
onClickListener = (OnFragmentListClickListener) context;
}
}
.
.
.
}
The list contains thumbnail (loaded with Glide) and a text for each time.
When I add this fragment in my activity implementing the ItemListFragment.OnFragmentListClickListener interface, everything works fine using the code below:
public class MainActivity extends AppCompatActivity
implements ItemListFragment.OnFragmentListClickListener {
.
.
.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ItemListFragment fragment_list = ItemListFragment.newInstance();
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.content_frame, fragment_list);
fragmentTransaction.commit();
}
#Override
public void OnFragmentListClick(CameraModel camera) {
// handle the list click
}
.
.
.
}
Things get weird when I try to add a second fragment (ItemViewFragment) in my activity as followed:
When there is a second fragment, the RecyclerViewClickListener's onClick on the ItemListFragment is not called anymore:
class RecyclerViewClickListenerImpl implements RecyclerViewClickListener {
#Override
public void onClick(View view, int position) {
if (onClickListener != null) {
onClickListener.OnFragmentListClick(adapter.getItem(position));
}
}
Also, certain thmbnails in the ItemListFragment stopped working when a second fragment has been added in the activity.
This is a weird one... I tried using fragmentTransaction.add instead of fragmentTransaction.add also and all kind of combinations...
You want put your onClick in item of recycle view. You should do every thing of item (like: load image, make event for view,... ) in ViewHolder class. Beacause that's easier to view source code in your activity or fragment. You can references my ViewHolder class with this link. It will help you.
I'm new in android. I have a navigation drawer where i used recyclerview for the item. I have three tabs representing three fragments in a viewpager. My problem is that i can not open a fragment by clicking recycler view item. And another problem is that I tried FragmentTransaction but don't know how to get the fragment id. Please help me i'm stuck on it.You can give me any tutorial link.
Here is my code...
In my recyclerview adapter i have tried:
public void onBindViewHolder(MyViewHandler holder, int position) {
final Information current = data.get(position);
holder.title.setText(current.title);
holder.icon.setImageResource(current.iconId);
holder.title.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
fragmentJump(current);
}
});
}
private void fragmentJump(Information mItemSelected) {
mFragment = new FragmentIncome();
mBundle = new Bundle();
mBundle.putParcelable("item_selected_key", mItemSelected);// here my Information class is not parcelable how to solve it?
mFragment.setArguments(mBundle);
switchContent(R.id.frag1, mFragment);/// Here how to get the fragment id in R.id.frag1 of viewpager
}
public void switchContent(int id, Fragment fragment) {
if (mContext == null)
return;
if (mContext instanceof MainActivity) {
MainActivity mainActivity = (MainActivity) mContext;
Fragment frag = fragment;
mainActivity.switchContent(id, frag);
}
}
And in the MainActivity i have created this method:
public void switchContent(int id, Fragment fragment) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(id, fragment, fragment.toString());
ft.addToBackStack(null);
ft.commit();
}
I have followed this link how to open a different fragment on recyclerview OnClick
You can do this by using interface callback when a recyclerview item is clicked:
You could do this:
class NavdrawerRecyclerAdapter extends RecyclerView.Adapter<> {
private OnFragmentInteractionListener mListener;
public NavdrawerRecyclerAdapter(OnFragmentInteractionListener listener){
mListner = listner;
}
//call and pass position inside onClick
public void onRecyclerItemClicked(int pos){
mListener.openFragment(pos);
}
public interface OnFragmentInteractionListener{
public void openFragment(int pos);
}}
Now your mainactivity which has tablayouts implement this interface.
public class MainActivity extends AppCompatActivity implements NavdrawerRecyclerAdapter.OnFragmentInteractionListener{
NavdrawerRecyclerAdapter adapter = new NavdrawerRecyclerAdapter(this);
#override
openFragment(int position){
TabLayout.Tab tab = tabLayout.getTabAt(position);
tab.select();
}
}
Try it and tell me if this works.
In my project context, I have a Button b in a Fragment f(1) in an Activity a.
Fragment f(x) is an instance of F where content depends of argument x
I need to replace the current instance f(1) by an instance f(2) on b click event:
From Activity a:
private void setFragment(int x) {
Bundle args = new Bundle();
args.putInt("x", x);
F f = new F();
f.setArguments(args);
f.setListener(new F.Listener() {
public void onButtonClick(int x) {
setFragment(x);
}
});
getSupportFragmentManager()
.beginTransaction()
.replace(ID, f)
.commit();
}
From Fragment f:
b.setOnClickListener(new View.onClickListener() {
public void onClick(View view) {
listener.onButtonClick(x + 1);
}
});
My problem is:
An Exception is throw on b click event only if a configuration state change occurs:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
Please, what is my error? I read many posts on this Exception but I don't found any solution
Edit: I just make a test without AsyncTask, see the code:
Try to rotate the screen and push the button
public class MainActivity extends ActionBarActivity {
#Override
protected void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_main);
if (state == null) {
setFragment(1);
}
}
private void setFragment(int id) {
Bundle args = new Bundle();
args.putInt("id", id);
MyFragment myFragment = new MyFragment();
myFragment.setArguments(args);
myFragment.setListener(new MyFragment.Listener() {
#Override
public void onClick(int id) {
setFragment(id);
}
});
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment, myFragment)
.commit();
}
public static class MyFragment extends Fragment{
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup view, Bundle state) {
return new Button(getActivity()) {
{
setText(String.valueOf(getArguments().getInt("id")));
setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
listener.onClick(getArguments().getInt("id") + 1);
}
});
}
};
}
private static interface Listener {
public void onClick(int id);
}
private Listener listener;
public void setListener(Listener listener) {
this.listener = listener;
}
}
}
The problem is the way you are setting the listener.
You are setting the listener, then you rotate your device from landscape to portrait. What happens after the rotation is:
Android create a brand new instance of MainActivity.
FragmentManager create a new instance of MyFragment internally and re-adds it automatically to the activity as it was before orientation change.
If you click on the button, the listener will be called. However, the listener is the listener of the previous activity (before rotation) which has been destroyed.
So not only you have a Memory Leak (the old activity can not be garbage collected, because it's referenced from here) but you also get this error.
How to do it correctly:
Well, the problem is NOT only setRetainInstanceState() you have not understood the Android Fragments lifecycle correctly. As mentioned above, Fragments are controlled by the FragmentManager (FragmentTransaction). So, yes everytime you rotate your screen a new Fragment instance will be created, but FragmentManager will attach them automatically for you (it's a little bit strange, but thats how Fragment works)
I would recommend to use an EventBus. The fragment will fire an Event onClick() and the activity will receive this event since it's subscribed. I recomment GreenDao EventBus.
Otherwise you can have a look at the official docs, but from my point of view they are teaching not a good solution, because your fragment and activity are hardly connected (not modular). They say you should use onAttach() like you can see in the sample from the documentation: http://developer.android.com/guide/components/fragments.html#EventCallbacks
Btw. a similar problem can occur if you are not using Fragment arguments for "passing" data. For more details read this blog: http://hannesdorfmann.com/android/fragmentargs/
I have a Fragment with a Button embedded inside of a FragmentActivity. When I click the button I want the Fragment to be replaced with another Fragment. The problem is: the Fragment is a inner static class of my Activity and the method is a non-static one. I solved this problem by making an instance of my Activity class, but when I click on the button inside the fragment the application crashes.
Here is the code:
public class Stdp extends SherlockFragmentActivity implements ActionBar.OnNavigationListener {
public static class Bottomfrag extends SherlockFragment {
static Bottomfrag newInstance() {
Bottomfrag f = new Bottomfrag();
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.bottom_choose, container, false);
View li = v.findViewById(R.id.layoutbottom);
li.setBackgroundColor(Color.parseColor("#FFBB33"));
View button = v.findViewById(R.id.button1);
button.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View arg0) {
Stdp t = new Stdp();
t.addFragmentToStack();
}
});
return v;
}
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_stdp);
Context context = getSupportActionBar().getThemedContext();
ArrayAdapter<CharSequence> list = ArrayAdapter.createFromResource(context, R.array.test_array, R.layout.sherlock_spinner_item);
list.setDropDownViewResource(R.layout.sherlock_spinner_dropdown_item);
getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
getSupportActionBar().setListNavigationCallbacks(list, this);
if (savedInstanceState == null) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
Fragment bottom = new Bottomfrag();
ft.add(R.id.su1, bottom);
ft.commit();
}
}
#Override
public boolean onNavigationItemSelected(int itemPosition, long itemId) {
// TODO Auto-generated method stub
return false;
}
public void addFragmentToStack() {
Fragment newFragment = Bottomfrag.newInstance();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.su1, newFragment);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE);
ft.addToBackStack(null);
ft.commit();
}
}
I found a solution for my problem. The only thing I had to do is to move the addFragmentToStack to the Bottomfrag class.
I solved this problem by making an instance of my Activity class.
Instantiating an Activity is almost never a solution when it comes to Android development... in fact, I can't imagine a scenario in which you would ever want to create a new Activity using the default constructor.
You can reference the static instance of your Activity with ActivityName.this.
You should also move addFragmentToStack to the Bottomfrag class.