I have a Working model of fragments, when i was debugging the code i saw that the Fragment onCreate is being called 4 times.
Below is my code:
MyFragmentActivity
class MyFragmentActivity extends FragmentActivity{
#Override
public void onCreate(Bundle savedInstanceState) {
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction().replace(fragmentID, new MyListFragmentt())
.replace(detailFragmentID, new MyDetailFragment()).commit();
}
}
#Override
protected void onRestart() {
getSupportFragmentManager().beginTransaction().replace(detailFragmentID, new MyDetailFragment()).commitAllowingStateLoss();
}
}
MyDetailFragment.class
class MyDetailFragment extends Fragment{
// has method like oncreate(),onCreateView(),onSaveInstanceState()
}
How my oncreate of MyDetailFragment is called ? When i go to some other activity and come back and then tilt the device only then oncreate and onSaveInstanceState of MyDetailFragment is called multiple times.
How can i solve this, i have looked into few posts on SO but it says that we need use HIDE,Show methods and other things ? but What is the proper soultion to this ?
EDIT
When i am coming back from previous activity, my data in the MyDetailFragment needs to be refreshed.
Try this
MyDetailFragment fragment = new MyDetailFragment();
#Override
public void onCreate(Bundle savedInstanceState) {
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction().replace(fragmentID, new MyListFragmentt())
.replace(detailFragmentID, fragment).commit();
}
}
#Override
protected void onRestart() {
if(fragment != null) {
getSupportFragmentManager().beginTransaction().replace(detailFragmentID, fragment).commitAllowingStateLoss();
}
}
i think ur recreating fragments multiple times, u do new MyListFragment everytime on onCreate function, call findFragmentByTag to get the existing fragment and set that, if null (first time) then create one
/here is some code mate, if this doesnt work and ur app has single fragment better to just create xml and have only a fragment tag in it, and set that xml in setContentView function*/
// declare following member variable
MyFragment _fragment;
// in onCreate function, call this method
private void setupFragment()
{
_fragment = (MyFragment)getFragmentManager().findFragmentByTag("MyFragment");
if(null == _fragment)
{
_fragment = new MyFragment();
}
// now do the fragment transaction
FragmentTransaction trans = getFragmentManager().beginTransaction();
trans.add(containerId, _fragment, "MyFragment"); // here tag is important
trans.commit();
}
Related
I can't restore my fragment !
I'm saving like :
#Override
public void onSaveInstanceState(Bundle savedInstanceState)
{
if(cameraFragment != null && cameraFragment.isAdded())
getSupportFragmentManager().putFragment(savedInstanceState, "cameraFrameLayout", cameraFragment);
super.onSaveInstanceState(savedInstanceState);
}
and restoring in onCreate(savedInstanceState) :
if(savedInstanceState == null)
cameraFragment = CameraFragment.newInstance();
else
cameraFragment = (CameraFragment) getSupportFragmentManager().findFragmentByTag("cameraFrameLayout");
When I change the device orientation I can successfully see that the onSaveInstanceState in called with my fragment but on the onCreate I have a null instance...
Txs for help !
You need to restore the saved fragment in onRestoreInstanceState
public void onRetoreInstanceState(Bundle inState){
cameraFragment = getFragmentManager().getFragment(inState,"cameraFrameLayout");
}
Also notice that getFragment is called to the FragmentManager instead if findFragmentByTag. Hope it helps!
Note: call super methods in onSaveInstanceState and onRestoreInstanceState overrides
In your fragment onCreate() method write setRetainInstance(true);
In the Activity that creates the fragment you should do something like this
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null)
{
// Display the fragment as the main content.
getFragmentManager().beginTransaction()
.replace(android.R.id.content, CameraFragment.newInstance())
.commit();
}
}
The android.R.id.content can be different in your case. This is where you want to load your fragment.
The MainActivity of my Android 4+ app uses a ViewPager to switch between 5 different Fragments. It is necessary, that both the Activity and the Fragments knows which Fragment is currently selected/displayed. The MainActivity keeps track of this information using the ViewPagers OnPageChangeListener. The MainActivity also keeps references to the Fragments created the FragmentPagerAdapter to be able to notify a Fragment when it is selected.
The problem is, that the MainActivty (of course) looses the references to the Fragments when it is re-created, e.g. when rotating the device. This would not be big problem, if the re-created MainActivity would re-get the information from the FragmentPagerAdapter, but this is NOT the case.
It seems that the ViewPager automatically re-creates the Framgent(s) when super.onCreate(savedInstanceState)is called without asking the FragmentPagerAdapter. This is a problem because this way the MainActivty does not get any information about the created Fragment and cannot save a reference to it.
How can this be solved?
// MainActivity
#Override
protected void onCreate(Bundle savedInstanceState) {
// When the activity is re-creates, e.g. on rotation, the viewPager
// Fragments are created here without knowledge of the MainActivity.
super.onCreate(savedInstanceState);
...
ViewPager viewPager = (ViewPager)findViewById(R.id.pager);
viewPager.setAdapter(new SectionsPagerAdapter(getSupportFragmentManager()));
currentPageIndex = -1;
viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
updateCurrentPageIndex(position);
}
});
#Override
protected void onResume () {
super.onResume();
updateCurrentPageIndex(currentPageIndex);
}
private void updateCurrentPageIndex(int index) {
currentPageIndex = position;
if (pageOneFragment != null && position == 0)
pageOneFragment.onSelectedInViewPager();
if (pageTwoFragment != null && position == 0)
pageTwoFragment.onSelectedInViewPager();
...
}
Fragment pageOnFragment;
Fragment pageTwoFragment;
...
private class SectionsPagerAdapter extends FragmentPagerAdapter {
...
#Override
public Fragment getItem(int position) {
Fragment fragment = null;
if (position == 0) {
pageOneFragment = PageOneFragmet.newInstance();
fragment = pageOneFragment;
}
if (position == 1) {
pageTwoFragment= PageTwoFragmet.newInstance();
fragment = pageTwoFragment;
}
...
return fragment;
}
}
}
EDIT: Thanks for the answers, but (I think) saving the instance state does not solve the problem. Of course I am aware of the possibility to save Date to the instance state. Thus getting back the information about the current index is not a big problem. The problem is, that the MainActivity cannot notify the selected Fragment because it has not reference to it. The Fragment is not created in the Adapter but automatically by the ViewPager. Thus the Activity does not get any information about the Fragment and has not possibility to notify it about being the active one...
Create an interface
public interface FragmentListener {
void onFragmentActivated(Fragment f);
}
Implement this interface in your activity. Then in your fragment:
#Override
public void onResume() {
super.onResume();
((FragmentListener)getActivity()).onFragmentActivated(this);
}
Now, when this callback is being called in your activity, you know which fragment is activated
In onSaveInstanceState() you should "put" anything that you want to keep track of. In onCreate() you can "get" those items back out.
private static final String KEY_BUNDLE_CURRENT_PAGE_STATE = "current_page_state";
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(KEY_BUNDLE_CURRENT_PAGE_STATE, currentPageIndex);
}
#Override
public void onCreate(Bundle savedInstanceState){
// ...
if (savedInstanceState != null && savedInstanceState.containsKey(KEY_BUNDLE_CURRENT_PAGE_STATE) {
currentPageIndex = savedInstanceState.getInt(KEY_BUNDLE_CURRENT_PAGE_STATE);
}
// ...
}
Try this
private static final String INDEX = "f_index";
private int mCurrentIndex ; // the current index will be updated thru position
// orientation change occurs
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Save the user's current index state
savedInstanceState.putInt(INDEX, mCurrentIndex);
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
}
...
// this is where we get the data back if we are recreated
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); // Always call the superclass first
// Check whether we're recreating a previously destroyed instance
if (savedInstanceState != null) {
// Restore value of members from saved state
mCurrentIndex= savedInstanceState.getInt(INDEX);
} else {
// Probably initialize members with default values for a new instance
}
...
}
A nice diagram of lifecycle here.
EDIT
If the above doesnt work for you I suggest trying this instead.
try using shared preferences
every change of index, save it to sharedpreferences
when orientation change handle runtime changes to get the index
there
hope it helps :)
I've a activity which basically is :
public class FragmentContainer extends FragmentActivityBase implements IRefreshListener {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getIntent().getExtras() == null
|| getIntent().getExtras().get("type") == null) {
showProductList();
}
else
{
if (getIntent().getExtras().get("type").equals("customer"))
showCustomerList();
}
#Override
public void showProductList() {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager
.beginTransaction();
// load the product list
ProductList fragment = new ProductList();
fragmentTransaction.replace(R.id.fragment_container, fragment)
.addToBackStack(null);
fragmentTransaction.commit();
}
.....
}
in the fragment, I use onCreateView to get intent and then I create my view.
If I need to change the fragment, I get the reference to the parent Activity (taken from onAttach) and I call method referenced by the IRefreshListener.
like :
IRefreshListener mCallback;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception.
try {
mCallback = (IRefreshListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement IRefreshListener");
}
}
public void callCustomer() {
mCallback.showCustomerList();
}
It works but whne I change the orientation, even I use setRetainInstance(true) it will be reseted.
I have 2 questions :
Do I use the good pattern to manage my application. The big activity which contains one fragment become bigger with the time
How should I handle orientation change ?
Regards
I do not find this pattern is more perfect or best one, although it is or was a suggestion from Google. Because it could be a worse coding style if fragment knows particular activity or listeners, you might write more and more code, when you wanna to let your fragment know more its "container" or "parents". Will the fragment later be used for other activity which has not been implemented with IRefreshListener etc, you will code much more.
My introduce is using Otto-Bus or Event-Bus. You can just send message from one to one. Every one doesn't have to know each other.
I have main activity which embeds fragment:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
vd = VirtualDatabaseTableProvider.getInstance(getApplicationContext());
fm = getSupportFragmentManager();
fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
//Create Layout and add fragments
setContentView(R.layout.main_window);
ListFragment ListFragment= new ListFragment();
android.support.v4.app.FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragment_pane, ListFragment, "List");
//ft.replace(R.id.fragment_pane, ListFragment);
ft.addToBackStack(null);
ft.commit();
//Initialising buttons
imgBtnFontInc = (ImageButton) findViewById(R.id.ImgBtnUpFont);
imgBtnFontInc.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(textViewAttached){
try{
//Some text Resize
}
}catch (NullPointerException npe){
Log.e(TAG, "Error on calling Text resize");
Log.e(TAG, npe.getMessage());
Log.e(TAG, npe.getStackTrace().toString());
}
}
}
}
);
/* More Buttons code..... */
imgBtnFontDec.setVisibility(View.GONE);
imgBtnFontInc.setVisibility(View.GONE);
/* Some Saved State handling to recover detailed text Screen*/
if(savedInstanceState != null){
if (savedInstanceState.containsKey("UUID")){
try{
String uuid = savedInstanceState.getString("UUID");
if (uuid != null){
iniTextScreen(uuid);
}
}catch (Exception e){
Log.e(TAG, "Unable To return text");
}
}
}
Text is initialised with function:
private void initTextScreen(String StringID){
Bundle Data = new Bundle();
Data.putString("UUID", StringID);
TextScreenFragment TextFragment = new TextScreenFragment();
TextFragment.setArg1ments(Data);
if(fm == null){
fm = getSupportFragmentManager();
}
android.support.v4.app.FragmentTransaction ft = fm.beginTransaction();
ft.setCustomAnimations( R.anim.animation_enter, R.anim.animation_exit);
ft.replace(R.id.fragment_pane, TextFragment, "TextFragment");
ft.addToBackStack(null);
ft.commit();
}
I handled Buttons visibility in main activity with simple Callback from TextScreenFragment. Callback in main activity:
public void onTextViewAttached() {
textViewAttached = true;
MainActivity.this.imgBtnFontDec.setVisibility(View.VISIBLE);
MainActivity.this.imgBtnFontInc.setVisibility(View.VISIBLE);
}
Callback called in TextScreenFragment:
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (!(activity instanceof Callbacks)) {
throw new IllegalStateException(
"Activity must implement fragment's callbacks.");
} else {
listener = (Callbacks) activity;
listener.onTextViewAttached();
}
}
public interface Callbacks {
/**
* Callback for when an item has been selected.
*/
public void onTextViewAttached();
}
It works, however when I put android phone is switch potrait/landscape mode: onAttached in fragment get called way before onCreate in main Activity and Button objects.
How can fragment be attached on main activity before even onCreate is called in main activity?
I attached a particular fragment at very end of onCreate method after buttons were already initialized,but why onAttach in fragment is called before even I attach fragment and get null exception, because button objects were not initialized in onCreate? How it is even possible?
When I comment out:
`// MainActivity.this.imgBtnFontDec.setVisibility(View.VISIBLE);
//MainActivity.this.imgBtnFontInc.setVisibility(View.VISIBLE);`
in callback function public void onTextViewAttached(), no more crashes, but still I noticed that onAttach is called before main activity created and is called twice:
one time with uninitialised activity from hell knows were (every element of main activity is either null or has default values), second time when fragment is properly attached from main activity onCreate.
I made conclusion that on orientation switch fragments gets atached to uninitialised activity. Am I missing something, on orientation change should I call some other function before onCreate in main activity to get buttons from layout?
Is it some kind of fragment automated attach behaviour which I am not aware of and I could take advantage of?
What is life cycle of activity with fragment attached to it, because onAttach called in fragment before even main activity is created seems counter intuitive.
I've had a look around and found a couple of questions with a similar topic, but nothing that helped in my case.
I'm trying to access an existing active fragment using getSupportFragmentManager().findFragmentByTag(TAG), but it always returns null. Replies on similar questions suggested that it takes a while for the commit to be executed, so calling findFragmentByTag would return null if called too early. I've tried two things:
add getSupportFragmentManager().executePendingTransactions()
immediately after the commit, but still get null.
added a button... pressing this after the activity has been created,
the fragment registered and the view displayed should leave the
system enough time to commit. But i still get null.
Here's my activity:
public class MainActivity extends ActionBarActivity {
private static final String F_SETTINGS = "f_settings";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
debug();
}
});
if (savedInstanceState == null) {
FSettings newFragment = new FSettings();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.container, newFragment);
ft.addToBackStack(F_SETTINGS);
ft.commit();
// getSupportFragmentManager().executePendingTransactions();
//// Activating this did not make any difference...
}
debug();
}
private void debug() {
String txt = "null";
Fragment frag = getSupportFragmentManager().findFragmentByTag(F_SETTINGS);
if (frag != null) {
txt = frag.toString();
}
Log.i("Testing", txt);
}
}
What am I doing wrong here?
Cheers,
Max
In your code you haven't mentioned tag in replace method So,
Use this structure of replace method of fragment
ft.replace(R.id.container, newFragment,"fragment_tag_String");
Refer this link for more information. fragment replace with tag name