Can't find where context is missing - android

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();

Related

Error Using RecyclerView and Fragment on android

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.

Presenter in MVP(Android) gets deleted/gets null inside Fragment

I am using MVP. My activity contains one Fragment. I am initializing and then setting presenter to fragment inside Main-Activity's on Create method as follow.
public class MainActivity extends AppCompatActivity {
private StashPresenter stashPresenter;
private MainFragment mainFragment;
FragmentManager fm;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
App.getInstance().getAppComponent().inject(this);
setContentView(R.layout.activity_main);
fm = getSupportFragmentManager();
fm.beginTransaction()
.add(R.id.fragment_container, MainFragment.newInstance(),"mainFragment")
.commitNow();
mainFragment = (MainFragment) fm.findFragmentById(R.id.fragment_container);
stashPresenter = new StashPresenter(mainFragment);
mainFragment.setPresenter(stashPresenter);
}
Inside my mainFrgament class I am settinf Presenter in setPresenterFunction as follow.
public class MainFragment extends Fragment implements
StashContract.PublishToView {
public StashContract.ToPresenter forwardInteraction;
public void setPresenter(StashContract.ToPresenter forwardInteraction)
{
this.forwardInteraction = forwardInteraction;
}
Sometimes while performing searching operation as shown in my following code inside OnCreateView of mainFragment, I gets an error saying my forward
"Attempt to invoke interface method on a null object reference"
Sometime I get this error, sometimes I do not. I do not understand why this is happening
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_main, container, false);
unbinder = ButterKnife.bind(this, view);
searchView.setOnEditorActionListener((v, actionId, event) -> {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
progressBar.setVisibility(View.VISIBLE);
forwardInteraction.searchButtonClick(actionId, searchView.getText().toString());
return true;
}
return false;
});
String[] artistNames = getResources().getStringArray(R.array.artistNamesSuggestion);
ArrayAdapter<String> adapterArtist = new ArrayAdapter<>(getActivity().getApplicationContext(), R.layout.fragment_main, R.id.search_phrase, artistNames);
searchView.setAdapter(adapterArtist);
searchView.setThreshold(1);
recyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3));
recyclerView.setAdapter(adapter);
recyclerView.addItemDecoration(new SpaceItemDecoration(space, space, space, space));
return view;
}
Solution
In the "onCreateView" method of the fragment, only initialize the views.
Move rest of the code in the "onResume" method.
Reason for error
Check this image
As you can see, "onCreateView" of your fragment is called when you are in the "onCreate" method in you activity.
In the current state of your code, there maybe times you when you will try to use the presenter before it is initialized.
Therefore, set your presented in the "onCreate" method of your activity and use it either it on the "onStart" or "onResume" of your fragment.
You can check this project to understand the MVP architecture more.

"Can not perform this action after onSaveInstanceState" when fragment is replaced

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/

Android - save/restore fragment state

