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.

Jetpack Compose: Navigating to a Detail View (Part III)

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.

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

Jetpack Compose: An easy way to RecyclerView (Part I)
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.
Jetpack Compose: Styles and Themes (Part II)
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.

The Detail View ๐Ÿ–ผ

In order to create a detail view for a puppy list, here are some things that we need to do:

  1. Create a click handler that is triggered upon tapping a puppy item in the list.
  2. 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 the startActivity() 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:

  1. Open PuppyListItem.kt.
  2. 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.

  1. Open BarkHome.kt.
  2. Pass a new lambda as an argument to our BarkHomeContent function: navigateToProfile: (Puppy) -> Unit.
  3. 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:

  1. Open MainActivity.kt.
  2. Pass a new lambda as an argument to our MyApp function: navigateToProfile: (Puppy) -> Unit.
  3. Pass it as a parameter to the BarkHomeContent function that we're calling inside MyApp: 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 ๐Ÿพ

  1. 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:

  1. Open ProfileActivity.kt.
  2. Inside onCreate() method, after super.onCreate, call setupContent (i.e. a parent composition that starts the flow of all Composable function).
  3. Inside setupContent, we start a lambda expression where we first setup the theme of the screen, in our case BarkTheme.
  4. Inside BarkTheme, we call our built-in Text 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 ๐ŸŽฉ

  1. Open MainActivity.kt.
  2. Inside onCreate method, under MyApp, 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:

  1. Open ProfileActivity.kt.
  2. Create a new companion object.
  3. Inside the companion object, create a new PUPPY_ID constant that works as a key when sending and retrieving objects via Intent.
  4. While still inside the companion object, create a public function newIntent that creates the Intent for opening our profile screen, taking in context and a puppy 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 the Serializable class is not detectable by Android Studio, simply add import 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

For the next post, we'll look into how to:

  1. Implement a complete UI for the Profile screen.

You can find it available in the upcoming weeks. Until then, give yourself a pat on the back, and..

Happy coding! ๐Ÿ’ป

Source code for the Final Version

waseefakhtar/bark
An Android App for the #AndroidDevChallenge. Contribute to waseefakhtar/bark development by creating an account on GitHub.

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 โœŒ๏ธ