Lesson 12 of 83 intermediate

Android App Components: Activity, Fragment, Service, BroadcastReceiver & ContentProvider

The 4 Android app components — fundamentals that every Android interview covers

Open interactive version (quiz + challenge)

Real-world analogy

Activity is your store's showroom — customers interact directly with it. Fragment is a modular display stand inside the showroom — you can swap them out. Service is the warehouse operations running 24/7 behind the scenes. BroadcastReceiver is the store's PA system — it listens for announcements. ContentProvider is the secure file room where you share documents with authorized visitors.

What is it?

Android's 4 application components (Activity, Service, BroadcastReceiver, ContentProvider) are the entry points through which the system and other apps can interact with your application. Each has a distinct purpose and lifecycle. Fragment is not a component but an architectural building block. Modern Android development minimizes direct component use — Fragments for screens, WorkManager for background work, FileProvider for sharing.

Real-world relevance

In a school management platform: Activities handle the main entry point and deep links from notifications. Fragments implement each screen (student list, grade book, attendance). A Foreground Service with a notification plays audio during online classes. BroadcastReceivers listen for network changes to trigger sync. FileProvider shares grade report PDFs with other apps.

Key points

Code example

// Activity — Single Activity architecture entry point
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // NavHostFragment handles all screen navigation
    }

    // Handle deep links / results from other apps
    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        handleDeepLink(intent)
    }
}

// Fragment — correctly using viewLifecycleOwner
class StudentListFragment : Fragment() {
    private val viewModel: StudentViewModel by viewModels()
    private var _binding: FragmentStudentListBinding? = null
    private val binding get() = _binding!!

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        _binding = FragmentStudentListBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        // Use viewLifecycleOwner — NOT 'this' — for view-related observations
        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.students.collect { renderStudents(it) }
            }
        }
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null  // prevent memory leak
    }
}

// Foreground Service — audio playback for online class
class ClassAudioService : Service() {
    private var mediaPlayer: MediaPlayer? = null

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val notification = buildNotification("Live Class in Progress")
        startForeground(NOTIFICATION_ID, notification)  // required within 5s

        when (intent?.action) {
            ACTION_PLAY -> startAudio(intent.getStringExtra("stream_url"))
            ACTION_STOP -> { stopAudio(); stopSelf() }
        }
        return START_STICKY  // restart if killed by system
    }

    override fun onBind(intent: Intent?): IBinder? = null  // not a bound service
}

// BroadcastReceiver — runtime registered for network changes
class NetworkChangeReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        // onReceive runs on MAIN thread — keep it fast
        val isConnected = isNetworkAvailable(context)
        if (isConnected) {
            WorkManager.getInstance(context).enqueue(
                OneTimeWorkRequestBuilder<SyncWorker>().build()
            )
        }
    }
}

// FileProvider — sharing grade report PDF
fun shareGradeReport(file: File) {
    val uri = FileProvider.getUriForFile(
        requireContext(),
        "${requireContext().packageName}.fileprovider",
        file
    )
    val intent = Intent(Intent.ACTION_SEND).apply {
        type = "application/pdf"
        putExtra(Intent.EXTRA_STREAM, uri)
        addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
    }
    startActivity(Intent.createChooser(intent, "Share Report"))
}

Line-by-line walkthrough

  1. 1. @AndroidEntryPoint MainActivity — Hilt entry point; handles deep links and system integration; NavHostFragment does all navigation
  2. 2. private val viewModel: StudentViewModel by viewModels() — Hilt/AAC creates the ViewModel; same instance across config changes
  3. 3. viewLifecycleOwner.lifecycleScope — tied to Fragment's VIEW lifecycle, not Fragment lifecycle; avoids null binding crashes
  4. 4. _binding = null in onDestroyView() — view is destroyed when Fragment is backstacked; clearing prevents memory leak from view reference
  5. 5. startForeground(NOTIFICATION_ID, notification) — must be called within 5 seconds of Service start on Android 8+; shows persistent notification
  6. 6. return START_STICKY — system restarts Service with null Intent if killed; appropriate for ongoing audio/location services
  7. 7. override fun onBind(): IBinder? = null — returns null since this is a started Service, not a bound Service
  8. 8. WorkManager.enqueue in BroadcastReceiver — don't do long async work directly; enqueue WorkManager task and return immediately
  9. 9. FileProvider.getUriForFile(...) — converts File to content:// URI; FLAG_GRANT_READ_URI_PERMISSION gives receiving app temporary access

Spot the bug

class SyncReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        // Sync takes 30 seconds
        GlobalScope.launch {
            repository.syncAllData()  // bug 1
        }
        // onReceive returns — receiver is dead
    }
}

class StudentListFragment : Fragment() {
    override fun onViewCreated(view: View, saved: Bundle?) {
        // Using 'this' instead of viewLifecycleOwner
        viewModel.students.observe(this) { students ->   // bug 2
            binding.recyclerView.adapter = StudentAdapter(students)
        }
    }
}
Need a hint?
BroadcastReceiver dies after onReceive — GlobalScope launch may be killed. Fragment lifecycle != View lifecycle.
Show answer
Bug 1: BroadcastReceiver is dead after onReceive() returns — the process may be killed before GlobalScope.launch completes. Fix: use goAsync() to extend the receiver's life: val pendingResult = goAsync(); GlobalScope.launch { try { repository.syncAllData() } finally { pendingResult.finish() } }. Even better: enqueue a WorkManager OneTimeWorkRequest — it's guaranteed to complete even if the process is killed. Bug 2: observe(this) uses Fragment lifecycle — when the Fragment goes to the backstack, the view is destroyed but Fragment is alive and observers still run. Accessing binding in the observer causes NullPointerException. Fix: viewModel.students.observe(viewLifecycleOwner) { ... }

Explain like I'm 5

Imagine your school as an Android app. Activity is the front door and main hallway — where guests enter. Fragments are the classrooms — you swap them out for different subjects. Service is the janitor working in the basement — you don't see them but they keep things running. BroadcastReceiver is the school secretary who listens to the PA system for important announcements. ContentProvider is the secure document office that shares files with other schools through an official process.

Fun fact

The ContentProvider was designed for inter-app data sharing, but Android's own system uses it heavily: the Contacts app, Camera (MediaStore), and Downloads all expose their data via ContentProviders. FileProvider, introduced to prevent the security issue of sharing raw file:// URIs, is the safe way to share files — raw file:// URIs are blocked on Android 7+ and throw FileUriExposedException.

Hands-on challenge

Design the component architecture for the school management app's 'Live Class' feature. Specify: 1) Which Activity/Fragment handles the live class screen and why. 2) The Foreground Service for audio streaming with proper notification and START_STICKY. 3) A BroadcastReceiver for network changes — should it be manifest or runtime registered? What goAsync() usage is needed? 4) How WorkManager handles class recording upload after the session ends. 5) FileProvider setup for sharing class notes PDF. What entries go in AndroidManifest.xml for each component?

More resources

Open interactive version (quiz + challenge) ← Back to course: Android Interview Mastery