https://kotlinlang.org logo
#compose
Title
# compose
o

Othman El Jazouli

12/09/2022, 5:09 PM
sorry if this has been asked multiple times already - what’s the consensus on passing Serializable for compose navigation? I have some objects that don’t have unique ids and need to pass them around
i

Ian Lake

12/09/2022, 5:23 PM
o

Othman El Jazouli

12/09/2022, 6:19 PM
thanks! all clear!
c

Casey Brooks

12/09/2022, 6:42 PM
Yup, in my experience the main thing to help with the pattern linked in Ian’s thread is to push data shared between screens into the Repository layer. However, that layer doesn’t necessarily need to be backed by a persistent store like a SQLite database, for ephemeral data it could absolutely be cached in-memory in something like a
StateFlow
. That’s kind-of the whole point of Repository layer, that it is a place where data is stored, but the actual storage mechanism is an implementation detail. It’s not simply a wrapper over your local database
In fact, I’ve found that the same way one manages state in a reactive UI like Compose can actually be very effective for managing the data in the Repository layer too. Many folks treat the Repository layer as an abstraction over the database, basically remapping DB models “safer” UI models, but you can make much better use of it when you think of it in terms of State much like you would the UI. The only difference is just that it doesn’t render UI in itself, but it can be used to emit the non-UI State for your application in much the same way. Then you can use the same mental model for both the Repository and UI layers. For example, rather than the UI “querying” for the login state in request/response fashion, it can just passively observe the UI state as the user logs in/out. The complexity of managing the session becomes a relatively simple exercise in setting the appropriate data into a
StateFlow
. And the same idea can hold with much of the data that would be shared throughout your application, without necessarily needing to cache it in a local DB
f

Francesc

12/09/2022, 7:28 PM
if your data is stored ephemerally, that means that your app won't work properly after restoration from process death
o

Othman El Jazouli

12/09/2022, 7:32 PM
my concern is mostly around identifying that object - so if you don’t have an id, Ian mentioned you can just use the index on the list potentially - that would work in some cases but I can imagine some more complex data where that would fail
f

Francesc

12/09/2022, 7:33 PM
what I am referring to is that the solution where the data to pass to your destination is stored only in memory and the new destination retrieves that data, then this won't work after process death
you need to persist the data to survive process death
c

Casey Brooks

12/09/2022, 7:35 PM
It depends on how your app is set up to handle that case, whether you actually need to use the Android mechanism to save data after process death. I’m basing this on the problem from the linked thread, where the list of posts is fetched via GraphQL when it’s missing, rather than fetching at a specific point in time. If you keep it intentional that the list itself is ephemeral, than you shoudn’t expect it to survive process death, and the next the time app launches you just fetch it again. This is just my personal experience building apps, where honestly I’ve never found a good enough use-case to properly handle process death and manage the complexity around it; the clients I’ve worked with would rather wait and display a loader than show stale information. So just re-fetch that data if it goes missing for any reason, process-death or otherwise.
i

Ian Lake

12/09/2022, 7:35 PM
Maybe you should talk about your specific case and not in generalities if you want us to help with a specific case you have. Tell us what your data looks like, what the source of truth of your data is, who loads it, who needs to read it, etc.
As long as you can refetch after process death, that's enough to fulfill the expectation - it is apps that fail to restore at all that you want to avoid being lumped with
f

Francesc

12/09/2022, 7:38 PM
that works well when you have some id you want to use to fetch the data, from local or remote storage, but the original question refers to "some objects that don't have an id", and in that case, that would require some persistence to survive process death.
but the question is a bit too abstract
c

Casey Brooks

12/09/2022, 7:40 PM
yes, you’re right. In the case that there is no mechanism to look up a post (from memory or persistent storage), then you would need some way to persist that object though whatever the Android system is going to do it is. Storing it in a ViewModel’s
SavedStateHandle
, for example
o

Othman El Jazouli

12/09/2022, 7:40 PM
yeah exactly, because at that point, the data might come in a different order for some reason for example - so the question was what’s the practice for such cases
c

Casey Brooks

12/09/2022, 7:41 PM
That said, it’s probably not a good practice to do that, because there are so many ways that you could lose track of those objects. If the server doesn’t give you an ID, you might consider generating your own in the app itself to use as an “internal ID” and avoid many of these problems
i

Ian Lake

12/09/2022, 7:42 PM
Again, I think it is important to not talk about hypothetical cases. Tell us about the real case you have
o

Othman El Jazouli

12/09/2022, 7:42 PM
yea that’s what I would’ve gone for
okay
i

Ian Lake

12/09/2022, 7:43 PM
Tell us what your data looks like, what the source of truth of your data is, who loads it, who needs to read it, etc.
o

Othman El Jazouli

