Activities, and why there is usually only one now
Mandi Bhav has a MainActivity, and nowhere in the project is there a line that says MainActivity(). You never create it. Android does, when the user taps the icon, and Android destroys it again when it decides to. That one fact explains most of what is strange about Android code, and this lesson starts from it.
What an activity is
An activity is the component Android gives a window to. It is the unit the system starts, puts on screen, sends to the background and tears down. The launcher starts one; the Recents screen shows one per task; Back leaves one. Everything the user sees in your app is drawn inside an activity's window.
What an activity is not: a screen in your app's design. Mandi Bhav will have a market list, a price detail, a price-alert form and settings. In a modern Compose app those are composable functions. All of them live inside the one activity you already have.
Reading MainActivity
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
MaterialTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(name = "Mandi Bhav", modifier = Modifier.padding(innerPadding))
}
}
}
}
}
: ComponentActivity(). Android's original android.app.Activity class is part of the operating system, so it is frozen at whatever version the phone runs. ComponentActivity comes from the androidx.activity library, which ships inside your APK and so behaves the same on Android 8 and Android 16. It adds the things modern code depends on: a lifecycle other objects can observe, saved state, a proper Back handler, and support for ViewModels. You will also meet AppCompatActivity in older projects. It extends ComponentActivity and adds compatibility for XML-era themes and the old action bar. A Compose app does not need it.
override fun onCreate(savedInstanceState: Bundle?). Android calls this after it has created the activity object and attached it to the system. It is your first chance to do anything, and the right place for setup. The Bundle? parameter is state saved from a previous instance, which is null the first time; the lifecycle lesson shows when it is not.
super.onCreate(savedInstanceState). Always first, always called. ComponentActivity does its own setup here, including restoring saved state. Forget it and Android throws SuperNotCalledException as soon as the activity starts.
enableEdgeToEdge(). Lets the app draw behind the status and navigation bars. Module 4 covers what that obliges you to do.
setContent { ... }. An extension function from activity-compose that says "this activity's window shows this Compose UI". Everything inside the braces is a composable. This is the bridge between the Android component world and the Compose world, and it is the only place they meet.
The trap: doing work before onCreate
Because Android constructs the activity, the object exists for a moment before it is connected to anything. A property initialiser runs in that moment. Try reading a string resource there:
class MainActivity : ComponentActivity() {
private val appName = getString(R.string.app_name) // runs too early
override fun onCreate(savedInstanceState: Bundle?) {
...
It compiles. On launch it crashes before the first frame:
E AndroidRuntime: FATAL EXCEPTION: main
E AndroidRuntime: java.lang.RuntimeException: Unable to instantiate activity
ComponentInfo{com.riztechacademy.mandibhav/com.riztechacademy.mandibhav.MainActivity}:
java.lang.NullPointerException: Attempt to invoke virtual method
'android.content.res.Resources android.content.Context.getResources()'
on a null object reference
"Unable to instantiate activity" tells you it died while Android was still constructing it. The activity's Context, the thing that knows where resources are, is not attached until after construction. Nothing that needs the system belongs in a constructor, an init block or a property initialiser of an activity. Put it in onCreate, or better, in a composable or a ViewModel, which is where module 5 puts it.
Single-activity architecture
Older Android apps had an activity per screen: MarketListActivity, PriceDetailActivity, SettingsActivity, each declared in the manifest, each started with an intent. Current guidance, and the way this course builds Mandi Bhav, is one activity hosting composable screens, with a navigation library swapping which screen is shown. Module 6 builds that.
Why the change:
- An activity is heavy. Starting one goes through the system process, which creates a window and runs the full lifecycle. Showing a different composable is a function call.
- Sharing data between activities is awkward. Two activities share nothing except what you pack into an intent or store somewhere. Two screens in one activity can share a ViewModel.
- One place for app-wide setup. Theme, edge-to-edge, and anything else you configure once, live in one
onCreate. - Transitions and the back stack become yours. You control animations and what Back does, in Kotlin, instead of in manifest flags.
What it costs: the navigation library becomes something you have to learn, and a single activity that does too much becomes a god class. The fix for the second is to keep the activity nearly empty, like the starter's, and put screens in their own files.
Intents between activities: the pattern you will meet
You will still join projects with many activities, and you will still use intents to open other apps, so you need to read this pattern fluently. Here is a second activity for Lasalgaon prices, done the old way:
class PriceDetailActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val market = intent.getStringExtra(EXTRA_MARKET) ?: "Unknown market"
enableEdgeToEdge()
setContent {
MaterialTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Text(text = "Prices at $market", modifier = Modifier.padding(innerPadding))
}
}
}
}
companion object {
private const val EXTRA_MARKET = "market"
fun intent(context: Context, market: String): Intent =
Intent(context, PriceDetailActivity::class.java)
.putExtra(EXTRA_MARKET, market)
}
}
And a button in the first screen that opens it:
@Composable
fun OpenLasalgaonButton() {
val context = LocalContext.current
Button(onClick = {
context.startActivity(PriceDetailActivity.intent(context, "Lasalgaon"))
}) {
Text("Lasalgaon prices")
}
}
Intent(context, PriceDetailActivity::class.java)is an explicit intent: it names the exact class to start.putExtraandgetStringExtracarry data across, as key-value pairs. Inside the new activity,intentis the intent that started it.- The
companion objectfactory keeps the key in one file. Without it, the string"market"is typed in two places, and the day someone types"Market"in one of them, the detail screen silently shows "Unknown market".
It also needs a manifest entry, not exported, because nothing outside the app should open it:
<activity
android:name=".PriceDetailActivity"
android:exported="false" />
Forget that entry and the build still passes. Tap the button and the app crashes:
E AndroidRuntime: android.content.ActivityNotFoundException: Unable to find explicit
activity class {com.riztechacademy.mandibhav/com.riztechacademy.mandibhav.PriceDetailActivity};
have you declared this activity in your AndroidManifest.xml, or does your intent
not match its declared <intent-filter>?
That message is unusually kind: it tells you the fix.
When one activity starts another, both run lifecycle callbacks in a fixed order: the first activity's onPause, then the new one's onCreate, onStart and onResume, then the first one's onStop once it is hidden. The next lesson is about those callbacks.
What not to put in an extra
Extras pass through the system process, which has a fixed buffer for these transactions, about 1 MB shared across everything the process has in flight. Pass a market name or an id. Do not pass a list of five hundred prices; it works with test data and crashes with TransactionTooLargeException on a busy day in the real data. Pass the id and let the new screen load what it needs. The same rule applies to navigation arguments in module 6.
Implicit intents: still how you talk to other apps
Single-activity architecture is about your own screens. To open another app you still use an intent, but an implicit one that names an action instead of a class. Sharing today's onion price on WhatsApp:
@Composable
fun SharePriceButton(priceText: String) {
val context = LocalContext.current
Button(onClick = {
val send = Intent(Intent.ACTION_SEND)
.setType("text/plain")
.putExtra(Intent.EXTRA_TEXT, priceText)
context.startActivity(Intent.createChooser(send, "Share price"))
}) {
Text("Share")
}
}
Android finds every app whose manifest has an intent filter for ACTION_SEND with text, and the chooser lets the user pick one. That is the other side of the intent filters you saw in the manifest lesson.
Check your work
Who creates MainActivity? Android, when it is launched. Your code never calls its constructor.
Why ComponentActivity rather than Activity? It comes from a library shipped in your APK, so it behaves the same on every Android version, and it adds lifecycle, saved state, Back handling and ViewModel support.
What does setContent do? Sets the activity's window to show the given Compose UI.
Why did getString in a property initialiser crash? It ran during construction, before the activity's Context was attached.
Explicit or implicit: Intent(context, PriceDetailActivity::class.java)? Intent(Intent.ACTION_SEND)? The first is explicit (names a class); the second is implicit (names an action).
What happens if an activity is missing from the manifest? The build passes; starting it throws ActivityNotFoundException.
Why pass an id rather than a list in an extra? Extras go through a fixed-size system buffer; large data crashes with TransactionTooLargeException.
In single-activity architecture, what is a screen? A composable, shown inside the one activity and swapped by navigation.
Practice 2: where does Back take you? To the first screen. PriceDetailActivity was stacked on top of MainActivity in the same task, and Back finishes the top one.
Practice 6: what does the user see? "Prices at Unknown market". The extra is there under "market", the lookup asks for "Market", gets null, and the ?: fallback hides the mistake. A crash would have been found in the first test; this ships.
Practice
- Read
MainActivityaloud line by line and say what each does, without looking at this lesson. - Add
PriceDetailActivity, its manifest entry and the button. Run it, tap the button, then press Back. Which screen do you return to? - Break it on purpose: remove the manifest entry, run, tap the button, and find the
ActivityNotFoundExceptionin Logcat. Restore it. - Break it on purpose: move a
getString(R.string.app_name)call into a property initialiser and launch. Find "Unable to instantiate activity" in Logcat, then move the call intoonCreateand confirm it works there. - Add the share button with a text such as "Onion, Lasalgaon: ₹1,850 per quintal" and share it to any app on the emulator.
- Harder: change the extra key in
getStringExtrato"Market"without using the companion factory, and run. Nothing crashes. Describe what the user sees, and why this bug is worse than a crash.
Official documentation
- Android Developers — Introduction to activities — The activity concept, declaring activities and the entry-point model.
- Android Developers — Intents and intent filters — Explicit and implicit intents, how Android matches a filter, and the security reasons for explicit intents inside your app.
- Android Developers — ComponentActivity reference — Everything the androidx base class adds over the platform
Activity. - Android Developers — Navigation 3 — Google's current navigation library for Compose, which swaps screens inside a single activity.
Next: the lifecycle, and the bug that turns a farmer's count of three back into zero when the phone turns sideways.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Selected students who finish a course can join our internship, with real-world projects and code review from a working engineer.
About the internship