If Flow encapsulates the UI and provides a navigation stack, where do we put our business logic? What if we need to hook into the lifecycle? Mortar is a library that uses the MVP pattern to wrap up business logic into presenters and interact with views. Each presenter is scoped, thanks to Dagger , so that resources are cleaned up if a presenter gets destroyed.
Mortar, also, gives you a very scaled back lifecycle to manage the life of a presenter. Lots of boilerplate code. It has been a year since the last minor update to Mortar. Flow finally hit 1. In the wake of the post, it seemed as though two communities instantly formed: If your app is structured using this pattern, then you might be able to switch back to using multiple activities. If you are not using this pattern and are just trying to move away from Fragments, you could consider a custom view instead. There are challenges, however, with both of these approaches.
In the case of switching to multiple activities, one area of interest we would need to pay extra attention to is how we optimize interfaces to support different form factors such as tablets. Android gives us the ability to qualify resources for the particular screen sizes, which would work great for simple layouts. But what if you wanted to implement a master-detail interface where the experience is different between devices? You may find yourself coding special logic to handle each form factor or even creating entirely different activities to solve the problem.
But this approach can lead to interfaces that are tedious and difficult to maintain. One of the nice things about Fragments is they allow us to isolate our UI components so activities could focus on everything else. Could custom views give us the same benefits? The short answer is yes, unless we have to worry about the state of our UI. Remember, one of the goals of Activities and Fragments as an extension is that they always stay in the state where you left them. There are several items to consider if you decide to implement custom views:.
View State In the case of orientation changes, the view will be destroyed and recreated. The goal of both of these libraries is to eliminate the boilerplate needed to create a Parcelable. Managing the backstack How do you handle the back button? Depending on the use case, you may want to emulate the behavior you get with Fragments.
For example, say you wanted to navigate between two views within a single activity and return to the same state using the android back button or toolbar. The FragmentManager allowed us to push Fragments onto a historical stack and manually call popbackstack , returning to the previous state, whenever the user pushes the back button. In the wake of Flow and Mortar, there are now a couple of libraries that can help deal with these scenarios. The result is a library called Simple-Stack.
Simple-Stack is a backstack library that allows you to represent and navigate between the states of your application. The backstack is persisted through the BackstackManager which allows it to survive configuration changes and process termination. Similar to Fragments, Conductor is architecture agnostic. Controllers, which are simple Java classes that wrap views and give you access to a simplified lifecycle. Yet, through Mortar, Square showed how it is possible to decouple the business logic and view code from the Android lifecycle, making their presenters testable.
We have now seen what Square was able to accomplish with Flow and Mortar and we have also discussed how design patterns can make our code more testable. Unfortunately, unless you are working on a brand new application or have a large maintenance budget , you are likely going to be maintaining an existing application that uses Fragments.
Until there comes a time when a library or component is created to complete replace Fragments, and you have the budget to revisit your legacy applications, there is certainly something we can do today to make Fragments easier to work with and increase testability and coverage.
The idea is to make our Fragments smarter by moving decoupling the business code from the android code. We can then unit test our business code to obtain deeper code coverage within our application. You can find the code for this example here. Using a Dependency Injection framework in this case Dagger 2 , I can inject the presenter directly into the Fragment since we are unable to do constructor injection with Fragments. By injecting the presenter into the Fragment, I have isolated the presenter and view logic so I can test each independently from the Fragment using any unit testing framework.
When the system creates this activity layout, it instantiates each fragment specified in the layout and calls the onCreateView method for each one, to retrieve each fragment's layout. Each fragment requires a unique identifier that the system can use to restore the fragment if the activity is restarted and which you can use to capture the fragment to perform transactions, such as remove it. There are two ways to provide an ID for a fragment:. At any time while your activity is running, you can add fragments to your activity layout.
You simply need to specify a ViewGroup in which to place the fragment. To make fragment transactions in your activity such as add, remove, or replace a fragment , you must use APIs from FragmentTransaction. You can get an instance of FragmentTransaction from your FragmentActivity like this:.
You can then add a fragment using the add method, specifying the fragment to add and the view in which to insert it. The first argument passed to add is the ViewGroup in which the fragment should be placed, specified by resource ID, and the second parameter is the fragment to add. Once you've made your changes with FragmentTransaction , you must call commit for the changes to take effect.
To manage the fragments in your activity, you need to use FragmentManager. To get it, call getSupportFragmentManager from your activity. Some things that you can do with FragmentManager include:. For more information about these methods and others, refer to the FragmentManager class documentation. As demonstrated in the previous section, you can also use FragmentManager to open a FragmentTransaction , which allows you to perform transactions, such as add and remove fragments.
A great feature about using fragments in your activity is the ability to add, remove, replace, and perform other actions with them, in response to user interaction. Each set of changes that you commit to the activity is called a transaction and you can perform one using APIs in FragmentTransaction.
Want to add to the discussion?
You can also save each transaction to a back stack managed by the activity, allowing the user to navigate backward through the fragment changes similar to navigating backward through activities. You can acquire an instance of FragmentTransaction from the FragmentManager like this:. Each transaction is a set of changes that you want to perform at the same time. You can set up all the changes you want to perform for a given transaction using methods such as add , remove , and replace.
Then, to apply the transaction to the activity, you must call commit. Before you call commit , however, you might want to call addToBackStack , in order to add the transaction to a back stack of fragment transactions. This back stack is managed by the activity and allows the user to return to the previous fragment state, by pressing the Back button. For example, here's how you can replace one fragment with another, and preserve the previous state in the back stack:. In this example, newFragment replaces whatever fragment if any is currently in the layout container identified by the R.
By calling addToBackStack , the replace transaction is saved to the back stack so the user can reverse the transaction and bring back the previous fragment by pressing the Back button. FragmentActivity then automatically retrieve fragments from the back stack via onBackPressed. If you add multiple changes to the transaction—such as another add or remove —and call addToBackStack , then all changes applied before you call commit are added to the back stack as a single transaction and the Back button reverses them all together.
The order in which you add changes to a FragmentTransaction doesn't matter, except:.
If you don't call addToBackStack when you perform a transaction that removes a fragment, then that fragment is destroyed when the transaction is committed and the user cannot navigate back to it. Whereas, if you do call addToBackStack when removing a fragment, then the fragment is stopped and is later resumed if the user navigates back. For each fragment transaction, you can apply a transition animation, by calling setTransition before you commit.
Fragments: The Solution to All of Android's Problems
Calling commit doesn't perform the transaction immediately. Rather, it schedules it to run on the activity's UI thread the "main" thread as soon as the thread is able to do so. If necessary, however, you may call executePendingTransactions from your UI thread to immediately execute transactions submitted by commit.
- The Wickedest Man.
- Das ABC meines Lebens (German Edition).
- .
- Le noir lui va si bien: 1 (TERRITOIRES) (French Edition)!
Doing so is usually not necessary unless the transaction is a dependency for jobs in other threads. You can commit a transaction using commit only prior to the activity saving its state when the user leaves the activity. If you attempt to commit after that point, an exception is thrown. This is because the state after the commit can be lost if the activity needs to be restored. For situations in which it's okay that you lose the commit, use commitAllowingStateLoss. Although a Fragment is implemented as an object that's independent from a FragmentActivity and can be used inside multiple activities, a given instance of a fragment is directly tied to the activity that hosts it.
Specifically, the fragment can access the FragmentActivity instance with getActivity and easily perform tasks such as find a view in the activity layout:. To share data, create a shared ViewModel, as outlined in the Share data between fragments section in the ViewModel guide.
If you need to propagate events that cannot be handled with a ViewModel, you can instead define a callback interface inside the fragment and require that the host activity implement it. When the activity receives a callback through the interface, it can share the information with other fragments in the layout as necessary. For example, if a news application has two fragments in an activity—one to show a list of articles fragment A and another to display an article fragment B —then fragment A must tell the activity when a list item is selected so that it can tell fragment B to display the article.
Then the activity that hosts the fragment implements the OnArticleSelectedListener interface and overrides onArticleSelected to notify fragment B of the event from fragment A. To ensure that the host activity implements this interface, fragment A's onAttach callback method which the system calls when adding the fragment to the activity instantiates an instance of OnArticleSelectedListener by casting the Activity that is passed into onAttach:.
If the activity hasn't implemented the interface, then the fragment throws a ClassCastException. On success, the mListener member holds a reference to activity's implementation of OnArticleSelectedListener , so that fragment A can share events with the activity by calling methods defined by the OnArticleSelectedListener interface. For example, if fragment A is an extension of ListFragment , each time the user clicks a list item, the system calls onListItemClick in the fragment, which then calls onArticleSelected to share the event with the activity:.
The id parameter passed to onListItemClick is the row ID of the clicked item, which the activity or other fragment uses to fetch the article from the application's ContentProvider. More information about using a content provider is available in the Content Providers document. Your fragments can contribute menu items to the activity's Options Menu and, consequently, the app bar by implementing onCreateOptionsMenu. In order for this method to receive calls, however, you must call setHasOptionsMenu during onCreate , to indicate that the fragment would like to add items to the Options Menu.
Otherwise, the fragment doesn't receive a call to onCreateOptionsMenu. Any items that you then add to the Options Menu from the fragment are appended to the existing menu items. The fragment also receives callbacks to onOptionsItemSelected when a menu item is selected. You can also register a view in your fragment layout to provide a context menu by calling registerForContextMenu.
When the user opens the context menu, the fragment receives a call to onCreateContextMenu. When the user selects an item, the fragment receives a call to onContextItemSelected. Although your fragment receives an on-item-selected callback for each menu item it adds, the activity is first to receive the respective callback when the user selects a menu item. If the activity's implementation of the on-item-selected callback does not handle the selected item, then the event is passed to the fragment's callback.
This is true for the Options Menu and context menus.
MODERATORS
For more information about menus, see the Menus developer guide and the App Bar training class. The effect of the activity lifecycle on the fragment lifecycle. Managing the lifecycle of a fragment is a lot like managing the lifecycle of an activity. Like an activity, a fragment can exist in three states:. Also like an activity, you can preserve the UI state of a fragment across configuration changes and process death using a combination of onSaveInstanceState Bundle , ViewModel , and persistent local storage.
The most significant difference in lifecycle between an activity and a fragment is how one is stored in its respective back stack.
Fragments of Heraclitus
An activity is placed into a back stack of activities that's managed by the system when it's stopped, by default so that the user can navigate back to it with the Back button, as discussed in Tasks and Back Stack. However, a fragment is placed into a back stack managed by the host activity only when you explicitly request that the instance be saved by calling addToBackStack during a transaction that removes the fragment.
Otherwise, managing the fragment lifecycle is very similar to managing the activity lifecycle; the same practices apply. See The Activity Lifecycle guide and Handling Lifecycles with Lifecycle-Aware Components to learn more about the activity lifecycle and practices for managing it.
Design Philosophy
If you need a Context object within your Fragment , you can call getContext. However, be careful to call getContext only when the fragment is attached to an activity. When the fragment isn't attached yet, or was detached during the end of its lifecycle, getContext returns null. The lifecycle of the activity in which the fragment lives directly affects the lifecycle of the fragment, such that each lifecycle callback for the activity results in a similar callback for each fragment.
For example, when the activity receives onPause , each fragment in the activity receives onPause. Fragments have a few extra lifecycle callbacks, however, that handle unique interaction with the activity in order to perform actions such as build and destroy the fragment's UI. These additional callback methods are:. The flow of a fragment's lifecycle, as it is affected by its host activity, is illustrated by figure 3.
In this figure, you can see how each successive state of the activity determines which callback methods a fragment may receive.