Learning Android Development

7 Common Mistakes Easily Made with Android Fragment

You can eliminate these fragment issues with code review

Elye
Elye
Jan 31 · 8 min read
Photo by krakenimages on Unsplash

solid understanding of how Fragment works is essential when one working with Android Development. However, Fragment is still a complicated subject, and one can commonly miss out on something.

Mistake makes when working in Fragment sometimes are hard to debug, as it is not always replicable due to its complicated lifecycle event.

However, some of those issues could be easily prevented during coding review. Below are 7 of them

1. Create a new Fragment without checking savedStateInstance

In the Activity (or Fragment), if we have a Fragment as a view by default, then we can create it during onCreate as below.

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
supportFragmentManager.beginTransaction()
.replace(R.id.container, NewFragment())
.commit()
}

Why this is not good

There will be a problem with the above code. When your activity is killed and restored by the system, a duplicate new Fragment will be created, i.e. the restored Fragment and a newly created one.

The correct way

We should always remember to wrap it within savedInstanceState == null.

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState == null) {
supportFragmentManager
.beginTransaction()
.replace(R.id.container, NewFragment())
.commit()
}
}

This will prevent a new Fragment from being created and transacted if the existing fragment is restored.

If case you want to avoid restoration, below is some trick (though not recommended for professional app)

2. Create Fragment Owned Object during onCreateView

Sometimes we have data object that lives through the life of Fragment existence. We thought we can create it during onCreateView, as this will be called once when the Fragment is created, or when the Fragment is restored from a killed state.

private var presenter: MyPresenter? = nulloverride fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
presenter = MyPresenter() return inflater.inflate(R.layout.frag_layout, container, false)
}

Why this is not good

However, there is an issue with this. If this fragment is being replaced by another fragment in the container, the fragment is not killed. The data object in the Fragment still there.

When his fragment is restored (i.e. the other fragment got pop out), then onCreateView will be called again. Hence data object (e.g. presenter) will get recreated. All your data stored in it will be reset.

Image from https://raw.githubusercontent.com/evant/simplefragment/master/images/lifecycle.png

Not the above diagram who onCreateView could be called again and again in the same Fragment instance.

The not so correct way

We can add a null check to ensure the data object is not there before creating.

private var presenter: MyPresenter? = nulloverride fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
if (presenter != null) presenter = MyPresenter() return inflater.inflate(R.layout.frag_layout, container, false)
}

While this work, it is not ideal

The better way

We should instead create the fragment owned data object in onCreate

private var presenter: MyPresenter? = nulloverride fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
presenter = MyPresenter()
}

With this in place, the data object will only be created once every fragment creation. It will not be recreated when we pop the top layer fragment and get the fragment view re-displayed i.e. onCreateView called.

3. Perform state restoration during onCreateView

I understand that onCreateView does provide savedInstanceState

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
if (savedInstanceState != null) {
// Restore your stuff here
}
// ... some codes creating view ...
}

So we think we can restore our state there.

Why this is not good

But this might cause a strange issue, where your data in some fragments (not-top visible fragments) within the stacks missing if

  • you have more than one fragment in the stacks (using replace fragment API instead of add)
  • you background your app and restore it twice or more times
  • your fragment is destroyed (e.g. by the system) and restored

Check out the below blog for the detail of the issue

The better way

Just like item 2 above, we should instead restore the state during onCreate

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState != null) {
// Restore your stuff here
}
}

This will almost certain that your Fragment state gets restored always, regardless of if your view is created or not (i.e. the fragments in the stacks that are not visible, will also restore its data)

4. Keep reference to Fragment in Activity

Sometimes for some reason, in our activity (or parent fragment), we want to access to the fragment. Hence one easy way is to keep a reference to the fragment as below

private var myFragment: MyFragment? = nulloverride fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState == null) {
myFragment = NewFragment()
supportFragmentManager.beginTransaction()
.replace(R.id.container, myFragment)
.commit()
}
}
private fun anotherFunction() {
myFragemnt?.doSomething()
}

Why this is not good

Fragment does has its lifecycle. It gets killed and restore by the system. This means the original fragment referred to is no longer there.

If we do keep a reference to the fragment in our activity, we’ll need to ensure we keep updating the reference to the right fragment, and this might be tricky if missed.

The better way

