Status Update
Comments
je...@gmail.com <je...@gmail.com> #2
il...@google.com <il...@google.com> #3
Re #2 - No, that's not the right approach. The problem isn't with having arguments with complex types - that's actually totally fine (and you could absolutely use an EnumType
with a defaultValue
to build an
zh...@gmail.com <zh...@gmail.com> #4
People are, in fact, currently relying on the documentation, incorrectly claiming that sending Parcelable arguments to Composables is possible.
ja...@google.com <ja...@google.com>
zh...@gmail.com <zh...@gmail.com> #5
By the way, Stack Overflow is doing this:
This would be lost across process death, wouldn't it?
il...@google.com <il...@google.com> #6
Re
Re
[Deleted User] <[Deleted User]> #7
I also agree that the ideal scenario is to just pass an ID, but not every screen from the app has a well designed REST API that returns everything the screen needs based on the ID, so passing some arguments is sometimes necessary.
Creating a whole stack (ViewModel.function -> UseCase.function -> Repository.function -> DataSource.function) just for passing an object from one screen to another is such a pain. Me and many people that I've talked with would avoid using the library because of this limitation, specially when we look to the other side and see iOS developers doing it so easily.
ci...@gmail.com <ci...@gmail.com> #8
The problem with imposing this limitation is that we need to rely in other tools r strategies to solve our problem.
I don't understand why not letting the developers using the library decide when they are going to use it like this or not.
ro...@gmail.com <ro...@gmail.com> #9
//pass
navController.currentBackStackEntry
?.arguments?.putSerializable("dog", dog)
navController.navigate("details")
//receive
val dog = navController.previousBackStackEntry
?.arguments?.getSerializable("dog") as? Dog
go...@gmail.com <go...@gmail.com> #10
There needs to be proper support for Parcelable objects.
The workarounds suggested in the the
// In the source screen...
navController.currentBackStackEntry?.arguments =
Bundle().apply {
putParcelable("bt_device", device)
}
navController.navigate("deviceDetails")
quoted above doesn't seem to be working as of navigation-compose 2.4.0-alpha01
since arguments
does not have a setter.
This means that as of now the only way to pass an argument will be if it can be Stringified somehow. Consider the example where we need to pass a File Path as an argument e.g. /usr/home/my_file . There is currently no support for this scenario and it should be made a priority to support such functionality without workarounds, therefore supporting Parcelable/Serializable objects should be prioritised before compose-navigation goes to beta!
cf...@gmail.com <cf...@gmail.com> #11
il...@google.com <il...@google.com> #12
Re
Re
cf...@gmail.com <cf...@gmail.com> #13
Thanks for your reply. The documentation you linked says to use the function navGraphViewModels
to get a nav graph scoped ViewModel
. However, this appears to be an extension function on Fragment
, which is not applicable to a Compose-only app like mine, which has no Fragments
. If the best practice for my case is storing the object I want to pass between screens in a shared ViewModel
, is there equivalent functionality to scope such a ViewModel
to a fixed pair of Composable nav destinations? Or is your recommended approach not yet possible in Compose-only navigation?
Also, would the best practice in this scenario be for each screen to have an individual ViewModel
for state unique to that screen, in addition to this shared ViewModel
for the data I want to share between screens?
il...@google.com <il...@google.com> #14
Re viewModel()
As of 1.0.0-alpha07
viewModel()
allows you to set exactly what ViewModelStoreOwner
you want to scope the ViewModel to, thus allowing you to use the NavBackStackEntry
associated with a
val currentScreenViewModel = viewModel()
val navigationGraphViewModel = viewModel(navController.getBackStackEntry("nested_route_goes_here"))
This matches what we already suggested for hiltViewModel()
instead of viewModel()
).
The
cf...@gmail.com <cf...@gmail.com> #15
Excellent, thank you! This seems like a solid solution for this use case, rather than passing the complex object as a nav arg.
However, one complication I can see is that each individual screen within the flow still has a UI state that is truly unique to that particular screen. It seems to violate separation of concerns to have one single shared ViewModel
to control the whole flow, including the UI state and user actions that are only associated with one screen or the other.
Which approach would be better practice here, to have just one ViewModel
for the entire flow (scoped to the nested nav graph), and no screen-specific ViewModels
? Or to have a ViewModel
scoped to the nested nav graph which only contains the truly shared data, then have an additional ViewModel
for each screen within the flow, which controls the UI state and user actions unique to that screen? The latter approach would be a better separation of concerns, but is it okay to have two ViewModels
associated with one screen (one shared and one unique)?
il...@google.com <il...@google.com> #16
Re
a....@gmail.com <a....@gmail.com> #17
I am totally disagree that passing Parcelable is a bad practice. It is depends on a type of a passed object. For Entities (objects with identation) it is better, of course, to pass just an id. But Value Objects are another story. They don't have any ids.
In the context of navigation we use Value Objects to describe HOW some screen should be opened. For example, we need SongListScreen
in our music app. We want to make this screen reusable to show different lists of songs: popular songs, current user's favorite songs and some other user's favorite songs. Lets make a descriptor to specify a type of song list:
sealed class SongListType {
object Popular: SongListType()
object MyFavorite: SongListType()
data class UserFavorite(val userId: String): SongListType()
}
We need to pass SongListType
as an argument to SongListScreen
. What is our option with Jetpack Navigation now? To convert it to string. This is error prone and harder to support. Making SongListType
Parcelable is a better choice for this case.
be...@google.com <be...@google.com> #18
The
ja...@gmail.com <ja...@gmail.com> #19
Can you respond to the Value Objects case? I do agree that passing a parcelable is valuable in some cases, and you just stripped out the option for compose. That seems not like "anti-pattern warning" and more like "we don't like it, so deal with it". You allow passing parcelables in fragment navigation. You created parcelable for a reason. Give us an option to use it in compose navigation without "previous back stack" hack. You can create as many warnings as humanly possible, but just give us the option..
be...@google.com <be...@google.com> #20
There are multiple ways you could implement this within a composable hierarchy. With routes in Compose Navigation you could create a destination for each of these, e.g myapp://popular
or myapp://favorites
.
Routes are essentially deep linkable Strings which are addressable from within your app, or (if you enable it) even from other apps. Passing a Parcelable along on that route is just not practical.
If you have complex data that needs to be used inside the destination passing it not from the route but through the Composable function itself is a viable approach.
ja...@gmail.com <ja...@gmail.com> #21
I have to admit that i do not understand how can i pass complex data through the composable function using navigation compose.
Simplest case - i have a modal that just prints out 3 values. Right now, in fragments, i can pass parcelable with those 3 values. It's simple as that, pass it to fragment -> get it in fragment -> display it. How do i do that in compose navigation? Do i have to create all of this boilerplate to create url with all of those values, change their type? Or do i create a viewModel just to pass a single object between 2 screens? Both of those options seems like a hacks themselves.
co...@protonmail.com <co...@protonmail.com> #22
That was not the right approach in fragment-land either. You should just pass all three simple types if you want, or pass a single identifier that will allow you to retrieve the information from a persistent source or a larger scope (i.e. save to db, save to disk/file, or have the data that lives in some activity level VM).
Like the description in the doc says:
"Caution: Passing complex data structures over arguments is considered an anti-pattern. Each destination should be responsible for loading UI data based on the minimum necessary information, such as item IDs. This simplifies process recreation and avoids potential data inconsistencies."
Passing complex data has been an anti-pattern for years + google keeps lowering the bundle transaction limit and so teams that do this eventually hit problems where they have to refactor their code anyway.
I know it's not the answer you want to hear, but you need to change your approach. Hope it helps. Reach out to me on kotlinlang slack if you still need some help coltonidle
. Cheers
[Deleted User] <[Deleted User]> #23
The route structure in Navigation Compose has the best analog with a restful web service
Except RESTful services allow you to POST data.
val navigationGraphViewModel = viewModel(navController.getBackStackEntry("nested_route_goes_here"))
How do you suggest to send data into a nested route? When you're at a parent navigation graph destination, the "nested_route_goes_here" is not in the back stack, so getBackstackEntry()
will throw an NPE.
ja...@gmail.com <ja...@gmail.com> #24
We are not talking about complex data, we just want to pass a value with 3 fields (complex identification key, for example) without creating some abysmal string. I have a simple object, i want to pass it to other screen. Why should i bother making it a string? That seems like a lot of boilerplate without good reason. Platform is limiting parcelable size - fine, make it clear in documentation. I've been using parcelable for the last 10 years and I've never had this oversize error. Are you saying that as devices are becoming more powerful android will limit that even more? Why is that?
As i'm going through Stack Overflow people want and will hack navigation library to pass serializable and parcelable (just look at this issue
I'm looking to use types to represent my directions, like in library:
co...@protonmail.com <co...@protonmail.com> #25
Just elevate the objects scope to the nearest thing that encapsulates both screens. Done!
il...@gmail.com <il...@gmail.com> #26
Re
Now there is no easy way to pass parcelable or serializable object using compose navigation library. I have to write a lot boilerplate code to convert data class object to json(for example), format route string with json data and parse to restore data class in final destination afterwards.
I would like to use a method of NavController similar to this:
fun navigate(
screenId: String,
args: Bundle? = null,
navOptions: NavOptions? = null,
navigatorExtras: Navigator.Extras? = null
)
ja...@gmail.com <ja...@gmail.com> #27
I'm really gonna stick with voyager. Not because it's more fancy or something. But because it's a community project, driven by our needs. I understand that Jetpack is heavy opinionated and I'm fine with that. But I'm 100% sure that i will be fixing a lot of hacks around this limitation and IMO it's really pointless
ww...@gmail.com <ww...@gmail.com> #28
It's like in every great solution (like Compose) there must be at least one stupid limitation that brings usability to a complete halt. This is just disappointing...
co...@protonmail.com <co...@protonmail.com> #29
Just stop misusing parcelables.
nu...@gmail.com <nu...@gmail.com> #30
The current serialization hacks show very well that this is a bad solution that only complicates what is easily done without navigation for many years.
Nobody talks about parsels as large objects. You just killed the ability to work with a set of simple flags without giving any alternative. And custom NavTypes does not make this problem any easier.
It is not comfortable. It's very overcomplicated. It's not crash-safe. And this is not applicable in my current projects, and makes me think about cut navigation from it.
And it's amazing how Google manages to agitate everyone to multimodules, while providing such clumsy and inconvenient solutions for this.
It's just not usable with that, if you're not backend with army of QA, wich will constantly check navigation problems.
Just imagine how your backend telling you "Stop using POST because I don't like it, use only GET". This absurdity is happening here now.
ja...@gmail.com <ja...@gmail.com> #31
To rely on data and not only on our opinions. Look at jetpack-navigation-compose tag in StackOverflow:
You can clearly see that this is not only our imagination, this is a VERY serious issue. The amount of "how do i pass the argument" or "why is navigation crashing" questions is absurd. And that wouldn't ever happen if the whole thing was designed to be type-safe at start
ko...@gmail.com <ko...@gmail.com> #32
For anybody looking for a solution to pass Parcelables to destinations:
- create a custom
navigate
extension method that accepts a Bundle (or anything Bundle-compatible) - implement
NavController.OnDestinationChangedListener
and add it to theNavHostController
- this receives the
arguments
passed to the destination - in
onDestinationChanged
check if the destination is the target one, if it's true:- add your Bundle to the arguments
- remove the
OnDestinationChangedListener
- this receives the
- navigate with the built-in method
- congrats, you can now pass Bundle-compatible arguments to the destination
To ensure that the arguments
is not null, add any argument to the destination's argument list. The easiest one is to create a named argument that is nullable and is set to null by default.
This workaround works reliably even if the process is killed by the system.
le...@gmail.com <le...@gmail.com> #33
ro...@manadr.com <ro...@manadr.com> #34
Think about you have some screens with registration form : first name, last name, passport, gender...
With object and Parcelable, I only need to pass one object and decorate it like data class with COPY.
So disappointed.
ja...@gmail.com <ja...@gmail.com> #35
his issue let me stop of using Jetpack Compose from the company projects
It shouldn't! Jetpack Compose is a great technology. Just don't use the Jetpack Navigation and you'll be fine. There are already many great alternatives
ri...@gmail.com <ri...@gmail.com> #36
This issue let me stop of using Jetpack Compose from the company projects.
Just forget this issue and don't use this navigation library
I recommend Rafael Costa Compose Destination Libary instead, it's pretty good and easy to use
ro...@manadr.com <ro...@manadr.com> #37
ro...@manadr.com <ro...@manadr.com> #38
It shouldn't! Jetpack Compose is a great technology. Just don't use the Jetpack Navigation and you'll be fine. There are already many great alternatives
Yes, I see it great, but without this passing complex arguments, I think the only way to make it is to use Compose with Compose View inside a Fragment. I don't really like that, because I expect Compose can replace Fragment totally.
I recommend Rafael Costa Compose Destination Libary instead, it's pretty good and easy to use
Thank you. I will give it a try. However, the best thing is the Google Dev team should provide an official way.
ja...@gmail.com <ja...@gmail.com> #39
I think the only way to make it is to use Compose with Compose View inside a Fragment. I don't really like that, because I expect Compose can replace Fragment totally
No, like i said there are already multiple alternatives, just lookup Voyager. Also - using Compose in Fragment is really not big of a deal. There still be cases when you have to use fragment, for example when using like old libraries. AndroidView is fine, but possibility of using a single screen as a Fragment - priceless. Also, fragment-based solution is the best way for a migration to compose without having to rewrite everything.
However, the best thing is the Google Dev team should provide an official way
They seem to be locked on the stringified way of navigation so i wouldn't hold my breath
ro...@manadr.com <ro...@manadr.com> #40
Also, fragment-based solution is the best way for a migration to compose without having to rewrite everything.
Yes, I also see the point here. Thank you for the same between our ideas.
They seem to be locked on the stringified way of navigation so i wouldn't hold my breath
Me too. But adding a library to build core features seems to be not safe. I already had many thing to migrate in code : synthetic, MVP, RxJava2...
il...@google.com <il...@google.com> #41
Note that Compose Destination, mentioned in
Compose Destination is just code gen for code you can write yourself using Navigation Compose, as seen in the code in the documentation.
If you're interested in seeing Navigation itself generate similar type safe code, you'll want to star the
[Deleted User] <[Deleted User]> #42
al...@live.it <al...@live.it> #43
pass a single identifier that will allow you to retrieve the information from a persistent source or a larger scope (i.e. save to db, save to disk/file, or have the data that lives in some activity level VM).
In the case of using an higher scoped VM as source of truth, how do I access it from the screen level VM (the one that knows the identifier because it's its navigation argument)?
Description
Component used: Navigation Compose
Version used: 1.0.0-alpha08
On the Navigating with Compose guide , it talks about how arguments are passed as part of the route URI. However, it does not document the best practices for working with complex arguments such as
Parcelable
classes.The guide should explicitly mention that passing complex types is not supported and that you should pass the ID of the item instead of the item itself. The route structure in Navigation Compose has the best analog with a restful web service so developers should use
profile/{id}
notprofile/{a whole set of fields representing a user's profile}
which is essentially what passing a parcelable is doing. That ID should then be used to access a shared repository that has lets the new destination look up the related object given the ID.