Jetpack Compose changes how Android developers build screens by letting them describe what the interface should look like for a given state instead of updating views step by step after every event. In the older view system you created layout files and then wrote code to find each view and change its text or visibility whenever data changed during use. Compose removes much of that manual work because the framework re-runs the relevant interface functions when state changes and it updates only the parts that actually need a new drawing. That model feels different at first but it becomes intuitive once you think of a screen as a function of its data rather than a collection of objects to manipulate. This guide explains composables and state and layouts in practical terms so you can build a first Compose screen with confidence.
A composable is a Kotlin function marked for interface use and it can call other composables to build a tree of interface elements that mirrors the visual hierarchy on screen. Simple elements such as text and buttons and images combine into rows and columns and boxes that arrange children according to clear rules that are easy to predict. Modifiers attach layout and appearance and behaviour to an element without changing its core type and that separation keeps interface code flexible and reusable across screens. State lives outside the drawing code in observable holders that notify Compose when a value changes so the screen can refresh in a targeted way. Learning these four ideas gives you a solid base for almost every Compose screen you will build in a real project.
Thinking Declaratively About Screens
Declarative thinking starts with a question about data because you first decide what information a screen shows and what actions a user can take from that screen in normal use. You then write interface code that maps each data value to a visible element and each user action to a state change or an event for a view model to handle. When data changes the framework re-runs the affected composables and produces a new description of the screen that it compares with the previous description to update efficiently. You do not write code to hide one view and show another view manually because the description itself changes with the data and the framework handles the transition. This approach reduces bugs where a view stays visible after data changed because a manual update step was forgotten in one code path.
State hoisting is a key pattern in declarative work because it lifts state up to a parent that owns the data and passes values down to children that only display them and report events upward. A stateless child becomes easier to test and reuse because it depends only on its inputs and it does not hide behaviour inside internal variables that are hard to observe. A parent can share the same state between several children so a selection in one part of the screen updates another part without direct communication between siblings. View models often hold this state for a screen because they survive configuration changes such as rotation and they keep business logic separate from drawing code. Following this pattern from the start prevents tangled state that becomes painful to debug in a larger app.
- Describe the screen for each state instead of manually updating views after every event.
- Keep state in one owner and pass values down to children that display them clearly.
- Use modifiers for layout and padding and clicks so interface code stays flexible.
- Split large screens into small composables that each handle one clear visual job.
- Preview small composables often so layout problems appear early in development.
Layouts Rows Columns and Boxes
Rows arrange children horizontally and columns arrange children vertically and boxes stack children on top of each other for overlays and centred content in a simple and predictable way. Each layout accepts alignment and arrangement options that control spacing and positioning without requiring nested containers for common designs that used to need complex hierarchies. Modifiers such as padding and size and weight let a child claim space inside its parent and distribute remaining space fairly among siblings on the same row or column. Lazy layouts handle long lists efficiently because they only compose visible items and recycle work as the user scrolls through hundreds of entries. Understanding these layouts covers most screen designs and it keeps your interface code shallow and readable for future maintenance.
Material components built on Compose provide buttons and cards and text fields and navigation bars that follow modern design guidance and adapt to light and dark themes automatically. Using these components saves time because you do not need to style every control from scratch and users benefit from familiar behaviour that matches other apps on their device. Themes define colours and typography and shapes in one place so a design change updates every screen consistently without editing each composable individually. You can still customise components through modifiers and theme values when a brand needs a distinct look that goes beyond the default appearance. A consistent theme plus standard components gives a professional result even for a small team with limited design resources.
State and Recomposition in Practice
Recomposition is the process where Compose re-runs a composable after its inputs change and it only affects the scope that reads the changed state rather than the whole screen in most cases. That targeted behaviour keeps performance good because a text field update does not force a large list to rebuild when the list does not depend on that text value. You should keep composable functions fast and free of side effects because they may run many times and any heavy work inside them will repeat during recomposition. Side effects belong in dedicated effect handlers that run at the right lifecycle moment and clean up when the composable leaves the composition. Learning where to place work is essential because misplaced work causes repeated network calls or duplicated listeners that are hard to trace.
Remember and mutable state holders let a composable keep a value across recompositions while still notifying the framework when that value changes during interaction. For screen level data a view model exposes state as a flow or as an observable state object that the interface collects in a lifecycle aware way. Collecting state correctly prevents leaks and prevents updates after a screen has gone away during navigation or configuration changes. You can study background patterns further in coroutines and Flow for background tasks because flows power much of modern Compose state handling. A clear state model also makes testing easier because you can verify logic without launching a full interface in an emulator.
Navigation and Screen Structure
Navigation in Compose uses a controller that manages a back stack of destinations and each destination is a composable that represents one screen or one major section of the app. Arguments pass data between destinations in a type safe way when you define them clearly and you should pass identifiers rather than large objects whenever possible. A navigation graph describes the flow from a home screen to detail screens and settings screens so the overall structure stays visible in one place for the team. Bottom navigation and drawers and tabs can share the same controller and they update selection state as the user moves between destinations. Keeping navigation separate from screen content helps you test screens in isolation and reuse a detail screen from several entry points.
Screen structure benefits from a clear separation between a route composable that connects state and a content composable that only draws the interface from given inputs. The route handles view model collection and event forwarding while the content remains pure and preview friendly for designers and developers alike. Previews let you see a composable in the editor without running the app and they speed up layout iteration for edge cases such as long text or empty data. You should create previews for important states including loading and error and empty results because those states often reveal layout problems that happy path testing misses. A well structured screen is easier to maintain and easier to hand to another developer without long explanations.
Testing and Moving From Views
Testing Compose screens uses dedicated test tools that find nodes by text or content description and perform clicks and text input in a controlled environment without a physical device. Unit tests cover view model logic and state transformations while interface tests verify that a given state produces the expected visible elements and actions. Accessibility testing matters because semantic descriptions help screen readers and automated tools understand your interface and they also make tests more robust against layout changes. Screenshot tests can catch visual regressions but they need careful handling across devices and themes so they do not create false failures for minor rendering differences. A balanced test plan gives confidence without slowing development to a crawl on every small change.
Migration from the older view system can happen gradually because Compose can live inside existing activities and view based screens can host Compose content in a dedicated view container. Start with a new screen or a self contained component such as a settings row or a card so the team learns Compose patterns without rewriting a whole app at once. Shared themes and resources can bridge both systems during the transition period and design tokens keep colours consistent across old and new screens. You can plan layout work with building responsive layouts for phones tablets and foldables because responsive thinking applies to Compose layouts too. You can also review dark theme support with Material Design because Compose themes make dark mode straightforward. A gradual migration protects users while the team builds real Compose skill.
A final habit strengthens Compose skill because you should rebuild one familiar screen in Compose and compare its behaviour with the original view based version under rotation and theme changes. That comparison reveals state handling differences early and it shows where previews save time during layout iteration for edge cases. Keep composables small and focused so a future change touches one clear component instead of a large screen function that mixes many responsibilities. With steady practice declarative thinking becomes natural and your screens become easier to test and easier to maintain over a long project life.
@Composable
fun Greeting(name: String) {
Column(modifier = Modifier.padding(16.dp)) {
Text(text = "Hello, " + name)
Button(onClick = {}) { Text("Continue") }
}
}
The steps above follow the approach described in the official developer documentation for this platform and reflect common practice used by experienced developers when they build and test Android apps in a careful and organised way.


