linkYou Don't Need to Null Out Views

There's been a long standing footgun when using fragments on Android because the fragment may live longer than it's views. Solutions to this have ranged from ignoring the problem to some complicated rxjava setup. However, with the additions to AndroidX this is no longer necessary!

The trick is to scope all view interactions to onViewCreated().

1linkclass MyFragment : Fragment(R.layout.my_fragment) {

2link override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

3link val binding = MyBinding.bind(view)

4link binding.text = "Look, no leaks!"

5link }

6link}

If you don't assign any views to fragment fields, you don't need to worry about clearing them out.

Sidenote: if you are not familiar with some of the features here, check out the new fragment constructor and view binding.

Now, you may be wondering how you are supposed to accomplish much with this limited scope, but with a few other AndroidX features it turns out you can do quite a lot.

linkListening for updates

The other key part to this is viewLifecycleOwner. This gives you a scope that you can use for asynchronous operations. For example, you can listen to liveData:

1linkoverride fun onViewCreated(view: View, savedInstanceState: Bundle?) {

2link val binding = MyBinding.bind(view)

3link viewModel.title.observe(viewLifecycleOwner) { text -> binding.text = text }

4link}

Or scope a coroutine/Flow operation:

1linkoverride fun onViewCreated(view: View, savedInstanceState: Bundle?) {

2link val binding = MyBinding.bind(view)

3link viewLifecycleOwner.lifecycleScope.launchWhenStarted {

4link viewModel.title.collect { text -> binding.text = text }

5link }

6link}

linkLifecycle Events

If you need to handle view-related things in other lifecycle events like onPause/onResume, you can attach your own LifecycleObserver.

1linkoverride fun onViewCreated(view: View, savedInstanceState: Bundle?) {

2link val binding = MyBinding.bind(view)

3link viewLifecycleOwner.lifecycle.addObserver(object: DefaultLifecycleObserver {

4link override fun onResume(owner: LifecycleOwner) {

5link Snackbar.make(view, "OnResume Called", Snackbar.LENGTH_LONG).show()

6link }

7link })

8link}

linkOther Callbacks

The one place where this gets more tricky is if you need to handle other callbacks that haven't yet been broken out by AndroidX. For example, runtime permissions. In these cases, I would recommend routing the event in a way you can react to it.

For example, you could use an event wrapper with live data:

1linkprivate val showCamera = MutableLiveData<Event<Boolean>>()

2link

3linkoverride fun onViewCreated(view: View, savedInstanceState: Bundle?) {

4link val binding = MyBinding.bind(view)

5link

6link showCamera.observe(viewLivecycleOwner) { event ->

7link event.getContentIfNotHandled()?.let { permission ->

8link if (permission) {

9link startActivity(Intent(requireContext(), CameraActivity::class.java)

10link }

11link }

12link }

13link

14link binding.showCameraButton.setOnClickListener {

15link if (ContextCompat.checkSelfPermission(

16link requireContext(),

17link Manifest.permission.CAMERA == PackageManager.PERMISSION_GRANTED

18link ) {

19link showCamera.value = Event(true)

20link } else {

21link requestPermissions(arrayOf(Manifest.permission.CAMERA), 0)

22link }

23link }

24link}

25link

26linkoverride fun onRequestPermissionsResult(

27link requestCode: Int,

28link permissions: Array<out String>,

29link grantResults: IntArray

30link) {

31link if (requestCode == 0 && grantResults.size == 1) {

32link showCamera.value = Event(grantResults[0] == PackageManager.PERMISSION_GRANTED)

33link }

34link}

Or use a Channel:

1linkprivate val showCamera = Channel<Boolean>(1)

2link

3linkoverride fun onViewCreated(view: View, savedInstanceState: Bundle?) {

4link val binding = MyBinding.bind(view)

5link

6link viewLifecycleOwner.lifecycleScope.launchWhenStarted {

7link for (permission in showCamera) {

8link if (permission) {

9link startActivity(Intent(requireContext(), CameraActivity::class.java)

10link }

11link }

12link }

13link

14link binding.cameraButton.setOnClickListener {

15link if (ContextCompat.checkSelfPermission(

16link requireContext(),

17link Manifest.permission.CAMERA == PackageManager.PERMISSION_GRANTED

18link ) {

19link showCamera.offer(true)

20link } else {

21link requestPermissions(arrayOf(Manifest.permission.CAMERA), 0)

22link }

23link }

24link}

25link

26linkoverride fun onRequestPermissionsResult(

27link requestCode: Int,

28link permissions: Array<out String>,

29link grantResults: IntArray

30link) {

31link if (requestCode == 0 && grantResults.size == 1) {

32link showCamera.offer(grantResults[0] == PackageManager.PERMISSION_GRANTED)

33link }

34link}

By scoping your views to onViewCreated you don't have to worry about nulling them out our calling them at the wrong time. And AndroidX gives you the tools to do it!

You Don't Need to Null Out ViewsListening for updatesLifecycle EventsOther Callbacks

Home You Dont Need to Null Out Views Some More Biometric Messes The Mess that is Android Biometrics Random musing on Jetpack Compose