Skip to content

Fragments

Fragments Deep Dive

Overview

Fragments are reusable UI components within an Activity. They have their own lifecycle, manage their own UI, and can be dynamically added, removed, and replaced. Understanding Fragment lifecycle and architecture is essential for modern Android development.


Fragment Lifecycle

Lifecycle Methods (Complete Sequence)

onCreate()
  โ†“
onCreateView() [RETURN Layout]
  โ†“
onViewCreated() [View tree ready]
  โ†“
onStart()
  โ†“
onResume()
  โ†“
[USER INTERACTION]
  โ†“
onPause()
  โ†“
onStop()
  โ†“
onDestroyView()
  โ†“
onDestroy()
  โ†“
onDetach()

Key Lifecycle Methods

onCreate() - Fragment instance created (UI not yet created) - Safe to access arguments - Don't access views here yet

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val userId = arguments?.getInt("user_id") ?: 0
    viewModel.loadUser(userId)
}

onCreateView() - Called to create fragment's UI view hierarchy - MUST return the root View of hierarchy - First time to inflate layout

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View {
    return inflater.inflate(R.layout.fragment_main, container, false)
    // Return inflated view, container is Activity's container
}

onViewCreated() - Called after onCreateView() returns - View hierarchy now fully created - Safe to access views here - Modern Kotlin Synthetics or View Binding

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    binding = FragmentMainBinding.bind(view)

    binding.button.setOnClickListener {
        // Handle click
    }

    viewModel.data.observe(viewLifecycleOwner) { data ->
        updateUI(data)
    }
}

onStart() - Fragment visible to user - Navigate to onResume() โ†’ onStop() โ†’ back to onStart()

onResume() - Fragment interactive (has focus) - All animations should be at full speed

onPause() - Fragment losing focus - Stop animations, pause playback - Quick return required

onStop() - Fragment no longer visible - Safe for state saving

onDestroyView() - View hierarchy being destroyed - Null out your view binding/references here

override fun onDestroyView() {
    // Must null out binding to avoid memory leaks
    binding = null
    super.onDestroyView()
}

onDestroy() - Fragment instance being destroyed - Clean up observers, resources

onDetach() - Fragment being removed from Activity - Last lifecycle method


Fragment vs Activity

Comparison Table

Feature Activity Fragment
Creation Entry point in manifest No manifest entry needed
Lifecycle Owns full lifecycle Dependent on host Activity
Standalone Can exist alone Must be hosted in Activity
Window Has own window Shares Activity's window
Navigation Full screen transitions Can update partial UI
Back button Handled by system Controlled by FragmentManager
Memory Heavier Lighter, reusable

When to Use Each

Use Activity for: - App entry points - Full-screen distinct sections - Navigation between major app sections

Use Fragment for: - Reusable UI components - Modular screens - Tabbed interfaces - Master-detail layouts - Swap UI without changing Activity

Architecture Pattern

MainActivity (Activity - container)
  โ”œโ”€ Fragment A (reusable component)
  โ”œโ”€ Fragment B (reusable component)
  โ””โ”€ Fragment C (reusable component)

All fragments share MainActivity's window
Can be swapped independently

Fragment Communication

Pattern 1: Shared ViewModel (BEST PRACTICE)

// Shared ViewModel
class UserViewModel : ViewModel() {
    val selectedUser = MutableLiveData<User>()
}

// Fragment A (Master)
class UserListFragment : Fragment() {
    private val viewModel: UserViewModel by activityViewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.list.setOnItemClickListener { user ->
            viewModel.selectedUser.value = user  // Notify others
        }
    }
}

// Fragment B (Detail)
class UserDetailFragment : Fragment() {
    private val viewModel: UserViewModel by activityViewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel.selectedUser.observe(viewLifecycleOwner) { user ->
            displayUserDetails(user)
        }
    }
}

Why this works: - ViewModel shared across fragments (owned by Activity) - Survives fragment replacement - Survives configuration changes - LiveData observers automatically lifecycle-aware

Pattern 2: Interface Callback (Legacy)

interface UserSelectionListener {
    fun onUserSelected(user: User)
}

class UserListFragment : Fragment() {
    lateinit var listener: UserSelectionListener

    override fun onAttach(context: Context) {
        super.onAttach(context)
        listener = context as? UserSelectionListener
            ?: throw RuntimeException("Must implement UserSelectionListener")
    }

    private fun onUserClicked(user: User) {
        listener.onUserSelected(user)
    }
}

class MainActivity : AppCompatActivity(), UserSelectionListener {
    override fun onUserSelected(user: User) {
        val detailFragment = UserDetailFragment.newInstance(user)
        supportFragmentManager.beginTransaction()
            .replace(R.id.detail_container, detailFragment)
            .commit()
    }
}

