I've written up a dummy activity that switches between two fragments. When you go from FragmentA to FragmentB, FragmentA gets added to the back stack. However, when I return to FragmentA (by pressing back), a totally new FragmentA is created and the state it was in is lost. I get the feeling I'm after the same thing as this question, but I've included a complete code sample to help root out the issue:
public class FooActivity extends Activity {
#Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(android.R.id.content, new FragmentA());
transaction.commit();
}
public void nextFragment() {
final FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(android.R.id.content, new FragmentB());
transaction.addToBackStack(null);
transaction.commit();
}
public static class FragmentA extends Fragment {
#Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View main = inflater.inflate(R.layout.main, container, false);
main.findViewById(R.id.next_fragment_button).setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
((FooActivity) getActivity()).nextFragment();
}
});
return main;
}
#Override public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Save some state!
}
}
public static class FragmentB extends Fragment {
#Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.b, container, false);
}
}
}
With some log messages added:
07-05 14:28:59.722 D/OMG ( 1260): FooActivity.onCreate
07-05 14:28:59.742 D/OMG ( 1260): FragmentA.onCreateView
07-05 14:28:59.742 D/OMG ( 1260): FooActivity.onResume
<Tap Button on FragmentA>
07-05 14:29:12.842 D/OMG ( 1260): FooActivity.nextFragment
07-05 14:29:12.852 D/OMG ( 1260): FragmentB.onCreateView
<Tap 'Back'>
07-05 14:29:16.792 D/OMG ( 1260): FragmentA.onCreateView
It's never calling FragmentA.onSaveInstanceState and it creates a new FragmentA when you hit back. However, if I'm on FragmentA and I lock the screen, FragmentA.onSaveInstanceState does get called. So weird...am I wrong in expecting a fragment added to the back stack to not need re-creation? Here's what the docs say:
Whereas, if you do call addToBackStack() when removing a fragment,
then the fragment is stopped and will be resumed if the user navigates
back.
If you return to a fragment from the back stack it does not re-create the fragment but re-uses the same instance and starts with onCreateView() in the fragment lifecycle, see Fragment lifecycle.
So if you want to store state you should use instance variables and not rely on onSaveInstanceState().
Comparing to Apple's UINavigationController and UIViewController, Google does not do well in Android software architecture. And Android's document about Fragment does not help much.
When you enter FragmentB from FragmentA, the existing FragmentA instance is not destroyed. When you press Back in FragmentB and return to FragmentA, we don't create a new FragmentA instance. The existing FragmentA instance's onCreateView() will be called.
The key thing is we should not inflate view again in FragmentA's onCreateView(), because we are using the existing FragmentA's instance. We need to save and reuse the rootView.
The following code works well. It does not only keep fragment state, but also reduces the RAM and CPU load (because we only inflate layout if necessary). I can't believe Google's sample code and document never mention it but always inflate layout.
Version 1(Don't use version 1. Use version 2)
public class FragmentA extends Fragment {
View _rootView;
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (_rootView == null) {
// Inflate the layout for this fragment
_rootView = inflater.inflate(R.layout.fragment_a, container, false);
// Find and setup subviews
_listView = (ListView)_rootView.findViewById(R.id.listView);
...
} else {
// Do not inflate the layout again.
// The returned View of onCreateView will be added into the fragment.
// However it is not allowed to be added twice even if the parent is same.
// So we must remove _rootView from the existing parent view group
// (it will be added back).
((ViewGroup)_rootView.getParent()).removeView(_rootView);
}
return _rootView;
}
}
------Update on May 3 2005:-------
As the comments mentioned, sometimes _rootView.getParent() is null in onCreateView, which causes the crash. Version 2 removes _rootView in onDestroyView(), as dell116 suggested. Tested on Android 4.0.3, 4.4.4, 5.1.0.
Version 2
public class FragmentA extends Fragment {
View _rootView;
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (_rootView == null) {
// Inflate the layout for this fragment
_rootView = inflater.inflate(R.layout.fragment_a, container, false);
// Find and setup subviews
_listView = (ListView)_rootView.findViewById(R.id.listView);
...
} else {
// Do not inflate the layout again.
// The returned View of onCreateView will be added into the fragment.
// However it is not allowed to be added twice even if the parent is same.
// So we must remove _rootView from the existing parent view group
// in onDestroyView() (it will be added back).
}
return _rootView;
}
#Override
public void onDestroyView() {
if (_rootView.getParent() != null) {
((ViewGroup)_rootView.getParent()).removeView(_rootView);
}
super.onDestroyView();
}
}
WARNING!!!
This is a HACK! Though I am using it in my app, you need to test and read comments carefully.
I guess there is an alternative way to achieve what you are looking for.
I don't say its a complete solution but it served the purpose in my case.
What I did is instead of replacing the fragment I just added target fragment.
So basically you will be going to use add() method instead replace().
What else I did.
I hide my current fragment and also add it to backstack.
Hence it overlaps new fragment over the current fragment without destroying its view.(check that its onDestroyView() method is not being called. Plus adding it to backstate gives me the advantage of resuming the fragment.
Here is the code :
Fragment fragment=new DestinationFragment();
FragmentManager fragmentManager = getFragmentManager();
android.app.FragmentTransaction ft=fragmentManager.beginTransaction();
ft.add(R.id.content_frame, fragment);
ft.hide(SourceFragment.this);
ft.addToBackStack(SourceFragment.class.getName());
ft.commit();
AFAIK System only calls onCreateView() if the view is destroyed or not created.
But here we have saved the view by not removing it from memory. So it will not create a new view.
And when you get back from Destination Fragment it will pop the last FragmentTransaction removing top fragment which will make the topmost(SourceFragment's) view to appear over the screen.
COMMENT: As I said it is not a complete solution as it doesn't remove the view of Source fragment and hence occupying more memory than usual. But still, serve the purpose. Also, we are using a totally different mechanism of hiding view instead of replacing it which is non traditional.
So it's not really for how you maintain the state, but for how you maintain the view.
I would suggest a very simple solution.
Take the View reference variable and set view in OnCreateView. Check if view already exists in this variable, then return same view.
private View fragmentView;
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
if (fragmentView != null) {
return fragmentView;
}
View view = inflater.inflate(R.layout.yourfragment, container, false);
fragmentView = view;
return view;
}
I came across this problem in a Fragment containing a map, which has too many setup details to save/reload.
My solution was to basically keep this Fragment active the whole time (similar to what #kaushal mentioned).
Say you have current Fragment A and wants to display Fragment B.
Summarizing the consequences:
replace() - remove Fragment A and replace it with Fragment B. Fragment A will be recreated once brought to the front again
add() - (create and) add a Fragment B and it overlap Fragment A, which is still active in the background
remove() - can be used to remove Fragment B and return to A. Fragment B will be recreated when called later on
Hence, if you want to keep both Fragments "saved", just toggle them using hide()/show().
Pros: easy and simple method to keep multiple Fragments running
Cons: you use a lot more memory to keep all of them running. May run into problems, e.g. displaying many large bitmaps
onSaveInstanceState() is only called if there is configuration change.
Since changing from one fragment to another there is no configuration change so no call to onSaveInstanceState() is there. What state is not being save? Can you specify?
If you enter some text in EditText it will be saved automatically. Any UI item without any ID is the item whose view state shall not be saved.
first: just use add method instead of replace method of FragmentTransaction class then you have to add secondFragment to stack by addToBackStack method
second :on back click you have to call popBackStackImmediate()
Fragment sourceFragment = new SourceFragment ();
final Fragment secondFragment = new SecondFragment();
final FragmentTransaction ft = getChildFragmentManager().beginTransaction();
ft.add(R.id.child_fragment_container, secondFragment );
ft.hide(sourceFragment );
ft.addToBackStack(NewsShow.class.getName());
ft.commit();
((SecondFragment)secondFragment).backFragmentInstanceClick = new SecondFragment.backFragmentNewsResult()
{
#Override
public void backFragmentNewsResult()
{
getChildFragmentManager().popBackStackImmediate();
}
};
Kotlin and ViewBinding Solution
I am using replace() and backstack() method for FragmentTransaction. The problem is that the backstack() method calls the onCreateView of the Previous Fragment which causes in re-built of Fragment UI. Here is a solution for that:
private lateinit var binding: FragmentAdRelevantDetailsBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?
): View {
if (!this::binding.isInitialized)
binding = FragmentAdRelevantDetailsBinding.inflate(layoutInflater, container, false)
return binding.root
}
Here, since onSaveInstanceState in fragment does not call when you add fragment into backstack. The fragment lifecycle in backstack when restored start onCreateView and end onDestroyView while onSaveInstanceState is called between onDestroyView and onDestroy. My solution is create instance variable and init in onCreate. Sample code:
private boolean isDataLoading = true;
private ArrayList<String> listData;
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
isDataLoading = false;
// init list at once when create fragment
listData = new ArrayList();
}
And check it in onActivityCreated:
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if(isDataLoading){
fetchData();
}else{
//get saved instance variable listData()
}
}
private void fetchData(){
// do fetch data into listData
}
getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener()
{
#Override
public void onBackStackChanged()
{
if (getSupportFragmentManager().getBackStackEntryCount() == 0)
{
//setToolbarTitle("Main Activity");
}
else
{
Log.e("fragment_replace11111", "replace");
}
}
});
YourActivity.java
#Override
public void onBackPressed()
{
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.Fragment_content);
if (fragment instanceof YourFragmentName)
{
fragmentReplace(new HomeFragment(),"Home Fragment");
txt_toolbar_title.setText("Your Fragment");
}
else{
super.onBackPressed();
}
}
public void fragmentReplace(Fragment fragment, String fragment_name)
{
try
{
fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.Fragment_content, fragment, fragment_name);
fragmentTransaction.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right);
fragmentTransaction.addToBackStack(fragment_name);
fragmentTransaction.commitAllowingStateLoss();
}
catch (Exception e)
{
e.printStackTrace();
}
}
My problem was similar but I overcame me without keeping the fragment alive. Suppose you have an activity that has 2 fragments - F1 and F2. F1 is started initially and lets say in contains some user info and then upon some condition F2 pops on asking user to fill in additional attribute - their phone number. Next, you want that phone number to pop back to F1 and complete signup but you realize all previous user info is lost and you don't have their previous data. The fragment is recreated from scratch and even if you saved this information in onSaveInstanceState the bundle comes back null in onActivityCreated.
Solution:
Save required information as an instance variable in calling activity. Then pass that instance variable into your fragment.
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Bundle args = getArguments();
// this will be null the first time F1 is created.
// it will be populated once you replace fragment and provide bundle data
if (args != null) {
if (args.get("your_info") != null) {
// do what you want with restored information
}
}
}
So following on with my example: before I display F2 I save user data in the instance variable using a callback. Then I start F2, user fills in phone number and presses save. I use another callback in activity, collect this information and replace my fragment F1, this time it has bundle data that I can use.
#Override
public void onPhoneAdded(String phone) {
//replace fragment
F1 f1 = new F1 ();
Bundle args = new Bundle();
yourInfo.setPhone(phone);
args.putSerializable("you_info", yourInfo);
f1.setArguments(args);
getFragmentManager().beginTransaction()
.replace(R.id.fragmentContainer, f1).addToBackStack(null).commit();
}
}
More information about callbacks can be found here: https://developer.android.com/training/basics/fragments/communicating.html
Replace a Fragment using following code:
Fragment fragment = new AddPaymentFragment();
getSupportFragmentManager().beginTransaction().replace(R.id.frame, fragment, "Tag_AddPayment")
.addToBackStack("Tag_AddPayment")
.commit();
Activity's onBackPressed() is :
#Override
public void onBackPressed() {
android.support.v4.app.FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() > 1) {
fm.popBackStack();
} else {
finish();
}
Log.e("popping BACKSTRACK===> ",""+fm.getBackStackEntryCount());
}
Public void replaceFragment(Fragment mFragment, int id, String tag, boolean addToStack) {
FragmentTransaction mTransaction = getSupportFragmentManager().beginTransaction();
mTransaction.replace(id, mFragment);
hideKeyboard();
if (addToStack) {
mTransaction.addToBackStack(tag);
}
mTransaction.commitAllowingStateLoss();
}
replaceFragment(new Splash_Fragment(), R.id.container, null, false);
Perfect solution that find old fragment in stack and load it if exist in stack.
/**
* replace or add fragment to the container
*
* #param fragment pass android.support.v4.app.Fragment
* #param bundle pass your extra bundle if any
* #param popBackStack if true it will clear back stack
* #param findInStack if true it will load old fragment if found
*/
public void replaceFragment(Fragment fragment, #Nullable Bundle bundle, boolean popBackStack, boolean findInStack) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
String tag = fragment.getClass().getName();
Fragment parentFragment;
if (findInStack && fm.findFragmentByTag(tag) != null) {
parentFragment = fm.findFragmentByTag(tag);
} else {
parentFragment = fragment;
}
// if user passes the #bundle in not null, then can be added to the fragment
if (bundle != null)
parentFragment.setArguments(bundle);
else parentFragment.setArguments(null);
// this is for the very first fragment not to be added into the back stack.
if (popBackStack) {
fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
} else {
ft.addToBackStack(parentFragment.getClass().getName() + "");
}
ft.replace(R.id.contenedor_principal, parentFragment, tag);
ft.commit();
fm.executePendingTransactions();
}
use it like
Fragment f = new YourFragment();
replaceFragment(f, null, boolean true, true);
Calling the Fragment lifecycle methods properly and using onSavedInstanceState() can solve the problem.
i.e Call onCreate(), onCreateView(), onViewCreated() and onSavedInstanceState() properly and save Bundle in onSaveInstanceState() and resotre it in onCreate() method.
I don't know how but it worked for me without any error.
If anyone can explain it will very much appreciated.
public class DiagnosisFragment extends Fragment {
private static final String TITLE = "TITLE";
private String mTitle;
private List mList = null;
private ListAdapter adapter;
public DiagnosisFragment(){}
public DiagnosisFragment(List list, String title){
mList = list;
mTitle = title;
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState != null){
mList = savedInstanceState.getParcelableArrayList(HEALTH_ITEMS);
mTitle = savedInstanceState.getString(TITLE);
itemId = savedInstanceState.getInt(ID);
mChoiceMode = savedInstanceState.getInt(CHOICE_MODE);
}
getActivity().setTitle(mTitle);
adapter = (ListAdapter) new HealthAdapter(mList, getContext()).load(itemId);
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.diagnosis_fragment, container, false);
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ListView lv = view.findViewById(R.id.subLocationsSymptomsList);
lv.setAdapter(adapter);
}
#Override
public void onSaveInstanceState(#NonNull Bundle outState) {
outState.putParcelableArrayList(HEALTH_ITEMS, (ArrayList) mList);
outState.putString(TITLE, mTitle);
}
}
For who has looking for solution :
#Override
public void onDestroyView() {
Bundle savedState=new Bundle();
// put your data in bundle
// if you have object and want to restore you can use gson to convert it
//to sring
if (yourObject!=null){
savedState.putString("your_object_key",new Gson().toJson(yourObject));
}
if (getArguments()==null){
setArguments(new Bundle());
}
getArguments().putBundle("saved_state",savedState);
super.onDestroyView();
}
and in onViewCreated() method :
Bundle savedState=null;
if (getArguments()!=null){
savedState=getArguments().getBundle("saved_state");
}
if (savedState!=null){
// set your restored data to your view
}
I am trying to build an app with one MainActivity and multiple fragments.
In my MainActivity, I get the data and store it in the Data Model.
For example, getting sunrise time, then display it in Fragment B.
How can I detect the sunrise value changes and update the TextView in Fragment without restarting the app? Is there way can listen to value changed and update the textView?
here are my codes and fragment B layout.
JAVA data model CLASS
public class SunriseTimeClass {
private static final SunriseTimeClass INSTANCE = new SunriseTimeClass();
public String sunrise = "";
private SunriseTimeClass(){ }
public static SunriseTimeClass getInstance(){
return INSTANCE;
}
}
MAINACTIVITY
override fun onCreate(savedInstanceState: Bundle?) {
//this will clear the back stack and displays no animation on the screen
var sunRise = SunriseTimeClass.getInstance()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
supportActionBar!!.setDisplayShowHomeEnabled(true)
getSunriseSunset()
}
fun getSunriseSunset(){
val location = com.luckycatlabs.sunrisesunset.dto.Location("40.9167654", "-74.171811")
val calculator = SunriseSunsetCalculator(location, "America/New_York")
sunRise.sunrise = calculator.getOfficialSunriseForDate(Calendar.getInstance())
}
FRAGMENT B layout
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
android:gravity="center">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text=""
android:gravity="center"
android:id="#+id/DisplaySunrise"/>
</FrameLayout>
(Java code)
Create a setter method in fragment which will be called by activity like:
In fragment:
public void setSunrise(String sunrise){
this.sunrise = sunrise;
//further operations..
}
And in Activity:
Fragment fragment = new Fragment(); //your fragment
String s= calculator.getOfficialSunriseForDate(Calendar.getInstance());
fragment.setSunrise(s);
If I understood you right, you need change/update the fragment when something happens in the Activity.
So I think you could implement an interface ( callback ) between the Activity and the Fragment.
1.You need create an interface in the Activity:
public interface OnActivityAction {
void updateFragment();}
2.You need declare the interface and the setter in the Activity and call it when you want
public OnActivityAction actionListener;
public void setOnActionListener(OnActivityAction fragmentListener) {
this.actionListener = fragmentListener;
}
private void myActionToUpdate(){
if (null != activityListener) {
activityListener.doSomethingInFragment();
}
}
3.Then in your Fragment need implement this interface and set the listener.
public class MyFragment extends Fragment implements OnActivityAction {
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View rootView = inflater.inflate(R.layout.fragment_test_two, container, false);
((MainActivity) getActivity()).setOnActionListener(this);
return rootView;
}
#Override
public void updateFragment() {
//Do whatever you want
}
}
I hope this helps you.
Add fragment from activity
FragmentManager fragmentManager = getSupportFragmentManager();
android.support.v4.app.FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
//frame_layout put in activity xml
fragmentTransaction.add(R.id.frame_layout, drawer, "").commit();
Get Fragment instance and update your data
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentById(R.id.frame_layout);
((FragmentB)fragment).updateFragment("Updated value");
Make Public method in fragment for update textview
public void updateFragment(String value) {
//update textview here
Textview.setText(value);
}
Thanks for all the great answers.
I have used SharePreference to do my job. working as expected.
There are two possible ways to approach this.
FragmentB gets the sunRise data and updates its own TextView
Have FragmentB retrieve the sunRise values directly and update its TextView, rather than performing the retrieval in MainActivity.
OR
Communicate between Activity and Fragment via an interface
This answer is similar to another one in this thread, but is more explicit on how you would set the fragment's TextView. This solution makes several assumptions and may be an oversimplification due to the limited information in your question:
Assumption 1: This solution assumes that the getSunriseSunset method is synchronous, i.e. not dependent on any service or network call. If it is asynchronous, you would need a separate callback in your MainActivity (e.g. onSunriseRetrieved) to listen for when your data is returned, prior to updating your fragment.
Assumption 2: This solution also assumes that you want access to the fragment's views immediately after it has been added (hence, the executePendingTransactions call). That said, if you have FragmentB perform its own retrieval of the sunRise, then this assumption wouldn't be needed.
Define an interface in the Activity and :
public class MainActivity extends AppCompatActivity
{
public interface OnSunriseChangeListener
{
void onSunriseChange(String newText);
}
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null)
{
getFragmentManager()
.beginTransaction()
.add(R.id.fragment_container, new FragmentB()))
.commit();
//assuming you want access to the fragment's views immediately
getFragmentManager().executePendingTransactions();
}
getSunriseAndUpdateFragment(fragment);
}
/**
* Assumes that getSunriseSunset is synchronous and assumes that FragmentB is created.
*/
private void getSunriseAndUpdateFragment()
{
String newSunRiseValue = getSunriseSunset();
((FragmentB)getFragmentManager().findFragmentById(R.id.fragment_b)).onSunriseChange(newSunRiseValue);
}
// other code not shown...
}
Have FragmentB implement the interface:
public class FragmentB extends Fragment implements OnSunriseChangeListener
{
#Override
public void onSunriseChange(String newSunriseData)
{
TextView textView = getView.findViewById(R.id.DisplaySunrise);
textView.setText(newSunriseText);
}
I'm new Android programming. Earlier I was working with activities, where i could implement onClick on an ImageButton and go to a different activity.
Now I need to do the same but using Fragments. I have a prototype with a menu that always appear on screen and can show different activities to the user. The different lactivities would be inside this container.
Now I want to place an ImageButton inside a fragment and make that the screen shows the next fragment. But I'm confused how to do it.
I have the following components:
Activity_main(java)+activity_main.xml (with menu)
Fragment1(java)+fragment1.xml(working normal)
Inside this layout I have an ImageButton and want to show Fragment2
Fragment2(java)+fragment2.xml
How should look Fragment1 to can call Fragment2?
I will be glad if the answer could be the clearest possible because I'm new on it, and maybe I could forgot an obvious step. Thanks
Simply make a method in your activity which will always change/replace fragment when you invoke it. something like
public void updateFragment(Fragment fragment){
//add fragment replacing code here
}
in your fragment, invoke it some thing like this
((YourActivity)getActivity()).updateFragment(new YourFragment());
since, it is just an idea which works fine but still you can improve the logic.
Actually, going from one fragment to another is almost similar to going from one activity to another. There are just a few extra lines of code.
First, add a new Java class named SingleFragmentActivity which would contain the following code-
public abstract class SingleFragmentActivity extends AppCompatActivity
{
protected abstract Fragment createFragment();
#LayoutRes
protected int getLayoutResId()
{
return R.layout.activity_fragment;
}
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(getLayoutResId());
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.fragment_container);
if (fragment == null)
{
fragment = createFragment();
fm.beginTransaction().add(R.id.fragment_container, fragment).commit();
}
}
}
Make your activities in the following format-
public class SomeActivity extends SingleFragmentActivity
{
#Override
protected Fragment createFragment()
{
return SomeFragment.newInstance();
}
}
And your fragments like this-
public class SomeFragment extends Fragment
{
public static SomeFragment newInstance()
{
return new SomeFragment();
}
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View v = inflater.inflate(R.layout.fragment_some, container, false);
return v;
}
}
After this everything has the same code as you have for activities except for one small detail which is your onCreateView(LayoutInflater, ViewGroup, Bundle) class. This is how you would write it-
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View v = inflater.inflate(R.layout.fragment_some, container, false);
mTextView = (TextView)v.findViewById(R.id.some_text);
mButton = (Button)v.findViewById(R.id.some_button);
mTextView.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v)
{
check();
}
});
return v;
}
And that is it!
Hi i hope you are already aware about the fragments and their uses but still here is a brief. They are child to an activity and an activity can have more than one fragment so you can update your layout without changing activity just by changing fragments.
You can found more on fragment here : https://developer.android.com/training/basics/fragments/index.html
Back to the original problem, supposed you are in MainActivity.java and you want to load fragment in it, so you do this to load fragment first time.
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.frame, new Fragment1);
transaction.addToBackStack(null);
transaction.commit();
You will need this method to change fragment from another fragment, so add this in your MainActivity
public void changeFragment(Fragment fragment){
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.frame, new Fragment1);
transaction.addToBackStack(null);
transaction.commit();
}
Now from a button click in this fragment
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
((MainActivity)getActivity()).changeFragment(new Fragment2);
}
});
Hope it will help!
In my project, I want to set visibility of fragments buttons from MainActivity. But the problem is, it gives NullPointerException(). I also maked listBtn & gridBtn as static. I used below code :
FirstFragment fragment = (FirstFragment)getSupportFragmentManager().findFragmentById(R.id. <frameLayout Id>);
main_page_fragment.listBtn.setVisibility(View.GONE);
main_page_fragment.gridBtn.setVisibility(View.GONE);
You cannot access to your fragment view from Activity class because activity uses its own view (ex: R.layout.activity_main). Rather you can set visibility in your corresponding fragment class which will do the same job.
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.details, container, false);
Button listBtn = (Button)view.findviewById(R.id.listBrn);
Button gridBtn = (Button)view.findviewById(R.id.gridBrn);
listBtn.setVisibility(View.GONE);
gridBtn.setVisibility(View.GONE);
return view;
}
Fragment onCreateView callback is called after onCreate method of activity, so i think you have tried to get access from it. That views will be accessible only after onResumeFragments callback is called, you should perform your actions with fragments there.
Another tip is that you strongly should not call views of fragments directly like you did or via static reference to views that's the worst. You should avoid such dependencies on fragments inner implementation. Instead of it, better is create some method like setInitialState (the name depends on your business logic) and just call it from activity.
So result code:
In activity:
private FirstFragment fragment;
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//init fragment here
}
#Override
protected void onResumeFragments() {
super.onResumeFragments();
fragment.setInitialState();
}
In fragment:
//this will be called on fragment #onResume step, so views will be ready here.
public void setInitialState() {
listBtn.setVisibility(View.GONE);
gridBtn.setVisibility(View.GONE);
}
If you add your fragments dynamically from MainActivity like so:
YourFragment fragment = new YourFragment();
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.fragmentContainer, fragment, YOUR_TAG)
.commit();
Then you can define method in your fragment like so:
public void hideButtons()
{
yourBtn.setVisibility(View.GONE);
}
And call it from activity:
fragment.hideButtons();
I struggle with this for several hours and I found a much simpler solution.
Inside the fragment, simply make a public function (outside the on create view method) with the behavior that you want.
fun hideElement() {
binding.button.visibility = View.GONE
}
And then in main activity access to the fragment and call the function.
binding.bottomNavigation.setOnNavigationItemSelectedListener {
when (it.itemId){
R.id.someFragment -> someFragment.hideElement()
}
}
I've recently tried to use an interface for fragment-activity communication. The idea is that when a button is pressed in a fragment, it retrieves data from an EditText in the same fragment, it then sends the string to the MainActivty - this controls all my fragments - which then starts another fragment and delivers the string to this fragment for use later, however, I'm having trouble initially setting up the first interface which sends the data. Unfortunately nothing happens, and I cannot therefore get to the next fragment which should be displayed. Additionally I have tried using getActivity() but it cannot find the associated method within the fragment, leading me to believe that the fragments somehow aren't directly connected to MainActivity (I've only just grasped basics of Java and a little of Android, just learning.)
I've listed the relevant information below, thanks for the assistance!
Fragment
public class CreateWorkoutFragment extends Fragment implements OnClickListener {
View rootViewCreateWorkoutFragment;
EditText editTextWorkoutName;
// Using an ImageView for custom button
ImageView buttonNext;
String valueCreateWorkoutEditText;
OnDataPass dataPasser;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
rootViewCreateWorkoutFragment = inflater.inflate(R.layout.fragment_create_workout, container, false);
buttonNext = (ImageView) rootViewCreateWorkoutFragment.findViewById(R.id.button_workout_name_next);
editTextWorkoutName = (EditText) rootViewCreateWorkoutFragment.findViewById(R.id.edit_text_workout_name);
buttonNext.setOnClickListener(this);
return rootViewCreateWorkoutFragment;
}
public interface OnDataPass {
public void onDataPass(String data);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
dataPasser = (OnDataPass) activity;
}
public void passData(String data) {
dataPasser.onDataPass(data);
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button_workout_name_next:
valueCreateWorkoutEditText = editTextWorkoutName.getText().toString();
passData(valueCreateWorkoutEditText);
break;
}
}
}
Main Activity
public class MainActivity extends FragmentActivity implements OnClickListener, CreateWorkoutFragment.OnDataPass {
ImageView buttonCreate;
Fragment newFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme(R.style.AppThemeBlue);
setContentView(R.layout.activity_main);
buttonCreate = (ImageView) findViewById(R.id.create_foreground);
buttonCreate.setOnClickListener(this);
FragmentManager fragManager = getSupportFragmentManager();
FragmentTransaction tranManager = fragManager.beginTransaction();
CreateWorkoutFragment createWorkoutFrag = new CreateWorkoutFragment();
// fragment_change is just the area in XML where fragments switch
tranManager.add(R.id.fragment_change, createWorkoutFrag);
tranManager.commit();
newFragment = null;
}
#Override
public void onDataPass(String data) {
// CreateFragment is not to be confused with CreateWorkoutFragment
// CreateFragment is the fragment I'm trying to start when any strings
// are obtained from CreateWorkoutFragment
newFragment = new CreateFragment();
}
#Override
public void onClick(View v) {
switch (v.getId()) {
// create_foreground is just an ImageView used as a button
// Additionaly, other buttons are used to create other fragments,
// I've cut them out currently as they are not nessesary which is
// why CreateWorkoutFragment is only button and default currently
case R.id.create_foreground:
newFragment = new CreateWorkoutFragment();
break;
default:
newFragment = new CreateWorkoutFragment();
}
FragmentTransaction tranManager = getSupportFragmentManager().beginTransaction();
tranManager.replace(R.id.fragment_change, newFragment);
tranManager.addToBackStack(null);
tranManager.commit();
}
}
Sorry the code isn't exactly tidy, however, it was the most relevant code cut out from a large class. As I said, I have tried other methods yet cannot get any response from MainActivity either way. Thanks in advance!
Just before I posted: Got the app to write logcat messages to me, it manages to pass the data when the button is clicked - at least I think, and is something to do with the fragment not starting! At MainActivity>onDataPass()>new Fragment = new CreateFragment() Any ideas? As mentioned before, other buttons do exist and manage to change the fragment. However, were cutout to reduce amount of code posted.
getActivity() but it cannot find the associated method within the fragment
This is because getActivity() returns an Activity, not a MainActivity which is your custom subclass. You can easily fix this with a cast. For example, in your fragment, you can do this:
OnDataPass main = (OnDataPass) getActivity();
main.onDataPass(message);
Since such a cast is required, the interface seems to get in the way in my opinion. You can just as easily cast directly to MainActivity:
MainActivity main = (MainActivity) getActivity();
main.onDataPass(message);