I am refactoring an Android App to use fragments. I have a fragment which I add to the layout with transaction.replace method but whose onCreateView method is not called. Code looks as follows:
FragmentManager fragmentManager = m_Activity.getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
m_StartNewGameFragment = new StartNewGameToggleButtonsFragment();
fragmentTransaction.replace(R.id.bottom_pane, m_StartNewGameFragment);
fragmentTransaction.commit();
String winnerText = null;
if (isComputerWinner)
winnerText = m_Activity.getResources().getText(R.string.computer_winner).toString();
else
winnerText = m_Activity.getResources().getText(R.string.player_winner).toString();
m_StartNewGameFragment.updateStats(computerWins, playerWins, winnerText);
The method updateStats of fragment is as follows:
public void updateStats(int computer_wins, int player_wins, String winnerText) {
System.out.println("Update stats " + m_ComputerWins);
m_ComputerWins.setText(Integer.toString(computer_wins));
m_PlayerWins.setText(Integer.toString(player_wins));
m_WinnerTextView.setText(winnerText);
}
When updateStats is called m_ComputerWins is null and the program crashes. m_ComputerWins is initialized inside the onCreateView method of the fragment which seems not to be called.
Can anyone please help ?
Take global variables in your fragment class for computer_wins, player_wins, winnerText and init them inside updateStats method.
then inside onViewCreated() method, set values like
m_ComputerWins.setText(Integer.toString(computer_wins));
m_PlayerWins.setText(Integer.toString(player_wins));
m_WinnerTextView.setText(winnerText);
You never know when the fragments is created (which calls onCreate()), because fragment's life cycle differs from your activity's. In your code, you are trying to update fragment from activity. But in the way you have written your code, it is almost guaranteed that your fragment is not created yet.
Search for how to pass parameters to fragment or update fragment to find out the correct way of updating fragment from activity. You can find several related question, e.g. this one.
FragmentTransaction only runs on the next event loop. If you want to immediately run a fragment transaction, use fragmentTransaction.commitNowAllowingStateLoss();.
Please note that this won't actually work correctly after process death, which is why you should use the fragment.setArguments(Bundle method to pass initial argument values to a fragment.
Related
I want to dynamically create fragment. So when click the navigation fragment item, it will trigger the callback function in the activity to communicate with detail fragment. Here is the callback faction in activity:
public void getChatRoomId(long chatroom_id) {
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
MsgChatRoom msgChatRoom = new MsgChatRoom();
ft.replace(R.id.activity_chat_MsgChatroom_container, msgChatRoom, "messages");
ft.addToBackStack(null);
ft.commit();
msgChatRoom.startQuery(chatroom_id);
}
I could call the startQuery method, but in that method I need some arguments should be initialize in onCreateActivity(). However, at the time when I call startQuery, the Fragment does not have called OncreateActivity. So there will be a error:
.... on a null object reference
How to solve this problem. Thanks in advance.
You can pass arguments to the fragment by using Fragment's setArguments(Bundle) function.
When you create the fragment pass your arguments by setArguments() and then in your fragment retrieve them by calling getArguments() in this way you don't wait for specific fragment's life-cycle to be able to use your data.
For more information you can visit http://developer.android.com/reference/android/app/Fragment.html which also contains couple of examples of using these functions
I have an Activity with a FrameLayout and need to show different fragments based on user input.
The code I use for showing a fragment is this:
private void showFragment(Fragment fragment, Bundle args, boolean addToBackStack) {
if (args != null) {
fragment.setArguments(args);
}
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.setCustomAnimations(R.anim.activity_open_translate, R.anim.activity_close_scale);
fragmentTransaction.replace(R.id.main_frame, fragment);
if (addToBackStack) {
fragmentTransaction.addToBackStack(fragment.getClass().getName());
}
fragmentTransaction.commit();
}
This is called as :
if (contactPickFragment == null) {
contactPickFragment = new ContactPickFragment();
}
showFragment(contactPickFragment, args, true);
All this works fine. Now if the user goes into one fragment presses back and returns back to the same fragment, all my views inside stay the same. For example, I have an EditText inside the fragment and the user edits something inside. If the user comes back to the fragment, the same text persists. I do not want this to happen. How do I reset everything in the view?
I have added code within the Fragment's onCreateView() to clear the text, and from debugging I see that this is being called, but the text never gets cleared. What am I doing wrong here?
If you don't want the data from the previous instance to appear, simply create a new instance of ContactPickFragment each time you show it.
Clearing data in onCreateView() has no effect because view state is restored AFTER onCreateView(). Your Fragment has no view before onCreateView() and so Android cannot possibly apply the previous state any earlier. Values set on the views during onCreateView() will be overwritten by their previous values.
As a general answer, there is no way to "refresh" the view of a Fragment, other than replacing the fragment with another instance of itself (possibly initialized with the parameters that you want to refresh/update).
You can reuse your fragments and refresh the state of your views. You just can't do it from onCreateView as #antonyt correctly points out.
Instead, override onViewStateRestored and set up the state of your views the way you'd like from there.
Something like:
#Override
public void onViewStateRestored(Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
View view = getView();
// Code to call view.findViewById to grab the views you want
// and set them to a specific state goes here
}
There are advantages to reusing fragments. Not the least of which is that if you have a memory leak with your fragment (which is easier than you may think to accomplish,) you will exacerbate the problem by creating myriads of them.
I have this Activity which at first shows a Fragment with a list of elements. This works perfectly with this code:
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.list_act);
if(null == savedInstanceState)
{
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
ListFragment glfragment = new ListFragment();
fragmentTransaction.add(R.id.listfrag1, glfragment);
fragmentTransaction.commit();
}
}
Well I have a ListFragment and a DetailFragment. But I don't know how to do the transition when I click an element of the list. I know the fragmentTransaction.replace(), but I don't know WHEN to call it.
I thought I should use the OnListItemClick() inside the ListFragment, but I don't know how to use the FragmentManager inside the Fragment and not in the main Activity... Also I want to "export" some data to the DetailFragment as if it was a Intent, but it's not.
To use the fragment manager inside your Fragment, simply call
getActivity().getFragmentManager() instead of getFragmentManager(). Implementing this in your OnItemClickListener should suffice.
What I would do is:
Define an interface with one method listItemSelected() with as an argument the id of the selected item
Let your activity implement this interface
In the onAttach of your list fragment, take the activity and keep it as a member variable, cast to the interface type. Make sure that in the onDetach you dereference it.
In your onListItemClick, call this method on your activity
In the activity, you can now do a new fragmenttransaction, this time you need to replace instead of add the fragment
To create your detail fragment with the correct argument (the id), use the method described here.
This should normally work fine.
First, some background information. I have an Activity that hosts several Fragments; these are hidden and shown such that only one Fragment is visible at a time. Each Fragment hosts several custom Views that I call IncrementCounters. Those views display a number and increment that number by 1 when tapped.
Each of the Fragments has setRetainInstance(true) called on it when it is created in my Activity. When the Activity is created, I check to see if the Fragments exist; if they do, I store a reference to them; if not, I create a new instance, like this:
autonFragment = (AutonomousScoutingFragment) getFragmentManager()
.findFragmentByTag("auton");
teleopFragment = (TeleoperatedScoutingFragment) getFragmentManager()
.findFragmentByTag("teleop");
postMatchFragment = (PostMatchScoutingFragment) getFragmentManager()
.findFragmentByTag("post_match");
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
if (autonFragment == null) {
Log.d("onCreate", "autonFragment is null!");
autonFragment = new AutonomousScoutingFragment();
autonFragment.setRetainInstance(true);
ft.add(R.id.scouting_fragment_container, autonFragment, "auton")
.hide(autonFragment);
}
if (teleopFragment == null) {
Log.d("onCreate", "teleopFragment is null!");
teleopFragment = new TeleoperatedScoutingFragment();
teleopFragment.setRetainInstance(true);
ft.add(R.id.scouting_fragment_container, teleopFragment, "teleop")
.hide(teleopFragment);
}
if (postMatchFragment == null) {
Log.d("onCreate", "postMatchFragment is null!");
postMatchFragment = new PostMatchScoutingFragment();
postMatchFragment.setRetainInstance(true);
ft.add(R.id.scouting_fragment_container, postMatchFragment,
"post_match").hide(postMatchFragment);
}
ft.commit();
One problem I have is that after every orientation change, it seems as though the Fragments aren't actually being retained, as ever time I see debug prints stating that they are all null. This may be realted to my bigger problem; I'm not sure.
I am trying to figure out how to maintain the value of the number stored in each IncrementCounter across configuration changes, specifically rotation. I have overridden onSaveInstanceState() and onRestoreInstanceState() in IncrementCounter. When I rotate my device, I see that onSaveInstanceState() is called on all of the IncrementCounters I have in my Fragments. However, the corresponding onRestoreInstanceState() is never called, and my IncrementCounters do not have their states restored. What is the proper way to handle something like this? I've been banging my head against my desk for hours about this problem.
Since the Fragments have the setRetainInstance(true), they will not be destroyed on a rotation. The Activity will be destroyed, and the Fragments will be detached until a new Activity is created. However, the Fragments will go through the onCreateView() method again, so you would need to restore their IncrementCounters.
Also, you can save state in onSaveInstanceState(), and then restore it in onCreate(), onCreateView(), and several other methods that are all passed that same bundle as a parameter.
Here is what I do to create a new fragment (called AlbumsTrackFragment) and replace the existing one :
AlbumsTrackFragment albumstrackfragment = new AlbumsTrackFragment();
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.layout_ipod, albumstrackfragment, "albumsTrackfragment");
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
albumstrackfragment.DoTask(data);
The problem is that when I can't call the method DoTask() and pass the new fragment some data because the app crashes, indeed I get : Content view not yet created
How can I wait that the commit was performed to call DoTask() method of my fragment ?
Thanks
You might consider passing in data to your fragment through DoTask and then wait until OnActivityCreated() is called to perform whatever action happens in DoTask. This way you are passing in your data but not doing anything until your view is loaded and won't crash.