I am having a very odd behaviour here. From my MainActivity class, I invoke my DialogFragment:
EndRoundDialogFragment df = new EndRoundDialogFragment(myVO);
df.show(fragmentManager, "end_round_dialog_fragment");
The constructor for this DialogFragment is simple:
public EndRoundDialogFragment(UserVO vo) {
this.userVO = vo;
}
This Fragment has a Google Maps fragment inside it. So what I basically do is:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.end_round_dialog, container, false);
mMap = ((MapFragment)getFragmentManager().findFragmentById(R.id.map_result)).getMap();
mMap.setIndoorEnabled(false);
mMap.setMyLocationEnabled(false);
mMap.setTrafficEnabled(false);
buttonClose.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
mListener.onEndDialogClosed();
userVO = null;
dismissAllowingStateLoss();
}
});
//Rest of the code is manipulating Markers in this map.
So, when user clicks on Close button, I dismiss the dialog and am OKwith State loss. I am even setting the VO to null.
As you can see, my Activity implements the Dialog's Listener onEndDialogClosed(). This method implementation is:
#Override
public void onEndDialogClosed() {
Fragment prev = fragmentManager.findFragmentByTag("end_round_dialog_fragment");
if(prev != null)
fragmentManager.beginTransaction().remove(prev).commit();
new Get5PointsTask().execute();
}
So I am also removing the whole fragment when it is closed.
I also added the following to this DialogFragments, to make sure that the Google Maps fragment was indeed being removed from the code:
#Override
public void onDestroyView() {
super.onDestroyView();
Fragment f = (MapFragment) getFragmentManager().findFragmentById(R.id.map_result);
if (f != null) {
getFragmentManager().beginTransaction().remove(f).commit();
}
}
While debugging, I verified that the variable f is not null and the remove() code is indeed called.
When I instantiate the DialogFragment again, with a new VO data, Google Maps is shown with new and old data. It's just not resetting the de memory...
Any ideas?
EDIT 1:
And I found what the problem was. Was simply not erasing my VO in the Activity's onEndDialogClosed() method. Stupid me.
Thanks to all and hope this code helps other people.
So, I fixed the issue.
I was simply not re-instantiating the VO that held the user data in the Activity... so when opening the DialogFragment, it held the new and the old that. Simple like that, stupid like that.
Thanks!
Related
I use DialogFragment contain a webview. After show the DialogFragment , when pressed back button, the app will return to the activity view. If pressed show DialogFragment button, the webview load the init url again. But my requirement is that if second time open the DialogFragment , i want the webview show page last time loaded. How can i achieve this?
My current thought is keep the DialogFragment instance, when push the show button then check if activity already has the DialogFragment. But in my code, the findFragmentByTag always return null ,what's the problem? Or I should do some work in the DialogFragment?
main activity code:
mcWebViewDialog m_webviewDialog = null;
public void showWebviewDialog()
{
FragmentManager fragmentManager=getFragmentManager();
FragmentTransaction fragmentTransaction=fragmentManager.beginTransaction();
Fragment prev=fragmentManager.findFragmentByTag("mcWebDialog");
if(prev!=null)
{
Log.v("seayoung","m_webviewDialog not null");
fragmentTransaction.show(prev);
}
else
{
Log.v("seayoung","m_webviewDialog null");
m_webviewDialog=mcWebViewDialog.newInstance();
m_webviewDialog.set_loadurl("file:///android_asset/input.html");
m_webviewDialog.show(fragmentManager,"mcWebDialog");
}
}
WebViewDialog code:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
//other code
m_webView.loadUrl(m_loadurl);
return m_webView;
}
When you press back your dialog fragment is destroyed and is removed from backstack, hence you are getting null while performing findFragmentByTag, well it wont be a good idea to hold the view when user presses back, but as an alternative you can try caching the created fragment locally, like this.
HashMap<String,WeakReference<DialogFragment>> dialogMap = new HashMap<>();
// call this before callin `show`
dialogMap.put(TAG,new WeakReference<>(YOUR_FRAGMENT));
and next time, you can perform this
DialogFragment prev=fragmentManager.findFragmentByTag("mcWebDialog");
if(prev == null){
prev = dialogMap.get("mcWebDialog").get();
}
if(prev!=null){
Log.v("seayoung","m_webviewDialog not null");
fragmentTransaction.show(prev);
}
I have an Android application with a navigation drawer. My problem is that some fragment takes few second to load (parser, Map API). I would like to load all my fragment when the app starts.
I'm not sure if it is possible or a good way to do it, but I was thinking of create an instance of each of my fragments in the onCreate method of the main activity. Then, when the user select a fragment in the navigation drawer, I use the existing instance instead of creating a new one.
The problem is that it does not prevent lag the first time I show a specific fragment. In my opinion, the reason is that the fragment constructor does not do a lot of operation.
After searching the web, I can't find an elegant way to "preload" fragment when the application starts (and not when the user select an item in the drawer).
Some post talks about AsyncTask, but it looks like MapFragment operation can't be executed except in the main thread (I got an exception when I try: java.lang.IllegalStateException: Not on the main thread).
here is what I've tried so far:
mFragments = new Fragment[BasicFragment.FRAGMENT_NUMBER];
mFragments[BasicFragment.HOMEFRAGMENT_ID] = new HomeFragment();
mFragments[BasicFragment.CAFEFRAGMENT_ID] = new CafeFragment();
mFragments[BasicFragment.SERVICEFRAGMENT_ID] = new ServiceFragment();
mFragments[BasicFragment.GOOGLEMAPFRAGMENT_ID] = new GoogleMapFragment();
When an item is selected in the nav drawer:
private void selectItem(int position) {
Fragment fragment = mFragments[position];
// here, I check if the fragment is null and instanciate it if needed
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction ft = fragmentManager.beginTransaction();
ft.replace(R.id.content_frame, fragment);
ft.commit();
mDrawerList.setItemChecked(position,true);
mDrawerLayout.closeDrawer(mDrawerList);
}
I also tried this solution; it allows to prevent a fragment from being loaded twice (or more), but it does not prevent my app from lag the first time I show it. That's why I try to load all fragments when the application starts (using a splash-screen or something) in order to prevent further lags.
Thanks for your help / suggestion.
You can put your fragments in ViewPager. It preloads 2 pages(fragments) by default. Also you can increase the number of preloaded pages(fragments)
mViewPager.setOffscreenPageLimit(int numberOfPreloadedPages);
However, you will need to rewrite your showFragment method and rewrite back stack logic.
One thing you can do is load the resources in a UI-less fragment by returning null in in Fragment#onCreateView(). You can also call Fragment#setRetainInstance(true) in order to prevent the fragment from being destroyed.
This can be added to the FragmentManager in Activity#onCreate(). From there, Fragments that you add can hook in to this resource fragment to get the resources they need.
So something like this:
public class ResourceFragment extends Fragment {
public static final String TAG = "resourceFragment";
private Bitmap mExtremelyLargeBitmap = null;
#Override
public View onCreateView(ViewInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return null;
}
#Override
public void onStart() {
super.onStart();
new BitmapLoader().execute();
}
public Bitmap getExtremelyLargeBitmap() {
return mExtremelyLargeBitmap;
}
private class BitmapLoader extends AsyncTask<Void, Void, Bitmap> {
#Override
protected Bitmap doInBackground(Void... params) {
return decodeBitmapMethod();
}
#Override
protected void onPostExecute(Bitmap result) {
mExtremelyLargeBitmap = result;
}
}
}
Add it to the fragment manager in the Activity first thing. Then, whenever you load your other Fragments, they merely have to get the resource fragment from the fragment manager like so:
public class FakeFragment extends Fragment {
#Override
public View onCreateView(ViewInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final ResourceFragment resFragment = getFragmentManager().findFragmentByTag(ResourceFragment.TAG);
Bitmap largeBitmap = resFragment.getBitmap();
if (largeBitmap != null) {
// Do something with it.
}
}
}
You will probably have to make a "register/unregister" listener set up because you will still need to wait until the resources are loaded, but you can start loading resources as soon as possible without creating a bunch of fragments at first.
To preload fragments, attach() can be used. So in OP's case it will be:
ft.attach(fragment).commit();
Make sure to store the fragment somewhere and use that one the next time ft.replace() is called.
We all know that when using ViewPager with Fragment and FragmentPagerAdapter we get 3 Fragment loaded: the visible one, and both on each of its sides.
So, if I have 7 Fragments and I'm iterating through them to see which 3 of them are the ones that are loaded, and by that I mean onCreateView() has already been called, how can I determine this?
EDIT: The Fragment doesn't have to be the one that the ViewPager is showing, just that onCreateView() has already been called.
Well logically, this would be a reasonable test if onCreateView has been called:
myFragment.getView() != null;
Assuming you a have a reference to all of the fragments in the pager iterate, them and check if they have a view.
http://developer.android.com/reference/android/app/Fragment.html#getView()
Update
The above answer assumes that your fragments always create a view, and are not viewless fragments. If they are then I suggest sub classing the fragment like so:
public abstract class SubFragment extends Fragment
{
protected boolean onCreateViewCalled = false;
public boolean hasOnCreateViewBeenCalled()
{
return onCreateViewCalled;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup Container, Bundle state){
onCreateViewCalled = true;
return null;
}
}
Just bear in mind that further sub classes will have to call super or set the flag themselves should they override onCreateView as well.
I added an interface to Fragment. Looks like:
protected OnCreateViewCallback createViewCallback = null;
public void setCreateViewCallback(OnCreateViewCallback createViewCallback) {
this.createViewCallback = createViewCallback;
}
public interface OnCreateViewCallback {
void onCreateView();
}
In my onCreateView():
//initialize your view.
if (createViewCallback != null) {
createViewCallback.onCreateView();
createViewCallback = null;
}
return mainView;
From my activity:
if (ocrFragment.getView() == null) {
ocrFragment.setCreateViewCallback(new MainScreenFragment.OnCreateViewCallback() {
#Override
public void onCreateView() {
ocrFragment.ocrImage(picture, false);
}
});
} else {
ocrFragment.ocrImage(picture, false);
}
If you are trying to perform something after onCreateView is called, use onViewCreated:
Called immediately after onCreateView(LayoutInflater, ViewGroup,
Bundle) has returned, but before any saved state has been restored in
to the view. This gives subclasses a chance to initialize themselves
once they know their view hierarchy has been completely created. The
fragment's view hierarchy is not however attached to its parent at
this point.
public void onViewCreated(View view, Bundle savedInstanceState) {
MyActivity myActivity = (MyActivity) getActivity();
MyActivity.newAsyncTask(mPar);
}
You could also check for Fragment.isVisible() because a Fragment is in visible state when it's in the offscreen page limit of a ViewPager.
Edit: But it just really depends on what you really want to achieve with your question. Perhaps some kind of update to all UIs in your Fragments when their UI is ready?
EDIT:
Just another addition, you could listen to onViewCreated() and set a flag. Or notify your Activity and do further work (getActivity() will return your Activity at this point). But really, better state what you want to accomplish with your question.
So here is my code. 'currentFragment' is simply a field that tracks what is currently being displayed. This is in a class that itself is a Fragment (so I have a fragment showing a fragment).
private void selectNavBarItem(NavbarItem v)
{
Fragment fragmentToUse = null;
if (v == setpointsNavItem)
{
fragmentToUse = setpointsFragment;
}
else if (v == rapidSetupNavItem)
{
fragmentToUse = rapidSetupFragment;
}
else if (v == outdoorResetNavItem)
{
fragmentToUse = outdoorResetFragment;
}
else if (v == rampDelayNavItem)
{
fragmentToUse = rampDelayFragment;
}
if (fragmentToUse != null)
{
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
if (currentFragment != null)
{
ft.detach(currentFragment);
}
currentFragment = fragmentToUse;
if (currentFragment.isDetached())
{
ft.attach(currentFragment);
}
else
{
ft.add(R.id.setup_content_holder, currentFragment);
}
ft.addToBackStack(null);
ft.commit();
}
Everything looks great, but the views are getting recreated for all the fragments (onCreateView() and onViewCreated()). I was hoping that attaching and detaching would work, but it doesn't. The reason I want to maintain the view is so the user's selections are still there when they navigate back.
Another option is showing and hiding, but I don't know how to make that work because the fragment that owns this code has a FrameLayout (R.id.setup_content_holder) that holds the fragment I want to add, and I can't just add four fragments to it but hide three of them. There is an option to add a fragment with no container, but I have no idea how that is supposed to work.
So, any ideas?
Try this, this will solve your frgment view r-creating issue;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (mFragmentView != null) {
((ViewGroup) mFragmentView.getParent()).removeView(mFragmentView);
return mFragmentView;
}
mFragmentView = inflater.inflate(R.layout.home_fragment, container, false);
..... // your remaining code
}
The OnCreateView methods are always called within a Fragment.
To solve the problem you're describing what you really need to do is save the state of the fragment, then when it returns the application will restore what you saved.
e.g. (within the fragment class in question):
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
savedInstanceStatePutString("userString", someTextView.getText().toString());
savedInstanceStatePutInt("userInt", userInt);
// etc...
}
#Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
someTextView.setText(savedInstanceState.getString("userString"));
userInt = savedInstanceState.getInt("userInt");
}
That should solve your problem while hopping between fragments; the application will call onSaveInstanceState and onRestoreInstanceState when a fragment gets pushed into or pulled out of the stack.
It will not save them forever, however! If you need more persistent storage, look into other options such as saving to a sqlite database.
OP here.
So I hacked something together here, and I'm wondering if it's a good idea. I have a Fragment holding a View. The View contains everything I want to save (in the short term, of course - this isn't supposed to be any more persistent than RAM). When the Fragment calls onCreateView() I simply return the already-created View.
Now, I ran into an issue where the View was not being removed by the fragment manager. I added a call in onPause() to make sure it's removed from the parent.
Everything seems to work fine, but I want to make sure I'm not doing something really bad. I know Android really really wants to manage its view lifecycles itself, but I do not want it recreating them every damn time. They are complicated and I don't want to deal with re-initializing all the subview text/image/state. Will I run into issues in my attempt to do a run-around Android's normal operating procedure?
EDIT: forgot the code:
public class OutdoorResetFragment extends Fragment
{
private OutdoorResetView view;
public OutdoorResetFragment()
{
}
public void onAttach(Activity activity)
{
if (view == null || view.getContext() != activity)
{
view = new OutdoorResetView(activity);
}
super.onAttach(activity);
}
public void onPause()
{
super.onPause();
ViewGroup container = (ViewGroup) view.getParent();
if (container != null)
{
container.removeAllViews();
}
}
#Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState)
{
return view;
}
public OutdoorResetView getView()
{
return view;
}
}
Listed below is my basic code for controlling the maps. I do some really advanced stuff later. Everything seems to work perfect, until onResume().
Here is the layout, you navigate through the app in 1 single activity, with multiple fragments. This mapFragment is contained inside of a fragment. This works fine. However when I add another fragment and push this one on the back stack, when i come back to it later, the map is unresponsive.
I tried fixing this by moving my call to setupMaps(); into the onResume(), however this caused gMaps to be null when I get it from gMaps = mapFragment.getMap(); in the setViews().
How should I handle this?
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
root = inflater.inflate(R.layout.fragment_maps, container, false);
setupMaps();
return root;
}
private void setupMaps()
{
gMaps = null;
fm = getActivity().getSupportFragmentManager();
mapFragment = SupportMapFragment.newInstance();
android.support.v4.app.FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.add(R.id.flMapContainer, mapFragment).commit();
}
#Override
public void onResume()
{
super.onResume();
mapFragment.onResume();
setViews();
}
private void setViews()
{
gMaps = mapFragment.getMap();
getData(); // initializes overlays, markers, polygons etc.
}
#Override
public void onPause()
{
mapFragment.onPause();
super.onPause();
}
Do you see anything in your logcat? I've had some issues like this before, and I believe it was related to the old map fragment's View not being removed from its parent ViewGroup before creating a new instance of it. This resulted in errors regarding a duplicate fragment.
Try removing all views from your flMapContainer before you create the new instance of the SupportMapFragment.
To implement Scott Stanchfield's solution:
call cleanFrame() when add/replace another fragment.
public void cleanFrame(){
FrameLayout FL = (FrameLayout) thisview.findViewById(R.id.myfragmentcontainer);
FL.removeAllViewsInLayout();
}
I have disabled hardware acceleration inside manifest.xml and after it everything started to work successfully:
<application android:hardwareAccelerated="false">
...
</application>