Creating a solid understanding of Android Fragments

Introduction

  • This series is going to be dedicated to the basic to Android development. Join me and let us try to build and understand some cool stuff. All the resources I used to create this post can be found on ticketnote or HERE. With that being said lets get started.

YouTube Version

  • YouTube version available HERE

What is a Fragment?

  • Simply put a fragment is a reusable portion of our app's UI. It defines and manages its own layout, has its own lifecycle(more on this later) and can handle its own input events. A very important piece of information is that fragments must be hosted by an activity.

Modularity of Fragments

  • Fragments introduce modularity and reusability into our activity's UI by allowing us to divide the UI into discrete chunks.
  • Now activities are great for global elements like a navigation drawer. Fragments come in handy when we want to define and manage the UI of a single screen or a portion of the screen. So if we have various screen sizes, say one for a phone and another for a tablet, you would want different layouts for the different screen sizes. You could do this with activities but it would be much easier to do this with fragments. Fragments allow us to divide up our UI, which in turn makes it easier to modify the activities appearance at runtime. Now that we have a basic understanding on what a fragment is, lets create one.

Creating a fragment

  • When creating a basic fragment there are 5 steps that we will follow

1) Create a class that extends the Fragment class.

2) Add the FragmentContainerView to the hosting activity's XML file

3) Create the fragment's XML file

4) Inflate the fragment's view

5) Add the fragment via fragment transaction

1) Create a class that extends the Fragment class

public class ExampleFragment extends Fragment {


}
  • All we are doing here is creating a normal Java class and then have it extend the androidX Fragment class. This allows the Android OS to recognize the class as an activity and it allows us use our own app logic any overridden Fragment methods. Well will later add a constructor to inflate the view but for now just leave it as it is.

2) Add the FragmentContainerView to the hosting activity's XML file

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.fragment.app.FragmentContainerView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/fragment_container_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />


</androidx.constraintlayout.widget.ConstraintLayout>
  • First I want you to understand that this is inside of the MainActivity. The reason for this is that a fragment can not exist outside of an activity. A fragment does not replace an activity, it simply enhances one. Next notice the FragmentContainerView, this defines where the fragment is going to be placed within the activity's view hierarchy. You may have seen FrameLayout being used instead of FragmentContainerView but that is now depreciated. The documentation specifically states, It is strongly recommended to always use a FragmentContainerView as the container for fragments, as FragmentContainerView includes fixes specific to fragments that other view groups such as FrameLayout do not provide.. So if you see any tutorials that use FrameLayout make sure to replace them with FragmentContainerView.

3) Create the fragment's XML file

fragment_example.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <EditText
        android:id="@+id/crime_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="@string/example_text" />

</LinearLayout>
  • When we later inflate a View for our fragment will be using this XML file. Just like an activity a fragment still needs a XML file to determine the UI design. Lets move onto to the next step.

4) Inflate the fragment's view

public class ExampleFragment extends Fragment {

    public ExampleFragment(){
        super(R.layout.fragment_example);
    }
}
  • Notice that all we are doing is adding our fragment's XML file to the super constructor of our previously defined ExampleFragment class. We can do this because the Fragment class has an alternative constructor that can be called to provide a default layout. This fits perfect with our simple layout. However, if you have a more complex layout with extra logic, say you need to add a TextWatcher to a TextView. If that is the case then you can inflate the View inside of the onCreateView() method. Just make sure to only inflate inside of that method. The logic should be moved to the onViewCreated() method.

  • Now before we move onto the fifth and final step we need to talk about a few things. We need to understand what a FragmentManager, FragmentTransaction is and understand a little about the fragment lifecycle.

What is a Fragment Manager?

  • This class is responsible for performing actions on our app's fragments, such as, adding, replacing and removing fragments to the back stack. If you are unfamiliar with the back stack it simply a data structure that is used to organize fragments and it operates on the typical stack notion of first in last out. Thanks to the back stack the user is able to navigate with the back button. In terms of the fragment's lifecycle the Fragment Manager is responsible for determining what state the fragment should be in and then moving it to that state.

What is a Fragment Transaction?

  • So at runtime we can use the FragmentManager class to add, remove, replace or other actions with fragments. Each set of fragment changes that we commit is called a fragment transaction. We save each transaction to the back stack that is managed by the Fragment Manager. As you will see later, we create a fragment transaction by calling beginTransaction().

  • Now that we have those terms defined, lets move on to step 5.

