I am new to android, and i am facing this problem when the screen orientation is changed. The fragment gets called twice whenever screen orientation changes. Below is the sample of my code. I checked other posts, but couldnt find answer. Anyone guide me through this.
public class SampleFragment extends Fragment {
static final String TAG_NAME = SampleFragment.class.getSimpleName();
List<PhrToolBar> mToolBarList;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
DaggerHelper.getAppProviderComponent().inject(this);
mRootView = null;
getActivity().setTitle("Personal Health Records");
mRootView = inflater.inflate(R.layout.sample_phr_main_fragment, container, false);
mBinding = DataBindingUtil.bind(mRootView);
mBinding.setViewModel(mViewModel);
setHasOptionsMenu(true);
return mRootView;
}
Simple add this code
if (savedInstanceState == null) {
// only create fragment if activity is started for the first time
mFragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
FragmentOne fragment = new FragmentOne();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
} else {
// do nothing - fragment is recreated automatically
}
Related
Basically, I have a MainActivity that displays different Fragments when clicked on menu options.
Stats, is a Fragment that displays 4 fragments in it. Every time it is displayed, will replace 4 FrameLayouts on the view.
The first time work perfectly, but when I change to different fragments and get back in Stats, it seems not to replace the FrameLayouts to Fragments...
Stats as follows:
public class StatsFragment extends Fragment {
private View rootView;
private MoreSoldBarChartFragment moreSold = MoreSoldBarChartFragment.newInstance(null);
private MoreIncomeBarChartFragment moreIncome = MoreIncomeBarChartFragment.newInstance(null);
private SoldLineChartFragment soldLine = SoldLineChartFragment.newInstance(null);
private IncomeLineChartFragment incomeLine = IncomeLineChartFragment.newInstance(null);
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
rootView = inflater.inflate(R.layout.fragment_stats, container, false);
initViews();
return rootView;
}
public void initViews() {
replace(moreIncome, R.id.moreIncome);
replace(moreSold, R.id.moreSold);
replace(soldLine, R.id.soldLine);
replace(incomeLine, R.id.incomeLine);
}
private void replace(Fragment fragmentToReplace, int container) {
FragmentTransaction ft = getActivity().getSupportFragmentManager().beginTransaction();
ft.replace(container, fragmentToReplace);
ft.commit();
}
public static StatsFragment newInstance(Bundle args) {
StatsFragment fragment = new StatsFragment();
fragment.setHasOptionsMenu(true);
fragment.setArguments(args);
return fragment;
}
}
You should be using getChildFragmentManager() to display Fragments within Fragments:
private void replace(Fragment fragmentToReplace, int container) {
FragmentTransaction ft = getChildFragmentManager().beginTransaction();
ft.replace(container, fragmentToReplace);
ft.commit();
}
As that ensures that the Fragments are properly handled when the parent's state is changed (i.e., when it is swapped with another Fragment and back). When you use getActivity.getSupportFragmentManager(), Fragments have no idea that these Fragments are related and can't restore the Fragments.
I'm building an application that entirely consists of fragments and a single activity. I have this usecase where every fragment should be initialized once and everytime it is replaced it gets resume/paused. I thought having a single instance of fragment would do the work but its not happening as expected.
I want to know how to achieve this so that only one instance of fragment gets resume/pause everytime.
Use this method to switch Fragments
void switchFragment(#NonNull Fragment fragment, boolean addToBackStack) {
final String NAME = fragment.getClass().getName();
final FragmentManager fm = getSupportFragmentManager();
final boolean fragmentPopped = fm.popBackStackImmediate(NAME, 0);
if (fragmentPopped || fm.findFragmentByTag(NAME) != null) {
return;
}
if (addToBackStack) {
fm.beginTransaction()
.replace(mContainerResId, fragment, NAME)
.commit();
} else {
fm.beginTransaction()
.replace(mContainerResId, fragment, NAME)
.addToBackStack(NAME)
.commit();
}
}
And in Fragment class create view like below
private View rootView;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
if (rootView == null) {
rootView = inflater.inflate(R.layout.your_layout, container, false);
}
return rootView;
}
I'm using some fragments programmatically in activity. There is one button in my first fragment and when i click to this button, it replaces to second fragment.My second fragment's background is 90% transparent, and when it starts, i can see button which is situated in first fragment, and it also works. I want to stop or do something, because i dont want to see first fragment features and use it.
First Fragment
public class RegistrationFirstFragment extends Fragment {
RegistrationSecondFragment rf;
ImageButton btnNewUser,btnNewAgent;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//return super.onCreateView(inflater, container, savedInstanceState);
View v =inflater.inflate(R.layout.fragment_registration_first,container,false);
rf = new RegistrationSecondFragment();
btnNewUser = (ImageButton)v.findViewById(R.id.btnNewUserRegistrationFirstFragment);
btnNewAgent = (ImageButton)v.findViewById(R.id.btnNewAgentRegistrationFirstFragment);
btnNewUser.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(getActivity(), "Transaction completed succesfully", Toast.LENGTH_LONG).show();
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.flRegistrationFirst, rf);
ft.commit();
}
});
return v;
}
}
Second Fragment
public class RegistrationSecondFragment extends Fragment {
RegistrationFirstFragment rtl;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
rtl = new RegistrationFirstFragment();
//return super.onCreateView(inflater, container, savedInstanceState);
View v =inflater.inflate(R.layout.fragment_registration_second,container,false);
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
// ft.replace(R.id.flRegistrationFirst, rf);
ft.remove(rtl);
ft.commit();
return v;
}
}
Main Activity
public class RegistrationActivity extends AppCompatActivity{
RegistrationFirstFragment fr;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_registration);
fr = new RegistrationFirstFragment();
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.flRegistrationFragment,fr);
ft.commit();
}
}
You can put
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();
(the parameter fragment would be an instance of your second fragment)
into your onClick(View view){...} method to change the fragment instead of adding it.
Next time code for understanding your problem btw ;)
Add to fragment layout android:clickable="true". Int his way fragment will catch event so the click will not be caught by "main fragment".
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clickable="true" />
Give android:clickable="true" for Second Fragment root layout parent, when ever fragment opens It catches the click event of root and ignored previous click events.
Second One: If u use replace Fragment it's better than add fragment.
getActivity().getSupportFragmentManager().beginTransaction().replace
(R.id.YOUR_CONTAINER, 'FragmentObject').addToBackStack("TAG").commitAllowingStateLoss();
My Fragment
public class CustomFrag extends Fragment {
private Button btn;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.button_fragment, container, false);
btn = (Button)view.findViewById(R.id.button1);
return view;
}
public void sendItem(String item) {
btn.setText(item);
}
}
And in my activity
public void loadFragment(String data) {
// Load up new fragment
Fragment fragment = new CustomFrag();
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction transaction = fm.beginTransaction();
transaction.add(R.id.contentFragment, fragment, "custFrag");
transaction.addToBackStack("custFrag");
transaction.commit();
// Required before calling fragment methods
getSupportFragmentManager().executePendingTransactions();
// Load fragment with data
CustomFrag frag = (CustomFrag) getSupportFragmentManager().findFragmentByTag("custFrag");
frag.sendItem(data);
}
I'm getting a nullpointer exception any time I attempt to use the views of my fragment. If I try to load the view inside the method as well, it will not work
i.e. inside sendItem()
btn = (Button)getView().findViewById(R.id.button1);
My layout (button_fragment) contains the button:
<Button
android:id="#+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
Because you have executed the transaction does not mean that the fragment has actually created its view. Which is why btn is still null.
To pass data from the activity to the fragment, use the argument bundle:
Fragment fragment = new CustomFrag();
Bundle args = new Bundle();
args.putString("item", data);
fragment.setArguments(args);
Then, in onCreateView:
btn = (Button)view.findViewById(R.id.button1);
btn.setText(getArguments().getString("item"));
See this Best practice for instantiating a new Android Fragment question and the first answer.
The problem here is that the fragment's layout is not drawn yet when sendItem(...) is called. Which means btn is null at that point. Instead, this is how you're supposed to do it (see http://developer.android.com/guide/components/fragments.html):
public class CustomFrag extends Fragment {
private Button btn;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.button_fragment, container, false);
btn = (Button)view.findViewById(R.id.button1);
btn.setText(getArguments.getString("item"));
return view;
}
}
And
public void loadFragment(String data) {
// Load up new fragment
Fragment fragment = new CustomFrag();
Bundle args = new Bundle();
args.putString("item", data);
fragment.setArguments(args);
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction transaction = fm.beginTransaction();
transaction.add(R.id.contentFragment, fragment, "custFrag");
transaction.addToBackStack("custFrag");
transaction.commit();
// Required before calling fragment methods
getSupportFragmentManager().executePendingTransactions();
}
Edit:
njzk2 was faster, but I hope the details I gave will help you further. In any case, the link he gave explains nicely why you should do it like that.
I have a fragment (let's call it MyFragment) that inflates different layouts according to a parameter passed in the arguments.
All works well if MyFragment is started from a different fragment. But if MyFragment is active and I want to launch a new MyFragment with a different layout parameter, the fragmentManager does not create a new fragment at all.
data.setInt("Layout index",i);
fragmentTab0 = (Fragment) new MyFragment();
fragmentTab0.setArguments(data);
fragmentTransaction.replace(R.id.fragmentContent, fragmentTab0, "MY");
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
How can I force convince the fragmentTransaction to launch the fragment again?
NOTE: The important point here is I need to inflate again a layout, which is different from the layout inflated before. The code looks like:
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
switch( getArguments().getInt("Layout index") ) {
case 1:
rootView = inflater.inflate(R.layout.firstlayout, container, false);
break;
case 2:
rootView = inflater.inflate(R.layout.secondlayout, container, false);
break;
case 3:
rootView = inflater.inflate(R.layout.thirdlayout, container, false);
break;
default: break;
}
Bypass Solution
Explanation (hoover to see it)
Since the source code for fragmentTransaction.replace/add/remove is
not available I could not find what really happens. But it is
reasonable to think that at some point it compares the current class
name with the replacement class name and it exits if they are the
same.. Thanks to #devconsole for pointing out the source
code. I know now why this happens. The FragmentManager.removeFragment()
method does not reset the fragment state, it remains RESUMED, then the
method moveToState(CREATED) only initilizes a fragment if (f.mState <
newState) = if (RESUMED < CREATED) = false. Else, ergo, it just resumes the fragment.
So to solve this problem I created an almost empty fragment which only purpose is to replace itself with the target fragment.
public class JumpFragment {
public JumpFragment() {
}
#Override
public void onStart() {
Bundle data = getArguments();
int containerId = data.getString("containerID");
String tag = data.getString("tag");
//Class<?> c = data.get???("class");
//Fragment f = (Fragment) c.newInstance();
Fragment f = (Fragment) new MyFragment();
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
f.setArguments(data);
fragmentTransaction.replace(containerId, f, tag);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
}
}
And I use it:
data.setInt("Layout index",i);
data.setInt("containerID",R.id.fragmentContent);
data.setString("tag","MY");
fragmentTab0 = (Fragment) new JumpFragment();
fragmentTab0.setArguments(data);
fragmentTransaction.replace(R.id.fragmentContent, fragmentTab0);
fragmentTransaction.commit();
Now no fragment is replaced by a same class fragment:
MyFragment -> JumpFragment -> MyFragment
I haven't figured out how to pass the class through the arguments bundle, to make it totally generic.
The following worked without problems for me. Notice that I used the Android Support Library.
activity_main.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<Button
android:id="#+id/button_one"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ONE" />
<Button
android:id="#+id/button_two"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="#id/button_one"
android:text="TWO" />
<FrameLayout
android:id="#+id/main_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#id/button_one" >
</FrameLayout>
</RelativeLayout>
MainActivity.java:
public class MainActivity extends FragmentActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
Fragment fragment = DetailsFragment.newInstance("INITIAL");
transaction.add(R.id.main_container, fragment);
transaction.commit();
}
findViewById(R.id.button_one).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
update("Button 1 clicked");
}
});
findViewById(R.id.button_two).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
update("Button 2 clicked");
}
});
}
protected void update(String value) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
Fragment fragment = DetailsFragment.newInstance(value);
transaction.replace(R.id.main_container, fragment);
transaction.commit();
}
public static final class DetailsFragment extends Fragment {
public static DetailsFragment newInstance(String param) {
DetailsFragment fragment = new DetailsFragment();
Bundle args = new Bundle();
args.putString("param", param);
fragment.setArguments(args);
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
TextView textView = new TextView(container.getContext());
textView.setText(getArguments().getString("param"));
return textView;
}
}
}
Did you try to first remove your fragment with remove(fragMgr.findFragmentByTag("MY"))and then add the new one ?
PS : I assume you don't keep any reference to this fragment.
If I understand you correctly: the fragment you want to replace what is currently being displayed and the user does something to cause it to re-display itself?
If this is correct then have done something similar this way:
public class MyFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
final View V = inflater.inflate(R.layout.myLayout, container, false);
// Call method that fills the layout with data
displayData(V);
// Put a listener here that checks for user input
Button redisplayButton = (Button) V.findViewById(R.id.my_button);
// if the button is clicked....
redisplayButton.setOnClickListener(new OnClickListener() {
public void onClick(View v){
//
// do some stuff
//
// ....then eventually...
displayData(V);
}
});
return V;
}
Later on you can have the displayData() method that defines what the fragment displays....
public void displayData(View V){
// Do something
return;
}
Hope this helps!