if this is the first article you landed on, there exists an intro to explain the big picture please read it first and continue this article.
To recall our app it will be Notes app will contain the following:
1- List of notes in the app
2- Note details screen
3- Add a note screen.
Now is the dirty work we need to open Android Studio -> File -> New -> New Project.
If you have the latest Android studio ( I’m using Electric El) then you can start with Empty Compose Activity.
This will create the following:
- MainActivity.kt
- ui/theme/Color.kt
- ui/theme/Theme.kt
- ui/theme/Type.kt
We will start with the First screen and focus on it right now, which will contain The list of notes.
With Jetpack Compose it’s pretty easy with List so we will add a title and a list as follows in MainActivity
1- List of notes (static data in the UI for now)
this will be our start screen so in our MainActivity we will have the following
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
NotesTheme{
NotesScreen()
}
}
}
}
and then we will create a new Kotlin file and Name it NotesScreen
so for our basic screen, we will create the fun for the screen as the following:
@Composable
fun NotesScreen() {}
we will build our screen now so first of all we will add a Scaffold as this layout provides slots for the most common top-level Material components, such as TopAppBar
, BottomAppBar
, FloatingActionButton
, and Drawer
@Composable
fun NotesScreen() {
Scaffold(
topBar = { AppBar() },
content = { ScreenContent(it) } // we must pass the paddingValues in use it in the child
)
}
so our app bar will be very simple that has the title as our app name and we change the background to white
@Composable
fun AppBar(){
SmallTopAppBar(
title = { Text(text = stringResource(id = R.string.app_name)) },
colors = TopAppBarDefaults.smallTopAppBarColors(
containerColor = White
)
)
}
and then we will need to build `ScreenContent()` but first we need to know that it will contain a list of notes so we need to build our note item first it will have a simple title and date as follows:
@Composable
fun NoteItem(note: Note) {
Column(
modifier = Modifier.padding(8.dp)
) {
Text(
modifier = Modifier
.fillMaxWidth()
.padding(4.dp),
text = note.title,
style = MaterialTheme.typography.titleMedium,
)
Text(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 4.dp),
text = note.date,
style = MaterialTheme.typography.titleSmall
)
}
}
we created a model class to help us a little with the item :
data class Note(val title:String, val date:String)
Then we will create our ScreenContent()
and build a list with notes items in it
@Composable
fun ScreenContent(paddingValues: PaddingValues) { // create static notes in the ui that will be replaced later
val notes: List<Note> = listOf(
Note("Title1", "12:23"),
Note("Title2", "16:12"),
Note("Title3", "21:32")
) //Build a card that will contain the list of the notes
Card(
modifier = Modifier
.padding(paddingValues)
.padding(16.dp)
) { // create our list and gave it a background
LazyColumn (
Modifier.background(lightGray200)
){
items(
items = notes, // passing the notes list
itemContent = {
NoteItem(it) //building the note items basd on the list
})
}
}
}
if you run the app right now you will see the following screen:
then we need some improvements to our code and include a viewModel
that will hold the UI state in this part I’ll use Hilt for DI but if you don’t know it you can learn from here.
to use hilt with Compose we need to add one special lib for it as followed:
implementation 'androidx.hilt:hilt-navigation-compose:1.0.0'
and then we will create our ViewModel as follows:
// anotating it as hilt knows it's a view model and it's required
// to have an @Ijecct constructor
@HiltViewModel
class NotesViewModel @Inject constructor() : ViewModel() {}
we will create our Ui state inside our view model and will hold all data for this out notes list as follows:
we need to create our Ui state class that will hold the data inside and make it very simple now:
// we have an empty list so this will be our defult value
data class NotesUiState(val notes: List<Note> = emptyList())
and inside our view model, we will create the State flow of our UI state
// we need to pass our init state to the state flow
val uiState = MutableStateFlow(NotesUiState())
then we will assume later on we need to update our UI state and will do it as follows:
init {
uiState.update {
it.copy(
notes = listOf(
Note("Title1", "12:23"),
Note("Title2", "16:12"),
Note("Title3", "21:32")
)
)
}
}
and then our complete view model will look like this:
@HiltViewModel
class NotesViewModel @Inject constructor() : ViewModel() {
val uiState = MutableStateFlow(NotesUiState())
init {
uiState.update {
it.copy(
notes = listOf(
Note("Title1", "12:23"),
Note("Title2", "16:12"),
Note("Title3", "21:32")
)
)
}
}// you can here remove the data from the init block and get it from your server or whenever you want to get your data
}
after changing the data to come from the view model we need to have some modifications to the view so we can make our data observe the change of the uiState.
we will use the recommended way of observing the data in Compose.
and we need to add our lifecycle lib
implementation "androidx.lifecycle:lifecycle-runtime-compose:2.6.0-alpha01"
then back to our NoteScreen()
we will first need to inject our view model and create our observer on the screen and bind the data to its view
// inject our view model ot the constructor@Composable
fun NotesScreen(viewModel: NotesViewModel = hiltViewModel()) { //get our ui state with awareness of the life cycle
val state by viewModel.uiState.collectAsStateWithLifecycle()
Scaffold(
topBar = { AppBar() }, //we will pass our list of notes to screenContent
content = { ScreenContent(it, state.notes) }
)
}
// we will remove the created list in screen content and use our list that comes from the view model
@Composable
fun ScreenContent(paddingValues: PaddingValues, notes: List<Note>) {
Card(
modifier = Modifier
.padding(paddingValues)
.padding(16.dp)
) {
LazyColumn (
Modifier.background(lightGray200)
){
items(
items = notes,
itemContent = {
NoteItem(it)
})
}
}
}
now we achieved and built the whole UI part with its own ViewModel and you can run it now you will see the same UI but depending on the data in the view model
you can see the changes in this article here in this GitHub project commit.
if you have any questions let me know in the comments.
don’t forget to clap, and follow me for more articles, and you can find me on GitHub.