Within my Main_Activity, I have a Button which when OnClicked opens the new Game_Activity, within the OnCreate of my Game_Activity I have:
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_true_false);
level1GameInstructions = new Level1GameInstructions();
fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.game_activity, instructions).commit();
}
Which opens a Fragment, then within the Fragment class when a Button is clicked i want to close the current Fragment open so I have done this:
View.OnClickListener playBtnListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
getActivity().onBackPressed();
}
};
However, for some reason this returns the Main_Activity rather than the Game_Activity. Does anyone know why?
Be glad to use these two methods instead of your code:
public void removeFragment(#NonNull String tag) {
getSupportFragmentManager().beginTransaction().remove(getSupportFragmentManager().findFragmentByTag(tag)).commit();
}
public void replaceFragment(#IdRes int containerId, Fragment fragment, #Nullable String tag) {
getSupportFragmentManager().beginTransaction().replace(containerId, fragment, tag).commit();
}
Just replace the needed fragment with some tag, and remove it by that tag.
Related
Im developing an app and on my first activity, I'm using an tablayout and a viewpager. So when the user, change the tabs the fragment change too. But the problem is on the second tab. In that tab I use a "container" so, when the user change to this tab, I add a fragmentOne and it has a button, when I click on that button, I replace the fragment whit a fragmentTwo. Now, on this second fragment I have a textview, when I click in that textview I want to change to the firstFragment. The trouble I think is because both have an onActivityCreated and the belong to the same activity. This is my code.
Containerfragment:
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
RegistroUnoFragment registroUnoFragment = new RegistroUnoFragment();
android.support.v4.app.FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.add(R.id.contenedorRegistro, registroUnoFragment).commit();
}
This is my first fragment:
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
btnContinuar = (Button)getView().findViewById(R.id.btnContinuar);
btnContinuar.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
RegistroDosFragment registroDosFragment = new RegistroDosFragment();
android.support.v4.app.FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.contenedorRegistro, registroDosFragment).commit();
}
});
}
and in this fragment is when I want to return my first fragment
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
tviRetroceder = (TextView)getView().findViewById(R.id.tviRetroceder);
tviRetroceder.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
RegistroUnoFragment registroUnoFragment = new RegistroUnoFragment();
android.support.v4.app.FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.add(R.id.contenedorRegistro, registroUnoFragment).commit();
}
}
The tviRetroceder OnClickListener creates a new RegsitroUnoFragment called registroUnoFragment. But there is already a RegistroUnoFragment in existence called registroUnoFragment created in your container activity in its onCreate method. You cannot create it again. You have to "find" this fragment and only then add it to the container. So you need to check if the gragment is NULL first.
Use "replace" and not "add" and use a tag so that you can find the fragment e.g.
fragmentTransaction.replace(R.id.contenedorRegistro,registroUnoFragment,"TagUno");
Then in the OnClick
RegistroUnoFragment registroUnoFragment=(RegistroUnoFragment) getActivity().getFragmentManager.FindFragmentByTag("TagUno");
if(registroUnoFragment!=null) {
{fragmentTransaction.replace(R.id.contenedorRegistro,registroUnoFragment,"TagUno");
}
I'm trying to get back to a fragment from a fragmentActivity, but it is not working, I'm getting NullPointerException, When I hit cancel I need to update the previous fragment. How can I get back to a fragment and update it? don't know why this is happening.
public class AddEscolas extends FragmentActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.addescola);
mSessao = new SecurePreferences(AddEscolas.this, "sessao");
pesquisa = (EditText) findViewById(R.id.campo_pesquisa);
nomeEscola = (TextView) findViewById(R.id.nomeEscola);
logradouro = (TextView) findViewById(R.id.endereco);
cidade = (TextView) findViewById(R.id.cidade);
procura = pesquisa.getText();
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(pesquisa.getWindowToken(), 0);
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
findViewById(R.id.btnProcurar).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
new Pesquisa().execute();
mFerramentas.hideKeyboard(AddEscolas.this, pesquisa);
}
});
findViewById(R.id.btnCancelar).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();
}
});
}
}
FragmentClass :
public class EscolasFragment extends Fragment {
#Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_escolas, container, false);
ImageView add = (ImageView) rootView.findViewById(R.id.add);
mSessao = new SecurePreferences(getActivity(), "sessao");
add.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent addIntent = new Intent(getActivity(), AddEscolas.class);
startActivity(addIntent);
}
});
return rootView;
}
}
Suppose you just need to call finish(), like this:
findViewById(R.id.btnCancelar).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
finish();
}
});
The reason for the NullPointerException maybe is that the layout id of R.id.content_frame could not be found in the AddEscolas activity.
If you need to update the EscolasFragment fragment after the AddEscolas activity exits, you may do as follow steps:
use startActivity() from the EscolarFragment fragment to call the AddEscolas activity.
use onActivityResult() in the activity containing the EscolarFragment fragment to extract result returned by the AddEscolas activity.
use onResume() in the EscolarFragment fragment to update the UI.
You have wrong idea about what FragmentActivity is. Activities host fragments. You cant go to fragment without having activity. Read the docs
Null pointer exception comes from
fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();
you need to give instance to 'fragment'. Somehing like:
Fragment fragment = new YourFragment();
Simply call onBackPressed(); in the FragmentActivity.
I currently have an Unit Converter app that I'm working in.
Here I've used multiple Blank Activities. Where each Unit's Activity can be opened using MainActivity. But now I want to make it tablet friendly.
Hence I want to use FragmentActivity now. Is it possible to convert the Blank Activities to Fragment Activities.?
All you need to do is take all View-specific logic from the Activity to a Fragment, then load the Fragment in your Activity.
For example,
public class MainActivity extends Activity {
#InjectView(R.id.button)
public Button button;
#OnClick(R.id.button)
public void onButtonClick(View view) {
Toast.makeText(this, "Hello!", Toast.LENGTH_SHORT).show();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.inject(this);
}
}
This type of logic goes in
public class MainFragment extends Fragment {
#InjectView(R.id.button)
public Button button;
#OnClick(R.id.button)
public void onButtonClick(View view) {
Toast.makeText(this, "Hello!", Toast.LENGTH_SHORT).show();
}
#Override
public void onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_main, container, false);
ButterKnife.inject(this, view);
return view;
}
}
And your Activity needs to display this fragment either statically, or dynamically. If you go dynamical, you'll need the following lines in your Activity:
public class MainActivity extends FragmentActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fm = getSupportFragmentManager();
if(savedInstanceState == null) {
fm.beginTransaction()
.add(R.id.container, new MainFragment())
.commit();
}
fm.addOnBackStackChangedListener(new OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
if(getSupportFragmentManager().getBackStackEntryCount() == 0) finish();
}
});
}
}
If you go static, then you need to specify the fragments in your layout XML for the activity.
http://developer.android.com/guide/components/fragments.html#Adding
I would visit the Android website as they give a fairly good explanation on how fragments work.
You can learn how to add them to your existing application by another Android link here.
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 am attempting to create an app which has a Master/Detail flow using Fragments. Selecting an item will open a detail fragment which may then which to "open" another fragment and add it to the back stack.
I have renamed classes to help illustrate what they do.
public class ListOfDetails extends FragmentActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
}
//Callback method indicating that an item with the given ID was selected.
public void onItemSelected(String id) {
// Performing logic to determine what fragment to start omitted
if (ifTwoPanes()) {
Fragment fragment = new DetailFragmentType1();
getSupportFragmentManager().beginTransaction().replace(R.id.aContainer, fragment).commit();
} else {
Intent newIntent = new Intent(this, SinglePaneFragmentWrapper.class);
newIntent.putExtra("id", id);
startActivity(newIntent);
}
}
// My attempt at making it possible to change displayed fragment from within fragments
public void changeDetailFragment(Fragment fragment) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
transaction.addToBackStack(null);
transaction.replace(R.id.aContainer, fragment);
transaction.commit();
}
}
An example of one of the detail fragments. There are many different Fragments that may be created in different circumstances.
public class DetailFragmentType1 extends Fragment {
private ListOfDetails parent;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Activity a = getActivity();
if (a instanceof ListOfDetails) {
parent = (ListOfDetails) a;
}
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Button aButton = (Button) getActivity().findViewById(R.id.aButton);
aButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
parent.changeDetailFragment(new SubDetailFragment());
}
});
}
}
When on phone, a wrapper activity is used to hold the fragment
public class SinglePaneFragmentWrapper extends FragmentActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Duplicate logic must be performed to start fragment
// Performing logic to determine what fragment to start omitted
String id = getIntent().getStringExtra("id");
if(id == "DetailFragmentType1") {
Fragment fragment = new DetailFragmentType1();
getSupportFragmentManager().beginTransaction().replace(R.id.aContainer, fragment).commit();
} else {
...
}
}
}
What is the proper way to change the fragment that is open in the detail pane in this circumstance? My method feels like a hack when using two panes and doesn't even work when using only one pane because getParent() from SinglePaneFragmentWrapper returns null, making me unable to call parent.changeDetailFragment().
This is a complicated question, hopefully I explained it well. Let me know if I missed something. Thanks
There are lots of opinions around this and lots of ways of doing it. I think in this case the problem is "who is responsible for changing the fragment?" on the surface it seems that a listener on the button is the obvious place, but then the fragment shouldn't know what it is hosted in (a symptom of that is getting an undesirable result like null from getParent()).
In your case I would suggest you implement a "listener" interface in the parent and "notify" from the fragment.. when the parent is notified, it changes the fragment. This way the fragment is not changing itself (so doesn't need to know how).. so.. for your case..
Add a new interface:
public interface FragmentChangeListener {
void onFragmentChangeRequested(Fragment newFragment);
}
Implement the interface in your ListOfDetails activity
public class ListOfDetails extends FragmentActivity implements FragmentChangeListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
}
//Callback method indicating that an item with the given ID was selected.
public void onItemSelected(String id) {
// Performing logic to determine what fragment to start omitted
if (ifTwoPanes()) {
Fragment fragment = new DetailFragmentType1();
getSupportFragmentManager().beginTransaction().replace(R.id.aContainer, fragment).commit();
} else {
Intent newIntent = new Intent(this, SinglePaneFragmentWrapper.class);
newIntent.putExtra("id", id);
startActivity(newIntent);
}
}
// My attempt at making it possible to change displayed fragment from within fragments
public void changeDetailFragment(Fragment fragment) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
transaction.addToBackStack(null);
transaction.replace(R.id.aContainer, fragment);
transaction.commit();
}
// This is the interface implementation that will be called by your fragments
void onFragmentChangeRequested(Fragment newFragment) {
changeDetailFragment(newFragment);
}
}
Added listener to detail fragment
public class DetailFragmentType1 extends Fragment {
private FragmentChangeListener fragmentChangeListener;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Actually you might not have an activity here.. you should probably be
// doing this in onAttach
//Activity a = getActivity();
//if (a instanceof ListOfDetails) {
// parent = (ListOfDetails) a;
//}
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Button aButton = (Button) getActivity().findViewById(R.id.aButton);
aButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// parent.changeDetailFragment(new SubDetailFragment());
notifyFragmentChange(new SubDetailFragment());
}
});
}
#Override
public void onAttach(Activity activity) {
// This is called when the fragment is attached to an activity..
if (activity instanceof FragmentChangeListener) {
fragmentChangeListener = (FragmentChangeListener) activity;
} else {
// Find your bugs early by making them clear when you can...
if (BuildConfig.DEBUG) {
throw new IllegalArgumentException("Fragment hosts must implement FragmentChangeListener");
}
}
}
private void notifyFragmentChange(Fragment newFragment) {
FragmentChangeListener listener = fragmentChangeListener;
if (listener != null) {
listener.onFragmentChangeRequested(newFragment);
}
}
}
And implement the same interface to your single pane activity...
public class SinglePaneFragmentWrapper extends FragmentActivity implements FragmentChangeListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Duplicate logic must be performed to start fragment
// Performing logic to determine what fragment to start omitted
String id = getIntent().getStringExtra("id");
if(id == "DetailFragmentType1") {
Fragment fragment = new DetailFragmentType1();
getSupportFragmentManager().beginTransaction().replace(R.id.aContainer, fragment).commit();
} else {
...
}
}
// My attempt at making it possible to change displayed fragment from within fragments
public void changeDetailFragment(Fragment fragment) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
transaction.addToBackStack(null);
transaction.replace(R.id.aContainer, fragment);
transaction.commit();
}
// This is the interface implementation that will be called by your fragments
void onFragmentChangeRequested(Fragment newFragment) {
changeDetailFragment(newFragment);
}
}
Note the similarity between your single pane and your multi-pane activities.. this suggests that you could either put all of the duplicated code (changefragment etc) into a single activity that they both extend or that in maybe they are the same activities with different layouts...
I hope that helps, Good luck.
Regards,
CJ