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.kt
RecyceViewAdapter.kt
ItemViewHolder.kt
ItemDiffCallback.kt
Remove unwanted layout files
main_activity.xml
main_fragment.xml
item.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 viewModels
is used so that you don't recreate theMainViewModel
instance when theMainActivity
is 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)