Step-by-step tutorial to convert Android RecycleView(view-based UI approach) to LazyColumn (Jetpack Compose approach)
This beginner-friendly tutorial provides an example how to convert this simple RecycleView app to Jetpack Compose.
I also take some extra steps to clean up unused code or xml after migrating to Jetpack Compose.
1. Remove RecycleView, Layout, Fragment and Library Files
Other than RecycleView, you can also remove the fragment and layout files, since Jetpack Compose doesn't need them.
Remove unwanted source codes
MainFragment.ktRecyceViewAdapter.ktItemViewHolder.ktItemDiffCallback.kt
Remove unwanted layout files
main_activity.xmlmain_fragment.xmlitem.xml
Remove unwanted build features and libraries
In app\build.gradle, remove data binding since this is no longer applicableto Jetpack Compose.
buildFeatures {
dataBinding true
}
Remove these dependencies as well.
dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
implementation 'androidx.fragment:fragment-ktx:1.4.0'
}
Fix compilation issue in MainActivity.kt
Remove this code in MainActivity::onCreate() since you no longer need fragment.
setContentView(R.layout.main_activity)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.container, MainFragment.newInstance())
.commitNow()
}
You should be able to build successfully now.
2 Setup Jetpack Compose Libraries
Update build.gradle (project level)
Add compose_version extension inside the buildScript{ } so that the compose version can be referenced later.
buildscript {
ext {
compose_version = '1.0.5'
}
...
}
Update app\build.gradle(app level)
Add compose build features and kotlinCompilerExtensionVersion compose options.
android {
....
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion compose_version
}
....
}
Replace implementation 'androidx.appcompat:appcompat:1.4.0' with implementation 'androidx.activity:activity-compose:1.4.0' and add the following Jetpack Compose dependencies.
dependencies {
...
implementation 'androidx.activity:activity-compose:1.4.0'
...
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
...
}
Update MainActivity for compose
In Jetpack Compose, you don't need AppCompatActivity anymore, you can just directly inherit from ComponentActivity
Modify MainActivity to directly inherit from ComponentActivity, overrides onCreate() and call SetContent{} which allow any @composable functions can be called inside.
class MainActivity : ComponentActivity() {
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// Implement composable function here.
}
}
}
3. Add Theming in Jetpack Compose
Before you add theming in Jetpack Compose, let's clean up the colors.xml and themes.xml.
You only require the themes.xml to provide the color for android:statusBarColor. So you keep it and removing anything else.
Clean up colors.xml and themes.xml
These should be the minimum code required to customize the status bar color.
colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_700">#FF3700B3</color>
</resources>
themes.xml
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="Theme.RecycleViewDemo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
</style>
</resources>
themes.xml (night)
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="Theme.RecycleViewDemo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
</style>
</resources>
Add Compose Theming
Create ui.theme package folder, puts the Colors.kt, Shape.kt, Type.kt into this folder.
Colors.kt
val Purple200 = Color(0xFFBB86FC)
val Purple500 = Color(0xFF6200EE)
val Purple700 = Color(0xFF3700B3)
val Teal200 = Color(0xFF03DAC5)
Shape.kt
val Shapes = Shapes(
small = RoundedCornerShape(4.dp),
medium = RoundedCornerShape(4.dp),
large = RoundedCornerShape(0.dp)
)
Type.kt
val Typography = Typography(
body1 = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp
)
)
Theme.kt
private val DarkColorPalette = darkColors(
primary = Purple200,
primaryVariant = Purple700,
secondary = Teal200
)
private val LightColorPalette = lightColors(
primary = Purple500,
primaryVariant = Purple700,
secondary = Teal200
)
@Composable
fun RecycleViewDemoTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable() () -> Unit
) {
val colors = if (darkTheme) {
DarkColorPalette
} else {
LightColorPalette
}
MaterialTheme(
colors = colors,
typography = Typography,
shapes = Shapes,
content = content
)
}
These files allow you to customize your theme for Jetpack Compose.
To theme your app, call the MainContent() composable function from RecycleViewDemoTheme. The code looks like this:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MainScreen()
}
}
}
@Composable
fun MainScreen() {
RecycleViewDemoTheme {
MainContent()
}
}
@Composable
fun MainContent() {
//Todo: Implement LazyColumn
}
4. Add Top App Bar
Since you have removed AppCompatActivity, the top app bar is not created anymore. You need to create it using Jetpack Compose.
Add Scaffold() composable function
To create top app bar, you use ScaffoldI() composable function. The code looks like this:
@Composable
fun MainScreen() {
RecycleViewDemoTheme {
Scaffold(
topBar = { TopAppBar (title = {Text(stringResource(R.string.app_name))})
}
) {
MainContent()
}
}
}
Preview a composable function
In order to preview a composable function, you add the following code:
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
MainScreen()
}
After you compile, you should see something like this at your right. If you run your app, you should see the same UI as in preview.
Now the app is fully implemented with Jetpack Compose code. At this point, the UI is exactly same as the view-based UI approach without the recycle view content.
5. Implement LazyColumn Composable Function
The equivalent RecycleView in Jetpack compose is LazyColumn composable function.
Strictly speaking, they're not the same. LazyColumn does not really recycle the item UI. It just recreates the entire item UI. So in theory, RecycleView performance should be better than the LazyColumn.
The good thing about LazyColumn it uses less code since RecycleView has a lot of boilerplate code. See how many steps are required to implement RecyceView here:
Create MainViewModel and pass into MainContent
Since the data is coming MainViewModel, you create it with by viewModels delegated property in the MainActivity pass it as parameter to the MainContent() composable function.
MainActivity.kt
class MainActivity : ComponentActivity() {
val viewModel by viewModels<MainViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MainScreen(viewModel)
}
}
}
by viewModelsis used so that you don't recreate theMainViewModelinstance when theMainActivityis destroyed and recreated. See explaination here.
MainScreen.kt
@Composable
fun MainScreen(viewModel: MainViewModel) {
RecycleViewDemoTheme {
Scaffold(
topBar = { TopAppBar (title = {Text(stringResource(R.string.app_name))})
}
) {
MainContent(viewModel)
}
}
}
Convert the LiveData to State
In Jetpack Compose, you need to convert the LiveData<T> to State<T> so it can recompose correctly when the data is changed or updated. To convert it, you use observeAsState() LiveData function.
Before that, you need to add this library dependency:
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
After converting to State<T>, you past the value(i.e. List<ItemData>) as parameters ofListContent() composable function.
@Composable
fun MainContent(viewModel: MainViewModel) {
val itemsState = viewModel.items.observeAsState()
itemsState.value?.let { items ->
ListContent(items)
}
}
Implement LazyColumn
Since the RecycleView item original implementation fill up the entire screen width and center aligned, you need to do the same. This can be done through modifer and horizontalAlignment parameters of LazyColumn
In the last parameter of LazyColumn is Function Literal (Lambda Function) with Receiver. The LazyListScope is the receiver.
To add the items (i.e List<ItemData>), you call the LazyListSciope.items() composable function. To add the items content, you implement the ShowItem() composable function which just show the text.
To match the original RecycleView implementation, we set the font size to 34.sp and FontWeight.Bold.
The code looks like this:
@Composable
fun ListContent(items: List<ItemData>) {
LazyColumn (
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
items(items = items) { item ->
ShowItem(item)
}
}
}
@Composable
fun ShowItem(item: ItemData) {
Text(
text = item.id.toString(),
fontSize = 34.sp,
fontWeight = FontWeight.Bold
)
}
Update Preview to include MainViewModel creation
Since the MainScreen() takes in MainViewModel as parameter, you need to create it and pass it in.
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
val viewModel = MainViewModel()
MainScreen(viewModel)
}
6. Done
It is finally done!. The app looks like this, which is exactly the same with the RecycleView view-based UI approach.
If you want, you can also refactor the code by moving out the
MainContent()composable function to a seperate file which is a bit cleaner.
Reference
- Conversion diff here: master vs compose branch
- GitHub Repository: Demo_SimpleRecycleView (compose branch)
Originally published at https://vtsen.hashnode.dev.


Top comments (0)