In my recent project, I use the v4 support lib for FragmentTabHost.
When the tab and fragment init, the fragment bound to the tag is created and is rendered. Its lifecycle is
onCreate ==> onCreateView ==> onActivityCreated
I overrode the onActivityCreated methods, because I need to get some data from api.
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Lg.i("on TagDiscoverListFragment activity created");
new GetDiscoversTask().execute();
}
But every time i switch to this tab, this aysnTask executes again, because when switch occurs, the fragment calls:
onCreateView ==> onActivityCreated
I dont want the data to be refresh again.
Surely, I can extend the FragmentTabHost and override the method doTabChange() and change fragment's Attach/Detach operation to hide/view.
So, any other solutions?
You should override "onSaveInstanceState(Bundle outState)" in your fragment
and store data which from api into "outState"
When tab is switched, the onCreateView will be invoked, at this point, you can retrieve api data from "savedInstanceState" (the third argument)
Related
I need to recreate a fragment when user press a toggle. I'm using this function:
public void refreshFragment(){
FragmentTransaction ft = getFragmentManager().beginTransaction();
if (Build.VERSION.SDK_INT >= 26) {
ft.setReorderingAllowed(false);
}
ft.detach(this).attach(this).commit();
}
It works fine, but when calling it, onCreate isn't called, this is the sequence of methods called after recreate:
onCreateView()
onViewCreated()
onResume()
And this this is the sequence of methods called when creating it for the first time:
onCreate()
onCreateView()
onViewCreated()
onResume()
Appearently, onCreate is never called again when refreshing the fragment with that function. This will be as is for ever? Or in some circumstances can change? I'm asking it because it's perfect for me that onCreate only gets called the first time, because then I can put there code that I want to execute only one item and not when refreshing. But I need to know if it's safe or onCreate can be called when refreshing.
onCreate() will only be called when your fragment is being, well, created. Detaching and reattaching don't create new fragments, but onCreate() will also be called if your activity is destroyed and recreated (e.g., on a configuration change like rotation or if your app's process is killed).
I am trying to redesign some fragments to remove dependencies from the onAttach and onActivityCreated overrides and instead look up the Activity later on in the onViewCreated override.
Are there any cases in the Android application lifecycle where onViewCreated for the fragment is called before Activity onCreate finishes. For example I know that:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
will usually not result in Fragments onAttach being called (assuming the fragment is added to the fragment manager programmatically later on), however in rare cases with configuration updates the fragment manager can recreate the fragments in the super.onCreate which causes the onAttach to be called before onCreate for the activity has finished.
onCreate()
The system calls this when creating the fragment. Within your implementation, you should initialize essential components of the fragment that you want to retain when the fragment is paused or stopped, then resumed.
onCreateView()
Called to create the view hierarchy associated with the fragment.
The system calls this when it's time for the fragment to draw its user interface for the first time. To draw a UI for your fragment, you must return a View from this method that is the root of your fragment's layout. You can return null if the fragment does not provide a UI.
Refer Fragments Life Cycle
I'm using an Activity which has a ViewPager holding 2 fragments, the pager handler is some implementation of FragmentPagerAdapter.
As I understand, pager adapter handles the lifecycle of the fragments inside it.
I found out that my Activity onResume() method already gets called but the fragment onStart() method didn't even started.
how in the world can I fix that? it destroy the whole point of lifecycle interactions between activity an fragments...
Since pager adapter handles the lifecycle of the Fragment, does this means I can no longer depend on interaction with the Activity? I mean, if I want the Activity to do something in the onResume() but after the Fragment onStart() is called, I just can't do it...
Edited:
To make things clear:
Google says lifecycle of activity and fragment are going together, once one gets called, the other also gets called, e.g
Activiy -> onCreate() , and then, Fragment -> onCreate()
Activiy -> onResume() , and then, Fragment -> onResume()
BUT! in my case I get:
Activity -> onCreate() -> onStart() -> onResume() -> onPostResume()
And then:
Fragment -> onAttach() -> onCreateView() -> ... ->onResume().
and to be clear, I am using a pager adapter (not "state" pager) and I have an abstract base activiy in my app which all activities should extend.
public abstract class AbsLoginAppCompatActivity extends AppCompatActivity {
.............
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "*******************onCreate");
//do some general stuff like check for updates on server
}
And in my extend activity:
public class A extends AbsLoginAppCompatActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e(TAG, "*******************onCreate");
setContentView(R.layout.activity_a);
//also set pager + adapter + give it getSupportFragmentManager()
}
I am using:
android.support.v4.view.ViewPager
android.support.v4.app.FragmentPagerAdapter
android.support.v4.app.Fragment
android.support.v7.app.AppCompatActivity (for abs activity)
The Fragment[State]PagerAdapter uses the activities FragmentManager - or in case of a nested ViewPager in a parent fragment - that fragment's child FragmentManager to manage the fragments, just like normal fragments would do. Really, the only thing that these adapter implementations do is that they hide the nasty FragmentTransaction stuff for you.
I had never problems that particular lifecycle callbacks weren't called for me in my fragments, so I cannot say anything about that. One thing however that is important to understand and that many people get wrong is that the adapter's getItem() method is called only when a fragment is freshly created; if it is restored from a saved state this method is not called again and people tend to do all fancy things there to initialize their just "created" fragment, while they should really look into instantiateItem(), which either returns the instance you give the adapter via getItem() or returns the reference of the fragment that was automatically re-created for you.
Another thing that is good to know about fragments in pager is the method setUserVisibleHint(boolean). Since fragments are usually recreated and resumed all at once (non-state adapter) or on demand (state adapter), its usually important to know when one instance is actually visible to the user. This can be achieved by overriding the aforementioned method in a custom fragment.
Here is my problem area:
I have a Fragment A. Once it is attached, in its onCreateView, I load a webservice to fetch the data from the server and after that I set that data on the list view using a Base Adapter. Now on the Item Clicks of the list view I replace the Fragment A with Fragment B using replace Methods of the Fragment Transactions and addtoBackstack("FragmentA").
FragmentManager fm =getActivity().getFragmentManager();
fm.beginTransaction().replace(R.id.content_frame, Fragment B).commit();
Now here when I press back button on Fragment B, it takes me to Fragment A but the webservice again starts loading.
My Problem: I just want that when it returns to Fragment A, it should show its previous state and should not call the webservices again.
Thanks
OnCreateView for a fragment runs on the creation of the view every time it needs to be drawn. By going back you are causing the view to be recreated and hence the webservices are loading again.
I believe that if you only want the web services to load once then you could move the code to the "onCreate" method instead, but its probably a better idea to move this code to "onResume" instead and include some logic that checks whether you need to load your webservices again or not.
This way everytime the fragment is paused and then loaded again you could ensure that the fragment still has everything it needs.
(source: xamarin.com)
EDIT:
So for example you could have
#Override
public void onResume() {
super.onResume(); // Always call the superclass method first
if (data == null) { //Or list is empty?
getWebData()
}
}
I am rewriting a Bluetooth app with 3 Activities to use just 1 Activity and 3 Fragments:
So I have now 4 files:
MainActivity.java (contains bluetooth and shared preferences code)
MainFragment.java (contains ellipsis menu to show SettingsFragment)
SettingsFragment.java (contains "scan" button to show ScanningFragment)
ScanningFragment.java (displays nearby bluetooth devices in a list)
It almost works, but as an Android programming newbie I don't understand - what to do with Fragments when I show some other Fragment?
Should I just drop the Fragments (and remove from FragmentManager?) to be garbage collected?
Or should I add these 3 private variables to MainActivity.java and reuse them (when the user navigates forwards and backwards)?
private MainFragment mMainFragment;
private SettingsFragment mSettingsFragment;
private ScanningFragment mScanningFragment;
Or does FragmentManager somehow manage all 3 Fragment for me - regardless if they are visible or not?
Here is my current code (it is simple, I just call replace() all the time)-
public class MainActivity extends Activity implements
MainListener,
SettingsListener,
ScanningListener,
BleWrapperUiCallbacks {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.activity_main); // empty FrameLayout
Fragment fragment = new MainFragment();
getFragmentManager().beginTransaction()
.replace(R.id.root, fragment, "main")
.commit();
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_settings:
Fragment fragment = new SettingsFragment();
getFragmentManager().beginTransaction()
.addToBackStack(null)
.replace(R.id.root, fragment, "settings")
.commit();
break;
}
return super.onOptionsItemSelected(item);
}
// implementing SettingsFragment.SettingsListener interface
public void scanClicked() {
// TODO how to stop indicator when returned?
setProgressBarIndeterminateVisibility(true);
String address = // get from shared preferences
Fragment fragment = ScanningFragment.newInstance(address);
getFragmentManager().beginTransaction()
.addToBackStack(null)
.replace(R.id.root, fragment, "scan")
.commit();
}
Should I just drop the Fragments (and remove from FragmentManager?) to
be garbage collected?
No need to do anything else. FragmentManager is the guy in charge of Fragments' lifecycle. Once you call replace(), FragmentManager takes care for the rest. If needed it will keep fragment in memory, or release it.
Or should I add these 3 private variables to MainActivity.java and
reuse them (when the user navigates forwards and backwards)?
No, don't do it because of the said above.
Or does FragmentManager somehow manage all 3 Fragment for me -
regardless if they are visible or not?
Yes, it does. For instance, if you have invisible retained fragment, it's enough to create it once, and FragmentManager will take care of it and will keep it even when activity gets re-created during configuration change.
If you create fragments dynamically (as far as I can see, this is your case) then I suggest to add very first fragment dynamically too. You can do it like this.
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.activity_main); // empty FrameLayout
if (savedInstanceState == null) { // <- important
Fragment fragment = new MainFragment();
getFragmentManager().beginTransaction()
.replace(R.id.root, fragment, "main")
.commit();
}
}
This will ensure you don't duplicate MainFragment on configuration change, because when savedInstanceState is not null, then FragmentManager keeps instance of your fragment already.
Since you are calling .replace() on the fragment manager it's essentially the same thing as calling .remove(). According to the docs:
This is essentially the same as calling remove(Fragment) for all currently
added fragments that were added with the same containerViewId and
then add(int, Fragment, String) with the same arguments given here.
So you don't need to worry about any further management since it will be taken care of for you (and be removed to free up resources). This basically means that when one is shown the other is removed. If you were to call .add() then the fragments would still be alive in the background using up resources but you don't have to worry about that since using .replace() only allows one to live at a time.
If I understand your question correctly, you need not call any method to destroy the fragments after using them. Android OS will take of them. According to the documentation, When you replace the fragment with another, the onStop() method of the fragment will be executed, and documentaed as,
The fragment is not visible. Either the host activity has been stopped or the fragment has been removed from the activity but added to the back stack. A stopped fragment is still alive (all state and member information is retained by the system). However, it is no longer visible to the user and will be killed if the activity is killed.
So the fragment will be killed by the OS when the activity is killed. Till the activity is live, fragment objects will reside in the memory.
EDT:
So, if you want to use the fragment again in future, as document suggests,
Also like an activity, you can retain the state of a fragment using a Bundle, in case the activity's process is killed and you need to restore the fragment state when the activity is recreated. You can save the state during the fragment's onSaveInstanceState() callback and restore it during either onCreate(), onCreateView(), or onActivityCreated(). For more information about saving state, see the Activities document.
Fragment are hard coded in xml can not replaced....
but when you call replace(..) then what happen ??
Ok just consider you have 3 fragment A,B,C . In primary stage A initialize in MainActivity... now you are going to call B from A using replace(....). that means A will go onPause(),onStop() state of lifecycle and B will be initialized and paced in MainActivity...same as for C then B is onPause() ,onStop() state. If you want to reuse A or B then you need to call again A or B from C using replace (..). and then A or B will reinitialize and C goes to onPause(),onStop(). But there is another way you can do this
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
hide(A);
show(B);
ft.commit();
if you use above code block then A still be in the running state of its lifecycle but not visible (just detached from UI ).. so it would be better if you need those fragment again use hide()or show() method because it is less expensive operation then reinitialize the fragment.