A better way of passing data between destinations.

kasemsm

Kasem SM

Posted on October 2, 2021

A better way of passing data between destinations.

I use to pass data to the next screen (maybe to the Detail Screen) in a way that was strongly not recommended by the Official Android Documentation and I discovered that most beginners also follow the same way and that’s perfectly fine as a beginner, but nevertheless, there is a better way to do so, Let’s learn!

PS: This is not something new, I just wanted to share my experience. 😃


❗ The Issue:

OK! So I remember initially (when I was a total beginner) I use to pass data to the next screen via Intent (bundle) and retrieved it in the next screen’s onCreate and It did the job I wanted very well & lately, I started to use Jetpack Navigation Library and instead of writing that extra code that was; I guess, putExtra(name, value)… I use to add a Custom Parcelable as an argument and it also did the job exactly as it used to do via Intent until I one day read that:

image Reference: Pass data between destinations (Official Android Documentation)

So basically, what I have to do was to pass the minimum amount of data such as an ID or some sort of another unique identifier of the object and request either my cache or API to retrieve that specific data and by the way, it’s always fast when you do this via Cache. I refactored my whole project (the codebase was not too small or huge, You may check my app on Google Play Store) and now what I did was to pass the ID of the object, again retrieved it via the NavArguments and as soon as the ID was retrieved, I requested my ViewModel to do the remaining task and Yeah, I did as the documentation suggested 🤞


🤦‍♂️ ANOTHER ISSUE EMERGED:

Whenever there was any configuration change in the app such as if the user requested an orientation change or if they toggled the theme of the app, onCreate get’s triggered again and thus, it requests the ViewModel to get the data once more, and obviously I have to avoid this.


✨SavedStateHandle to the rescue:

You probably know that the init block of the ViewModel gets triggered only once, and that’s when the ViewModel is initialized, thus we will request our data from there but wait! How would we get the Unique identifier or ID from the previous screen in the init block of the ViewModel? (Seems Impossible?!). As seen previously, we have to avoid getting the data from the previous screen and pass it to our ViewModel at onCreate. So as the header of this paragraph says, we would be using SavedStateHandle to solve this problem.

Short explanation of the SavedStateHandle:

We use SavedStateHandle to restore our app from Process Death and the best thing about it is that it also contains the Navigation parameters. It means that you can pass the ID as you like and only the process of the retrieval changes. Also, we can always call the SavedStateHandle from our ViewModel!

PS: I’m using Dagger-Hilt and they provide SavedStateHandle as a free dependency i.e. We don’t have to provide it explicitly in any Hilt Modules. Read more about it here.

image Reference: Hilt View Models (Official Dagger-Hilt Documentation)

In case if you are not using Dagger-Hilt, you can learn how to add/instantiate SavedStateHandle here.


📙 USAGE:

I assume that you have already passed the ID via any method you like, such as via NavArguments, explicitly defining bundle Pairs while navigating, or via Intent if you are passing the ID to an activity.

The second one is which I’ll use here to demonstrate the usage:

// Extension Function
fun Fragment.navigate(@IdRes destination: Int, args: Bundle) {
    navigate(destination = destination, args = args)
}

// How To Use?
// post_id is the data you want to pass to the next screen [i.e. an Int, String or any type)
// "key_post_id" is the unique key which would be used to retrive post_id via SaveStateHandle.
navigate(
   destination = my_destination.actionId,
   args = bundleOf("key_post_id" to post_id)
)
Enter fullscreen mode Exit fullscreen mode

Here, we are explicitly passing the bundle, the “key_post_id” would be our KEY to retrieve the ID (post_id) via the SavedStateHandle and that’s how it works.

Our ViewModel:

@HiltViewModel
class MyViewModel @Inject constructor(
    // Injecting savedStateHandle with the help of Dagger-Hilt
    savedStateHandle: SavedStateHandle
) {
    /** *
     * This block get's called only once and that's when the ViewModel is initialized.
     */

    init {
        // If you are passing String, use .get<String> and so on...
        // In our case we are using an Int.
        // the "key_post_id" should be replaced with the unique key you passed.
        savedStateHandle.get<Int>("key_post_id")?.let { id ->
            // use the id to get the data from cache or your APi
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

As commented, the “key_post_id” should be replaced with the unique key you passed. In case you used Intent to pass the data, you would replace it with the key you used at putExtra(“the_key”, “value”) and if you have used Jetpack Navigation Library, you should replace it with the name of the argument.

That’s it. If you want a more robust example of this, please refer to my RocketXDeligt Project at Github. You can also directly check out the ViewModel where I have used this in practice.

🙋‍♂️ If you have any queries, feel free to reach me on Twitter. I would be more than happy to help you!

👍 Thanks for reading this out. 😃

💖 💪 🙅 🚩
kasemsm
Kasem SM

Posted on October 2, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related