Jetpack Compose: Navigating to a Detail View (Part III)
If you're new to Jetpack Compose and looking at all the cool UI screens and animations around the internet like me, you're probably a bit overwhelmed but also curious about how things work in compose.
If you've been following my series of posts regarding RecyclerView (LazyColumn) in Jetpack Compose, chances are, you might have already accomplished most of what we wanted to achieve with the Puppy Adoption app.
For a quick recap, here's where we left our app after styling it in Part II:
What's left to achieve, you ask? Adding a detail view that our list view navigates to when clicked on a puppy item.
In case you’re not fond of reading blog posts, I’ve also turned this 4-part Jetpack Compose series into a 13-minute speed code video with some laid back music for you to watch and relax to! 😊
If you aren't sure exactly what I am referring to, please go through my two previous posts in order to understand better.
Previous Posts
The Detail View 🖼
In order to create a detail view for a puppy list, here are some things that we need to do:
- Create a click handler that is triggered upon tapping a puppy item in the list.
- Implement a new Profile screen for puppies that is shown after clicking on the puppy item.
Note: Since showing a new screen (an activity) is still done by our dear ol'startActivity()
method (which can only be called from an activity or when we have a context), we need to refactor our code a bit to pass in our click handler up the flow to our MainActivity in order to call thestartActivity()
method and open the new Profile screen.
Refactoring 🎛
Let's get started by refactoring our code a bit to add a click handler to our Row composable function and pass it up the flow to our MainActivity
:
- Open
PuppyListItem.kt
. - Pass a new lambda as an argument to our
PuppyListItem
composable function:navigateToProfile: (Puppy) -> Unit
.
Note: We pass in the Puppy object to our navigateToProfile
lambda in order to know which Puppy item was clicked, in order to show the correct profile information for the clicked puppy item.
3. Add a Modifier.clickable
parameter to our Row
composable function and call the lambda that we created, passing in the Puppy object as an argument: Modifier.clickable { navigateToProfile(puppy) }
.
That makes your PuppyListItem
function now look like this:
If you run the app at this time, you will face a build error that says:
e: BarkHome.kt: (19, 41): No value passed for parameter 'navigateToProfile'
Meaning we've added a new argument to our PuppyListItem
function but haven't yet passed any parameter to it where we call it from, i.e. BarkHome.kt
.
So similar to our refactoring above, we update other functions that's related to PuppyListItem
, as well.
- Open
BarkHome.kt
. - Pass a new lambda as an argument to our
BarkHomeContent
function:navigateToProfile: (Puppy) -> Unit
. - Inside the same function, we now update our call to
PuppyListItem
by passing in the lambda parameter:PuppyListItem(puppy = it, navigateToProfile)
.
The new changes make our function now look like this:
The error is now fixed. However, if you run the app again, you get another error saying:
e: MainActivity.kt: (29, 29): No value passed for parameter 'navigateToProfile'
Meaning we added a new argument to the function BarkHomeContent
but we haven't passed anything to it when called from MainActivity
.
Similarly, we need to now update our functions in MainActivity
, as well:
- Open
MainActivity.kt
. - Pass a new lambda as an argument to our
MyApp
function:navigateToProfile: (Puppy) -> Unit
. - Pass it as a parameter to the
BarkHomeContent
function that we're calling insideMyApp
:BarkHomeContent(navigateToProfile = navigateToProfile)
This makes our updated function look like below:
To get our heads around what we've achieved so far, we started building our click handler from bottom up, passing in our Puppy object so that we notify our MainActivity that a click is triggered by a user. The MainActivity then helps us call the next activity, which is the Profile screen for that particular puppy.
In order to achieve that, we made use of Kotlin Lambdas that work as anonymous functions that we can pass as arguments to our functions in order for us to perform a lambda expression in another function once the lambda is called.
In our case, we pass in our lambda in the following way:
PuppyListItem > BarkHomeContent > MyApp > onCreate
So far, we've achieved passing the lambda up until MyApp
, we now need to update our onCreate
method inside MainActivity
where we call MyApp
and call our dear ol' startActivity()
method to start a new activity.
But before we do so, let's create our ProfileActivity
screen first.
Creating a Profile Screen 🐾
- Right click on our /bark directory and go to New > Activity > Empty Activity.
2. Give your activity the name, ProfileActivity
and uncheck Generate a Layout file
.
3. Click Finish.
Your new profile screen is now created without an XML layout file (since we're all about Jetpack Compose 😉).
Let's try to quickly setup our Profile screen content so we don't run into errors when we build our project so we know we've refactored and set up everything correctly:
- Open
ProfileActivity.kt
. - Inside
onCreate()
method, aftersuper.onCreate
, callsetupContent
(i.e. a parent composition that starts the flow of all Composable function). - Inside
setupContent
, we start a lambda expression where we first setup the theme of the screen, in our caseBarkTheme
. - Inside
BarkTheme
, we call our built-inText
composable to test our screen.
With the changes, this is how your onCreate method looks:
With everything setup, let's get back to calling our ProfileActivity from MainActivity.
Showing the Profile Screen 🎩
- Open
MainActivity.kt
. - Inside
onCreate
method, underMyApp
, define a lambda expression by calling the new activity, like so:
Finally, run the app now to see the new changes.
You might wonder what made us pass in the Puppy object when we're not even using it inside our lambda expression.
This is where we need to test passing our Puppy object to the Profile screen to see that all our refactoring of code works perfectly.
Passing a puppy to the Detail View 🐶
In order to pass an object to a detail screen, these steps are the best practices to achieve it:
- Open
ProfileActivity.kt
. - Create a new companion object.
- Inside the companion object, create a new
PUPPY_ID
constant that works as a key when sending and retrieving objects via Intent. - While still inside the companion object, create a public function
newIntent
that creates the Intent for opening our profile screen, taking incontext
and apuppy
as arguments.
You might notice that when we call putExtra
, it is underlined red and has an error message:
None of the following functions can be called with the arguments supplied.
This is because our Puppy model class extend from neither of the mentioned types that Intent supports when sending/retrieving an extra. In order to solve this, we need to extend our class from the Serializable
to make it supportable by Intent to carry.
In order to do so:
5. Open Puppy.kt
.
6. Extend it from the Serializable
class, like so:
Note: If theSerializable
class is not detectable by Android Studio, simply addimport java.io.Serializable
at the top of the class.
If you notice in ProfileActivity.kt
now, the red underline under putExtra
should be gone.
7. Now, create a new field in ProfileActivity.kt
called puppy
, that actually retrieves the puppy object (as a Serializable) from the intent when the the ProfileActivity
runs.
8. Let's now modify our Text
composable inside onCreate
to say hello to the puppy that is clicked on.
9. Finally, we modify our startActivity
method in MainActivity
to call the newIntent
method that we just created in ProfileActivity
.
Let's finally run the app now to see the new changes.
And.. there you go! As you can now see, each puppy item that you click, you're directed to a profile screen where it greets that particular puppy.
Give yourself a pat on the back at this point for having now learnt how navigation works in Compose. 👏
We've come really far from where we started, but.. we're not done here, because we've yet to implement a complete UI for the Profile screen, to make our app look like the one below. 👇
Up Next
Source code for the Final Version
Awesome that you came this far! 👏 Now I'd love to know what the most annoying part of this post was or if it was of any help to you. Either ways, you can drop me a DM on: www.twitter.com/waseefakhtar ✌️
Happy coding! 💻