Xamarin Android - Fragments and Orientations/Rotation

When you rotate/change the orientation of your Android application the Activity will be destroyed and recreated.  What this means is that any variables, fragments will be destroyed when the user changes the orientation of their device (say going from portrait to landscape).  Your users will hate you.  For example, If they are filling out a form and by accident the orientation changes all fields entered will be lost.

How can we avoid this bad experience? There are a few ways.

1. Within the OnCreate of the Activity you can force the orientation to one or the other.  A user can rotate the device however the user interface does not change orientation.

RequestedOrientation = Android.Content.PM.ScreenOrientation.Portrait;


2. Using an attribute on the class will lock the orientation.  The following ignores the orientation and the screen size changes.  This is easy however the difficulty is that your application will not be responsive to size/orientation changes.  Your application will respond to different screen sizes or orientation.  This too can be less than optimum.

[Activity(Label = "AppDave", MainLauncher = true, Icon = "@drawable/icon", Theme = "@style/MyTheme", ConfigurationChanges = Android.Content.PM.ConfigChanges.ScreenSize | Android.Content.PM.ConfigChanges.Orientation)]
    public class MainActivity : AppCompatActivity
    {
    }


3. In the following example I have an application with three fragments.   The first fragment with tag “Fragment1” gets set when the application loads.  I will use this tag as the test to see if the activity has started at least one time.  Within the OnCreate method I check to see if the Activity has run by checking SupportFragmentManager.FindFragmentByTag(“Fragment1”).

if (SupportFragmentManager.FindFragmentByTag("Fragment1") != null)
{
    if (SupportFragmentManager.FindFragmentByTag("Fragment1") != null)
        _fragment1 = SupportFragmentManager.FindFragmentByTag("Fragment1") as Fragment1;

    if (SupportFragmentManager.FindFragmentByTag("Fragment2") != null)
        _fragment2 = SupportFragmentManager.FindFragmentByTag("Fragment2") as Fragment2;

    if (SupportFragmentManager.FindFragmentByTag("Fragment3") != null)
        _fragment3 = SupportFragmentManager.FindFragmentByTag("Fragment3") as Fragment3;
}
else
{
    //no fragments in the container
    _fragment1 = new Fragment1();
    
    var trans = SupportFragmentManager.BeginTransaction();
    trans.Add(Resource.Id.fragmentContainer, _fragment1, "Fragment1");                
    trans.Commit();
    _currentFragment = _fragment1;
}

I am going through this backwards but this is the method that I am using to replace the current fragment with a new one.  Notice however that I am adding it with a specified tag so that we can recover it later

private void ReplaceFragment(SupportFragment fragment, string tag)
{
    if (fragment.IsVisible)
        return;

    var trans = SupportFragmentManager.BeginTransaction();           
    trans.Replace(Resource.Id.fragmentContainer, fragment, tag);
    trans.AddToBackStack(null);
    trans.Commit();
    _currentFragment = fragment;
}

And finally I will show the method that responds to a menu selection which calls the ReplaceFragment method

public override bool OnOptionsItemSelected(IMenuItem item)
{    
    switch (item.ItemId)
    {                
        case Android.Resource.Id.Home:
            //The hamburger icon was clicked which means the drawer toggle will handle the event
            //all we need to do is ensure the right drawer is closed so the don't overlap
            _drawerLayout.CloseDrawer(_rightDrawer);
            _drawerToggle.OnOptionsItemSelected(item);
            return true;

        //case Resource.Id.action_refresh:
        //    //Refresh
        //    return true;

        case Resource.Id.action_fragment1:
            //ShowFragment(_fragment1);

            if (_fragment1 == null)                    
                _fragment1 = new Fragment1();          
                
                ReplaceFragment(_fragment1, "Fragment1");
            return true;
        case Resource.Id.action_fragment2:
            //ShowFragment(_fragment2);
            if (_fragment2 == null) 
                _fragment2 = new Fragment2();
                
            ReplaceFragment(_fragment2, "Fragment2");
            return true;
        case Resource.Id.action_fragment3:
            //ShowFragment(_fragment3);
            
            if (_fragment3 == null)                    
                _fragment3 = new Fragment3();

            ReplaceFragment(_fragment3, "Fragment3");
            return true;
        case Resource.Id.action_help:
            if (_drawerLayout.IsDrawerOpen(_rightDrawer))
            {
                //Right Drawer is already open, close it
                _drawerLayout.CloseDrawer(_rightDrawer);
            }
            else
            {
                //Right Drawer is closed, open it and just in case close left drawer
                _drawerLayout.OpenDrawer(_rightDrawer);
                _drawerLayout.CloseDrawer(_leftDrawer);
            }
            return true;
        default:
            return base.OnOptionsItemSelected(item);
    }
}

So finally, why are we using the transaction manager to replace fragments instead of showing and hiding them?  If we load up all fragments upon start up and show/hide they all are residing in memory which can be problematic if the fragments are large or you have many of them in your application.  Using the replace approach optimizes memory to only work with active fragments.  While a fragment is not in the current layout it is ‘paused’ and the memory footprint is much less.

 

Reference: http://developer.android.com/guide/components/fragments.html

image

Author

david

comments powered by Disqus