Downside: Verbose, tightly couples to Activity

Pattern 3: Bundle Arguments (Static Data)

class UserDetailFragment : Fragment() {
    companion object {
        private const val ARG_USER_ID = "user_id"

        fun newInstance(userId: Int) = UserDetailFragment().apply {
            arguments = Bundle().apply {
                putInt(ARG_USER_ID, userId)
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val userId = arguments?.getInt(ARG_USER_ID) ?: 0
        viewModel.loadUser(userId)
    }
}

// Usage
val fragment = UserDetailFragment.newInstance(123)

Use for: Initial data passing only, not ongoing communication


Fragment Back Stack

How It Works

fragmentManager.beginTransaction()
    .replace(R.id.container, newFragment)
    .addToBackStack(null)
    .commit()

// Stack state:
// TOP: [NewFragment]
// [PreviousFragment]

With Named Back Stack Entry

fragmentManager.beginTransaction()
    .replace(R.id.container, newFragment)
    .addToBackStack("detail_screen")
    .commit()

// Pop by name
fragmentManager.popBackStackImmediate("detail_screen",
    FragmentManager.POP_BACK_STACK_INCLUSIVE)

Back Button Behavior

// Back pressed
// System calls: fragmentManager.popBackStack()

// Stack pops:
// [PreviousFragment] โ†’ TOP
// [CurrentFragment] removed

Manual Back Stack Control

// Go back specifically
fragmentManager.popBackStack()

// Check if can go back
if (fragmentManager.backStackEntryCount > 0) {
    fragmentManager.popBackStack()
}

// Clear entire back stack
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)

Animation with Back Stack

fragmentManager.beginTransaction()
    .setCustomAnimations(
        R.anim.slide_in,
        R.anim.fade_out,
        R.anim.fade_in,
        R.anim.slide_out
    )
    .replace(R.id.container, newFragment)
    .addToBackStack(null)
    .commit()

Best Practices

1. Always Use Fragment Factory for Arguments

// โœ… CORRECT
class UserDetailFragment : Fragment() {
    companion object {
        fun newInstance(userId: Int) = UserDetailFragment().apply {
            arguments = Bundle().apply { putInt("user_id", userId) }
        }
    }
}

// Use
val fragment = UserDetailFragment.newInstance(123)

// โŒ WRONG - Args lost on process death
class UserDetailFragment : Fragment(userId: Int) {}

2. Always Clean Up View References

override fun onDestroyView() {
    binding = null  // Prevent memory leaks
    super.onDestroyView()
}

3. Use Lifecycle-Aware Observers

// โœ… CORRECT - automatically handles lifecycle
viewModel.data.observe(viewLifecycleOwner) { data ->
    updateUI(data)
}

// โŒ WRONG - can cause memory leaks
viewModel.data.observe(this) { data ->  // 'this' is Fragment lifecycle
    updateUI(data)
}

4. Use Appropriate ViewManager for Different Scenarios

// Replace (swap fragments)
fragmentManager.beginTransaction()
    .replace(R.id.container, newFragment)
    .addToBackStack(null)
    .commit()

// Add (stack fragments)
fragmentManager.beginTransaction()
    .add(R.id.container, newFragment)
    .addToBackStack(null)
    .commit()

// Hide/Show (don't destroy)
fragmentManager.beginTransaction()
    .hide(currentFragment)
    .show(newFragment)
    .commit()

Common Pitfalls

Pitfall 1: Accessing Views Before onViewCreated()

// โŒ WRONG - binding is null
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding.button.text = "Hi"  // NullPointerException
}

// โœ… CORRECT
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    binding.button.text = "Hi"  // Safe
}

Pitfall 2: Creating Fragments with Constructor Arguments

// โŒ WRONG - args lost on process death
class UserFragment(userId: Int) : Fragment() {
    // userId lost if process killed
}

// โœ… CORRECT
class UserFragment : Fragment() {
    companion object {
        fun newInstance(userId: Int) = UserFragment().apply {
            arguments = Bundle().apply { putInt("user_id", userId) }
        }
    }
}

Pitfall 3: Retaining Fragment Lifecycle

// โŒ DEPRECATED - don't use this anymore
setRetainInstance(true)

// โœ… USE ViewModel INSTEAD
class MyVM : ViewModel() {
    // Survives configuration changes automatically
}

Key Takeaways

โœ… Fragments are reusable UI components within Activities

โœ… Fragment lifecycle includes onCreateView() for UI creation

โœ… Always use onViewCreated() to access views

โœ… Use ViewModel for fragment communication (best practice)

โœ… Always add onDestroyView() { binding = null } to prevent leaks

โœ… Use viewLifecycleOwner for lifecycle-aware observers

โœ… Always use factory companion object for safe arguments

โœ… Fragment back stack managed by FragmentManager