12/09/2022, 7:50 PM
so there is a list of
Coin
, and they can be either
Fiat
or
Crypto
- there are multiple api calls to construct them our usecase call doesn’t know about any of those, it just expects a list of
Coin
in compose we list them all and you can tap on a
Fiat
to view some things there are many modules involved, including one with a core compose navigation, from my current module that shows the list, I have to pass that
Fiat
object up to the compose navigation, and send it through to open a different screen
this is roughly how it looks like
i

Ian Lake

12/09/2022, 8:10 PM
This example doesn't match with what you've been talking about in this thread though, since each coin does have a unique identifier (USD, eth, etc) which seems like it would make it easy to pass to a CoinUseCase to retrieve by identifier
o

Othman El Jazouli

12/09/2022, 8:12 PM
sure, but there might be some coins with the same ticker and name, since anyone can create any coin with any name they want - like someone can create a btc/bitcoin coin and publish it
that’s why we didn’t want to rely on that info
i

Ian Lake

12/09/2022, 8:14 PM
There might be? Or there is?
o

Othman El Jazouli

12/09/2022, 8:15 PM
there is other cases where the same coin is in different blockchains - usdc on eth network and on matic for example
so 2 instances with the same name
i

Ian Lake

12/09/2022, 8:18 PM
It sounds like you already know how to tell those apart though - the unique identifier is name+network?
o

Othman El Jazouli

12/09/2022, 8:18 PM
so I guess the idea is to basically construct an ID, perhaps out of different field, I agree that one source of truth and query it
I was wondering if there was a different way
that’s why I asked about sending a serializable
i

Ian Lake

12/09/2022, 8:20 PM
Other than structuring your data correctly in the first place?
o

Othman El Jazouli

12/09/2022, 8:21 PM
that would partially need backend change and I would agree that’s not the major concern here
i

Ian Lake

12/09/2022, 8:23 PM
The previous thread did talk about how Navigation supports passing objects that exist only at the UI layer: https://kotlinlang.slack.com/archives/CJLTWPH7S/p1657897115655109?thread_ts=1657844159.552379&cid=CJLTWPH7S
But that clearly isn't the case here now that you've talked about the specifics
o

Othman El Jazouli

12/09/2022, 8:24 PM
right, I believe part of the issue here is a bad data structure and doesn’t have much to do with actual compose
thanks a lot for the help!! much appreciated
c

Casey Brooks

12/09/2022, 8:30 PM
If the data you get from the APIs doesn’t give you reliable/stable IDs, that where generating your own comes in handy. You could create a custom “composite key”, such as name+network as Ian mentioned. And these values can even be a mix of hard-coded values and ones pulled from the API. For example, the network name might not come from the API response, but you probably have a list of network endpoints or different Service classes for each one that could be given some hardcoded String name within the app’s code. Or if there isn’t even that, just create something completely arbitrary like a UUID that you can reference, that had absolutely no relation to the actual data but still gives you a way to uniquely identify an object in your application.
p

Pablichjenkov

12/09/2022, 10:18 PM
Another alternative could be having a separate Repository that holds your App UI State. As Casey mention, repositories are being sold as pure data layer wrappers but nothing stops you for having one where you keep In-Memory data of you Application. For example, you can have a repo with a mirror of your nav-graph nodes, this nav-graph mirror only keeps specific UI data about each nav-graph node. Then let’s say, from any NavBackStackEntry1 you save ui data in the mirror node1, navigate to a different NavBackStackEntry2 and consume the data from the node1. That sort of things
i

Ian Lake

12/09/2022, 10:40 PM
I would strongly recommend against that approach, both because of the process death/recreation conversation above and because you cannot and should not try to mirror the lifetime of individual NavBackStackEntries and their state (a ViewModel created at the destination or graph level is a much better way to do that kind of in memory store that is backed by NavController's logic for when they can be reclaimed)
c

Casey Brooks

12/09/2022, 10:47 PM
Yeah, I would also suggest keeping the majority of your state in ViewModels scoped to whatever you need (the fragment, the navGraph, the composable, etc.). The Repository, then, holds onto anything that isn’t strictly related to the UI, or that needs to be shared across screens, and is more-or-less global to the application. The Repository layer can cache stuff in memory, but as Ian mentioned, it should also respect the possibility of being lost at any time due to process death. Whether that means having that data backed by a local DB, having logic to re-fetch from the API when the in-memory cache is empty, having those Repositories somehow save their state to a
SavedStateHandle
, etc.
p

Pablichjenkov

12/10/2022, 1:30 AM
I don’t mean mirror each NavBackStackEntry state but each NavDestination state. This is assuming destinations are unique across the whole App. There could be multiple navbackstactentries pointing to the same mirrored destination. But right, that will definitely won’t be process death friendly unless you persist everything in the mirror. It kind of scape the lifecycle of the main Android components Activity/Graph/NavBackStackEntry, it would be more like a Global ui state holder. So well, disregard my comment above
95 Views