Don’t mess with Android Fragments
There’s this pattern of development that I have chosen to follow when writing mobile apps for the Android Platform. It’s basically having to write apps with the least number of Activities as possible. This is made possible through the use of Fragments.
Fragments are like sub-Activities; they nearly even have identical callbacks. However, Fragments needs a host, an Activity, to run. Since Fragments are re-usable bits of managed UI pieces, it’s easy to have a single Activity change its UI by swapping the main content of the UI with relevant Fragments. When I first learnt about this pattern (thanks to Timi Ajiboye), I was pumped with excitement (I even created a library that simplified this process). IMO, Activities were cool but seemed like an overkill for just UI components. Creating a new Activity for every view just didn’t feel right.
As we all know, software development isn’t that straightforward. I’ve had to learn how not to use Fragments the hard way. I’ll simply describe three solutions to the bad experiences I’ve had and how I’ve learnt from them.
1. Setting Fragment properties using Bundles
Now I bet you thought the best way to set properties on Fragments would be to call the setter methods for each property or initialize them using an overloaded constructor. What if I told you that your app won’t crash, well not immediately. But I can bet you’ll start to see some NullPointerException after a while. The reason is because the Android OS will try to allocate sufficient memory to any app running on the foreground. Long story short, since memory cannot be created or destroyed, if your app has not been used in a long while its state would be saved and your app will be killed to free up memory for the foreground app. When the user returns to your app from the Recent Apps view, your app will crash if you ever attempt to use any property that was not restored properly. These would be everything you set using the setter or overloaded constructor. This crash will also happen if you change orientation of your app while viewing same Fragment.
Using a Bundle will help solve this problem. You can set the properties on your new Fragment when creating the Fragment as seen below
The method above is usually defined in the Fragment you plan to create. If you’re using Android Studio just start typing ‘newi’ in class level position , where you create methods. In your Fragment (in this case MyFragment), retrieve the properties like this
Now you can check your mails in peace knowing that Firebase Crash Reporting will never alert you of such errors again.
2. Check for isAdded() for Async operations
You’ve designed a beautiful list using the RecyclerView, when a user taps an item, you’ll open a DetailsFragment that will display a beautiful animation while the app asynchronously pulls the details from your server. It will stop the animation and display the results when there is a valid response or show an appropriate error message if something goes wrong. Ideally, this shouldn’t be much of a problem. But wait a minute, what will happen if while the details are being retrieved the user navigates back to the list from the DetailsFragment? App Dies™. A simple check on your logs will show that you’re trying to update a Fragment that is not attached to an Activity. A simple way to prevent such error will be to check if the Fragment has been added to the Activity before you perform UI-changing code. See an example below when using Retrofit 2 Callback for async operations.
A smarter way to do this will be to make use of Android Broadcasts. This way, you’ll only listen for a ‘Details Retrieved’ Broadcast when the Fragment is in view. You’ll be safe from attempting UI-changing code when the Fragment is not in view since you’ll unregister your Broadcast Receiver in the onDestroy() after all.
3. Use OnCreate() for initializations
It is very tempting to perform all your computations and heavy lifting in onCreateView() when you’re using Fragments, probably because you’ve just inflated the view and you can easily update the UI in the same code block. Updating the UI may involve creation of objects or even mild IO operations such as querying your SQLite DB. A quick look at the Android Fragment lifecycle will reveal that onCreateView() gets called when it’s time for the Fragment to draw its user interface for the first time or when the Fragment returns to the layout from the backstack. Now imagine a scenario where you have created a list using a RecyclerView, tapping each item will reveal more details about the item in a new Fragment. Say you are fetching your list items from your SQLite DB and you are doing this in your onCreateView() method. What this means is that you’ll be querying your SQLite DB every single time a user views a detail and returns back to the list.
Don’t panic, you can silently update your code before someone notices. Basically any thing that won’t change much should be initialized in the onCreate() not onCreateView() or even worse still onResume(). onResume() can actually be called twice as much as onCreateView(). Don’t put initialization code in onResume() except when you really have to.
That’s it folks, my last post was actually about an issue I encountered when using Fragment Transactions. I can see that using Fragments will come with its challenges, there will always be a way to navigate through and I’ve been able to get help by understanding and aggregating answers from StackOverflow. I am learning how not to mess with Fragments the hard way.