I have an Activity in which I go through several fragments. In every fragment I have several views (EditText, ListView, Map, etc).
How can I save the instance of the fragment that is shown at that moment? I need it to work when the activity is onPause() --> onResume(). Also I need it to work when I return from another fragment (pop from backstack).
From the main Activity I call the first fragment, then from the the fragment I call the next one.
Code for my Activity:
public class Activity_Main extends FragmentActivity{
public static Fragment_1 fragment_1;
public static Fragment_2 fragment_2;
public static Fragment_3 fragment_3;
public static FragmentManager fragmentManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
fragment_1 = new Fragment_1();
fragment_2 = new Fragment_2();
fragment_3 = new Fragment_3();
fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction_1 = fragmentManager.beginTransaction();
transaction_1.replace(R.id.content_frame, fragment_1);
transaction_1.commit();
}}
Then here is the code for one of my fragments:
public class Fragment_1 extends Fragment {
private EditText title;
private Button go_next;
#Override
public View onCreateView(final LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_1,
container, false);
title = (EditText) rootView.findViewById(R.id.title);
go_next = (Button) rootView.findViewById(R.id.go_next);
image.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
FragmentTransaction transaction_2 = Activity_Main.fragmentManager
.beginTransaction();
transaction_2.replace(R.id.content_frame,
Activity_Main.fragment_2);
transaction_2.addToBackStack(null);
transaction_2.commit();
});
}}
I have searched a lot of information but nothing clear. Can somebody give a clear solution and an example, please ?
When a fragment is moved to the backstack, it isn't destroyed. All the instance variables remain there. So this is the place to save your data. In onActivityCreated you check the following conditions:
Is the bundle != null? If yes, that's where the data is saved (probably orientation change).
Is there data saved in instance variables? If yes, restore your state from them (or maybe do nothing, because everything is as it should be).
Otherwise your fragment is shown for the first time, create everything anew.
Edit: Here's an example
public class ExampleFragment extends Fragment {
private List<String> myData;
#Override
public void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable("list", (Serializable) myData);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
//probably orientation change
myData = (List<String>) savedInstanceState.getSerializable("list");
} else {
if (myData != null) {
//returning from backstack, data is fine, do nothing
} else {
//newly created, compute data
myData = computeData();
}
}
}
}
Android fragment has some advantages and some disadvantages.
The most disadvantage of the fragment is that when you want to use a fragment you create it ones.
When you use it, onCreateView of the fragment is called for each time. If you want to keep state of the components in the fragment you must save fragment state and yout must load its state in the next shown.
This make fragment view a bit slow and weird.
I have found a solution and I have used this solution: "Everything is great. Every body can try".
When first time onCreateView is being run, create view as a global variable. When second time you call this fragment onCreateView is called again you can return this global view. The fragment component state will be kept.
View view;
#Override
public View onCreateView(LayoutInflater inflater,
#Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
setActionBar(null);
if (view != null) {
if ((ViewGroup)view.getParent() != null)
((ViewGroup)view.getParent()).removeView(view);
return view;
}
view = inflater.inflate(R.layout.mylayout, container, false);
}
Try this :
#Override
protected void onPause() {
super.onPause();
if (getSupportFragmentManager().findFragmentByTag("MyFragment") != null)
getSupportFragmentManager().findFragmentByTag("MyFragment").setRetainInstance(true);
}
#Override
protected void onResume() {
super.onResume();
if (getSupportFragmentManager().findFragmentByTag("MyFragment") != null)
getSupportFragmentManager().findFragmentByTag("MyFragment").getRetainInstance();
}
Hope this will help.
Also you can write this to activity tag in menifest file :
android:configChanges="orientation|screenSize"
Good luck !!!
In order to save the Fragment state you need to implement onSaveInstanceState():
"Also like an activity, you can retain the state of a fragment using a Bundle, in case the activity's process is killed and you need to restore the fragment state when the activity is recreated. You can save the state during the fragment's onSaveInstanceState() callback and restore it during either onCreate(), onCreateView(), or onActivityCreated(). For more information about saving state, see the Activities document."
http://developer.android.com/guide/components/fragments.html#Lifecycle
As stated here: Why use Fragment#setRetainInstance(boolean)?
you can also use fragments method setRetainInstance(true) like this:
public class MyFragment extends Fragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// keep the fragment and all its data across screen rotation
setRetainInstance(true);
}
}
You can get current Fragment from fragmentManager. And if there are non of them in fragment manager you can create Fragment_1
public class MainActivity extends FragmentActivity {
public static Fragment_1 fragment_1;
public static Fragment_2 fragment_2;
public static Fragment_3 fragment_3;
public static FragmentManager fragmentManager;
#Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
setContentView(R.layout.main);
fragment_1 = (Fragment_1) fragmentManager.findFragmentByTag("fragment1");
fragment_2 =(Fragment_2) fragmentManager.findFragmentByTag("fragment2");
fragment_3 = (Fragment_3) fragmentManager.findFragmentByTag("fragment3");
if(fragment_1==null && fragment_2==null && fragment_3==null){
fragment_1 = new Fragment_1();
fragmentManager.beginTransaction().replace(R.id.content_frame, fragment_1, "fragment1").commit();
}
}
}
also you can use setRetainInstance to true what it will do it ignore onDestroy() method in fragment and your application going to back ground and os kill your application to allocate more memory you will need to save all data you need in onSaveInstanceState bundle
public class Fragment_1 extends Fragment {
private EditText title;
private Button go_next;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true); //Will ignore onDestroy Method (Nested Fragments no need this if parent have it)
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
onRestoreInstanceStae(savedInstanceState);
return super.onCreateView(inflater, container, savedInstanceState);
}
//Here you can restore saved data in onSaveInstanceState Bundle
private void onRestoreInstanceState(Bundle savedInstanceState){
if(savedInstanceState!=null){
String SomeText = savedInstanceState.getString("title");
}
}
//Here you Save your data
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("title", "Some Text");
}
}
I'm not quite sure if this question is still bothering you, since it has been several months. But I would like to share how I dealt with this.
Here is the source code:
int FLAG = 0;
private View rootView;
private LinearLayout parentView;
/**
* The fragment argument representing the section number for this fragment.
*/
private static final String ARG_SECTION_NUMBER = "section_number";
/**
* Returns a new instance of this fragment for the given section number.
*/
public static Fragment2 newInstance(Bundle bundle) {
Fragment2 fragment = new Fragment2();
Bundle args = bundle;
fragment.setArguments(args);
return fragment;
}
public Fragment2() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
Log.e("onCreateView","onCreateView");
if(FLAG!=12321){
rootView = inflater.inflate(R.layout.fragment_create_new_album, container, false);
changeFLAG(12321);
}
parentView=new LinearLayout(getActivity());
parentView.addView(rootView);
return parentView;
}
/* (non-Javadoc)
* #see android.support.v4.app.Fragment#onDestroy()
*/
#Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
Log.e("onDestroy","onDestroy");
}
/* (non-Javadoc)
* #see android.support.v4.app.Fragment#onStart()
*/
#Override
public void onStart() {
// TODO Auto-generated method stub
super.onStart();
Log.e("onstart","onstart");
}
/* (non-Javadoc)
* #see android.support.v4.app.Fragment#onStop()
*/
#Override
public void onStop() {
// TODO Auto-generated method stub
super.onStop();
if(false){
Bundle savedInstance=getArguments();
LinearLayout viewParent;
viewParent= (LinearLayout) rootView.getParent();
viewParent.removeView(rootView);
}
parentView.removeView(rootView);
Log.e("onStop","onstop");
}
#Override
public void onPause() {
super.onPause();
Log.e("onpause","onpause");
}
#Override
public void onResume() {
super.onResume();
Log.e("onResume","onResume");
}
And here is the MainActivity:
/**
* Fragment managing the behaviors, interactions and presentation of the
* navigation drawer.
*/
private NavigationDrawerFragment mNavigationDrawerFragment;
/**
* Used to store the last screen title. For use in
* {#link #restoreActionBar()}.
*/
public static boolean fragment2InstanceExists=false;
public static Fragment2 fragment2=null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
setContentView(R.layout.activity_main);
mNavigationDrawerFragment = (NavigationDrawerFragment) getSupportFragmentManager()
.findFragmentById(R.id.navigation_drawer);
mTitle = getTitle();
// Set up the drawer.
mNavigationDrawerFragment.setUp(R.id.navigation_drawer,
(DrawerLayout) findViewById(R.id.drawer_layout));
}
#Override
public void onNavigationDrawerItemSelected(int position) {
// update the main content by replacing fragments
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction=fragmentManager.beginTransaction();
switch(position){
case 0:
fragmentTransaction.addToBackStack(null);
fragmentTransaction.replace(R.id.container, Fragment1.newInstance(position+1)).commit();
break;
case 1:
Bundle bundle=new Bundle();
bundle.putInt("source_of_create",CommonMethods.CREATE_FROM_ACTIVITY);
if(!fragment2InstanceExists){
fragment2=Fragment2.newInstance(bundle);
fragment2InstanceExists=true;
}
fragmentTransaction.addToBackStack(null);
fragmentTransaction.replace(R.id.container, fragment2).commit();
break;
case 2:
fragmentTransaction.addToBackStack(null);
fragmentTransaction.replace(R.id.container, FolderExplorerFragment.newInstance(position+1)).commit();
break;
default:
break;
}
}
The parentView is the keypoint.
Normally, when onCreateView, we just use return rootView. But now, I add rootView to parentView, and then return parentView. To prevent "The specified child already has a parent. You must call removeView() on the ..." error, we need to call parentView.removeView(rootView), or the method I supplied is useless.
I also would like to share how I found it. Firstly, I set up a boolean to indicate if the instance exists. When the instance exists, the rootView will not be inflated again. But then, logcat gave the child already has a parent thing, so I decided to use another parent as a intermediate Parent View. That's how it works.
Hope it's helpful to you.
If you using bottombar and insted of viewpager you want to set custom fragment replacement logic with retrieve previously save state you can do using below code
String current_frag_tag = null;
String prev_frag_tag = null;
#Override
public void onTabSelected(TabLayout.Tab tab) {
switch (tab.getPosition()) {
case 0:
replaceFragment(new Fragment1(), "Fragment1");
break;
case 1:
replaceFragment(new Fragment2(), "Fragment2");
break;
case 2:
replaceFragment(new Fragment3(), "Fragment3");
break;
case 3:
replaceFragment(new Fragment4(), "Fragment4");
break;
default:
replaceFragment(new Fragment1(), "Fragment1");
break;
}
public void replaceFragment(Fragment fragment, String tag) {
if (current_frag_tag != null) {
prev_frag_tag = current_frag_tag;
}
current_frag_tag = tag;
FragmentManager manager = null;
try {
manager = requireActivity().getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
if (manager.findFragmentByTag(current_frag_tag) == null) { // No fragment in backStack with same tag..
ft.add(R.id.viewpagerLayout, fragment, current_frag_tag);
if (prev_frag_tag != null) {
try {
ft.hide(Objects.requireNonNull(manager.findFragmentByTag(prev_frag_tag)));
} catch (NullPointerException e) {
e.printStackTrace();
}
}
// ft.show(manager.findFragmentByTag(current_frag_tag));
ft.addToBackStack(current_frag_tag);
ft.commit();
} else {
try {
ft.hide(Objects.requireNonNull(manager.findFragmentByTag(prev_frag_tag)))
.show(Objects.requireNonNull(manager.findFragmentByTag(current_frag_tag))).commit();
} catch (NullPointerException e) {
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
Inside Child Fragments you can access fragment is visible or not using below method
note: you have to implement below method in child fragment
#Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
try {
if(hidden){
adapter.getFragment(mainVideoBinding.viewPagerVideoMain.getCurrentItem()).onPause();
}else{
adapter.getFragment(mainVideoBinding.viewPagerVideoMain.getCurrentItem()).onResume();
}
}catch (Exception e){
}
}

Multiple calls to FragmentTransaction.replace() - only one works after orientation change

I am using the following code to populate my UI with 2 fragments, the containers are FrameLayout's defined in XML. This first time this code is called i.e. when the app starts, it works fine, and both my fragments are displayed as expected. However after a configuration change(specifically, orientation), only the first fragment in the transaction is shown.
I don't think it's an issue with the fragments themselves, because if I reverse the code so one replace is called before the other or vice versa, that fragment will be displayed. So for example with the snippet from below as a guide, if I swap the mSummary and mDetails replace calls, then mDetails will be displayed and mSummary won't.
It's always the second one in the block that is missing.
// Using tablet layout
} else {
FragmentManager fm = super.getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.summary_container, mSummaryFragment);
ft.replace(R.id.details_container, mDetailsFragment);
ft.commit();
}
I'm saving the fragments in onSaveInstanceState and restoring them from the Bundle savedInstanceState when the activity is recreated. I also tried breaking the transaction into two pieces by calling commit() and then getting another FragmentTransaction object but no joy there.
So for anyone coming across this at a later stage...
I finally manage to fix this by creating a new instance of the fragment and restoring it's state using a Fragment.SavedState object. So:
if (mSummaryFragment.isAdded() && mDetailsFragment.isAdded()) {
Fragment.SavedState sumState = getSupportFragmentManager().saveFragmentInstanceState(mSummaryFragment);
Fragment.SavedState detState = getSupportFragmentManager().saveFragmentInstanceState(mDetailsFragment);
mSummaryFragment = new SummaryFragment();
mSummaryFragment.setInitialSavedState(sumState);
mDetailsFragment = new DetailsFragment();
mDetailsFragment.setInitialSavedState(detState);
}
FragmentTransaction ft = mFragmentManager.beginTransaction();
ft.add(R.id.summary_container, mSummaryFragment);
ft.add(R.id.details_container, mDetailsFragment);
ft.commit();
I do not understand why this works and the old method doesn't, however this may be helpful for someone else.
this should work and orientation change will not affect the fragment., if you face any problem just let me know.
public class MainActivity extends FragmentActivity {
Fragment fragment = new Fragment1();
Fragment fragment2=new Fragment2();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fm = super.getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.frame1, fragment);
ft.replace(R.id.frame2, fragment2);
ft.commit();
}
public void onSaveInstanceState(Bundle outState){
getSupportFragmentManager().putFragment(outState,"fragment1",fragment);
getSupportFragmentManager().putFragment(outState,"fragment2",fragment2);
}
public void onRetoreInstanceState(Bundle inState){
fragment = getSupportFragmentManager().getFragment(inState,"fragment1");
fragment2 = getSupportFragmentManager().getFragment(inState,"fragment2");
}
class Fragment1 extends Fragment{
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
ListView listView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view=inflater.inflate(R.layout.summary_view,container,false);
return view;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
}
}
class Fragment2 extends Fragment{
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
ListView listView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view=inflater.inflate(R.layout.detail_view,container,false);
return view;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
}
}
}

Categories

Resources