Transact your fragment with a Tag.

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState == null) {
supportFragmentManager
.beginTransaction()
.replace(R.id.container, NewFragment(), FragmentTag)
.commit()
}
}
private fun anotherFunction() {
(supportFragmentManager.findFragmentByTag(FragmentTag) as?
NewFragment)?.doSomething()
}

When you need to access it, you can always find it from the fragment transaction instead.

Though this is a possible way, we should still minimize such communication between the fragment and activity (or parent fragment).

5. Access the View during onSavedStateInstance of Fragment

Sometimes, we want to access some view information to be saved when the fragment is killed by the system e.g.

override fun onSaveInstanceState(outState: Bundle) {            
super.onSaveInstanceState(outState)
binding?.myView?.let {
// doSomething with the data, maybe to save it?
}
}

Why this is not good

Check this scenario out.

  • If the fragment is not killed but instead replaced by another fragment, the onViewDestroy() of the fragment is called (and we normally set binding = null there.
  • The onSaveInstanceState is not called as the fragment is still there. But the view of the fragment is no there anymore.
  • Then, in the event that the fragments got killed by the system that time then onSaveInstanceState is called. But since the access to binding is null, and whatever intended to be done by that code will not be executed.

The correct way

Anything you want to access from the view, should be done before onSavedStateInstance and store somewhere else. Preferably all these are done in the presenter or the View Model

6. Prefer Add Fragment API by default instead of Replace

To transact a fragment, we have replace and add. Sometimes we just wonder which should we use. Perhaps we should use add as it sounds more logical to do so.

supportFragmentManager.beginTransaction()
.add(R.id.container, myFragment)
.commit()

The benefit of using add, will ensure the view of the bottom fragment not destroyed and never need to be recreated when the top fragment pops out. The below are some scenarios where it is useful.

  • when the next fragment is add on top of a fragment, both are still visible and stack up with one another. If you have a semi-transparent view on the top fragment, you can see the bottom one.
  • when your bottom added fragment is something that takes a long time to load (e.g. a loaded Webview), you want to avoid it loaded again when the top fragment is popped out, then you want to add the top fragment instead of replace.

Why this is not good

The 2 scenarios provided above are rare cases. So the need of add should be limited as there are disadvantage of it.

  • Using add will keep the bottom fragment views visible, and take up more memories unnecessarily (since it is blocked by the top fragment).
  • Having more than one fragment in the stack added and visible might cause state restoration issues at times when they are all restored together. Below is the case where 2 fragments loaded together and using, which cause complication and confusing issue.

The preferred way

Use replace instead of add, even for the first fragment to be committed. As for the first fragment, both add and replace has no difference, might as well just use replace to make it a default and common practice.

7. Use Simple Class Name as TAG for Fragment

Sometimes we want to tag the fragment for retrieval later. We can tag it simply using the simple name of the class, as it is convenient.

supportFragmentManager.beginTransaction()
.replace(
R.id.container,
fragment,
fragment.javaClass.simpleName)
.commit()

Why this is not good

In Android, we obfuscate the class name using Proguard or DexGuard. And through the process, the obfuscate simple name might collide with other class names, as explained below.

It might be rare, but when it happens, it could make you pull your hair out.

The preferred way

Consider using a constant or the canonical name as the tag instead. This will better assure it will be unique

supportFragmentManager.beginTransaction()
.replace(
R.id.container,
fragment,
fragment.javaClass.canonicalName)
.commit()

Much of the issues above are due to the complicated fragment lifecycles. You can find out more about them in the articles below

I have spent much time debugging the fragments related issues and learn the hard way. Hopes the above 7 tips will help you prevent tons of strange and hard to replicable issues, by just carefully look out for them during coding review.

Mobile App Development Publication

Sharing Mobile App Development and Learning

Elye

Written by

Elye

Passionate about learning, and sharing mobile development and others https://twitter.com/elye_project https://www.facebook.com/elye.proj

Mobile App Development Publication

Sharing iOS, Android and relevant Mobile App Development Technology and Learning

Elye

Written by

Elye

Passionate about learning, and sharing mobile development and others https://twitter.com/elye_project https://www.facebook.com/elye.proj

Mobile App Development Publication

Sharing iOS, Android and relevant Mobile App Development Technology and Learning

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store