5) Add the fragment via fragment transaction

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if(savedInstanceState == null){
            getSupportFragmentManager().beginTransaction()
                    .setReorderingAllowed(true)
                    .add(R.id.fragment_container_view,ExampleFragment.class,null)
                    .commit();
        }
    }
}
  • Normally our fragment must be embedded within a FragmentActivity class to be able to contribute a portion of the UI to that activity's layout. However, AppCompatActivity is actually a subclass of FragmentActivity so we still inherit from FragmentActivity. AppCompatActivity not only provides us with the FragmentActivity methods, it also allows older Android devices access to newer features, like built in switching between light and dark mode. Then we check if the savedInstance is null, why do we do this? Well this ensures that the fragment is added only once when the activity is first created. When something like a configuration change occurs(screen flip) and the savedInstance is no longer null, the fragment will be automatically restored from the savedInstanceState.

  • Then we have the classic onCreate() method that gets called during the activity's creation. It gets passed a Bundle object which contains any previously saved state. the super.onCreate(savedInstanceState); is to make sure the original onCreate() method gets called, despite us overriding it. setContentView(R.layout.activity_main) is setting the activity's view. Now lets explain some of those weird looking method calls.

getSupportFragmentManager()

  • provided to us by the FragmentActivity class, remember that AppCompatActivity is a subclass of FragmentActivity. We are able to access the getSupportFragmentManager() method through Java's dynamic dispatch. getSupportFragmentManager() will return the FragmentManager for interacting with fragments associated with this activity

beginTransaction()

  • Once we have an instance of the FragmentManager we can call beginTransaction() which will start a series of edit operations on the Fragments associated with this FragmentManager. A fragment can only be added through a fragment transaction.

setReorderingAllowed(true)

  • This method is actually optional, however, the documentation strongly recommends that we add it. This method enables some optimizations when multiple transactions execute together. Also, if any fragments are added and then immediately replaced, this method makes sure that they do not go through any lifecycle methods.

.add(R.id.fragment_container_view,ExampleFragment.class,null)

  • This method gets called when we want to add a fragment to the fragment manager to handle. It is provided 3 parameters.

1) : the identifier for the container of the fragment.

2) : the fragment to be added. This fragment must not already be added to the activity.

3) : a optional tag name for the fragment, to later retrieve the fragment.

  • Now ExampleFragment.class is accessing the instance of this class in memory. What this means is that for any class that we create, Java will automatically create and store an instance of it in memory. So the ExampleFragment.class is us accessing the stored instance in memory

commit()

  • The final call on each fragment transaction must commit the transaction. commit() signals to the fragment manager that all operations have been added to the transaction

  • That is it, we have successfully finished all of the steps to create an fragment in android. But before we go I want to talk a little about the fragment lifecycle.

Fragment lifecycle

  • Each Fragment instance has its own lifecycle. As the user navigates and interacts with the app our Fragment transitions through various states in their lifecycle as they are added, removed and enter or exit the screen. These various states are:

INITIALIZED

CREATED

STARTED

RESUMED

DESTROYED

  • Each of these states have specific callback methods that correspond to each of the changes in the fragment's lifecycle. For today we are only going to cover the INITIALIZED, CREATED and the STARTED states. If you are interested and want a deeper understanding on a fragment's life cycle see the documentation HERE

Fragments and the FragmentManager

  • When a fragment is initialized it begins in the INITIALIZED state. In this state the methods onCreateView() and onViewCreated() are called. In our example we don't use those methods but we should still notice that they are being called. In order for a fragment to transition through the rest of its lifecycle it must be added to a fragment manager. The fragment manager is responsible for determining what state a fragment should be in and then moving them into that state. Once a fragment has been added to the fragment manager it enters the CREATED state. This state is where we would want to restore any previously saved state.

Fragment created and view initialized

  • The next step is for our view to get created. Since we provided our fragment constructor with a XML file our view is automatically inflated for us. We did this back in step 4:
public class ExampleFragment extends Fragment {

    public ExampleFragment(){
        super(R.layout.fragment_example);
    }
}

Fragment is started

  • Once the view is inflated our fragment enters the 'STARTED' state, this state guarantees that the fragment's view is available. If we had a nested fragment, this is the state that we could safely perform transactions on it.

Conclusion

  • Thank you for taking the time out of your day to read this blog post of mine. If you have any questions or concerns please comment below or reach out to me on Twitter.

25