The Compose's NavigationController doesn't allow f...
# compose
b
The Compose's NavigationController doesn't allow for "free-form" navigation. I get that having a laid out static navigation graph is great, but in a considerably large project I don't think its tenable. I was able to create a "hacky" solution. However I'm curious why "free-form" navigation isn't directly supported? Answer: A static graph must to be created at start up to handle resuming after process death Hopefully something better will come along soon!
๐Ÿ‘ 1
a
What happens when your app process is terminated in the background and the user returns to the app?
b
I haven't tested that out yet, but what do you expect?
a
The reason for the static/parameterized navigation graph is so that the system can always restart your app from a deep destination without re-running all of the navigation code that got you there. I would expect that with the code above, the navigation system would try to navigate to a route that doesn't exist, since the code that navigated to it never ran.
so either your app would crash when the user returns to it, or it would restart from home from scratch after getting confused. Which one of these is preferable depends on your perspective ๐Ÿ™‚
b
How does compose's navigation normally handle this?
a
Normally since the routing is declared up front, the navigation can restore from the saved instance state and know what the last state of the back stack was
so you'll go straight there
b
I see which is the purpose of having a static navigation graph
that said most apps don't bother handling process death
a
those apps are deeply broken. All it takes is launching a web browser or the camera on most android devices and all other apps get killed in the background.
โ˜๏ธ 1
we'd prefer to get people set up for success ๐Ÿ™‚
You could write an alternative navigation system that didn't require explicitly declaring everything up front, but at a minimum it's going to have to be able to restore your navigation state and back stack from a flattened/serialized/parceled form when your app is launched from a cold start
so you would need some sort of functional mapper from navigation state => calling the right composable with the right parameters
b
Do you think Google will ever provide such a solution?
a
Maybe! We're certainly open to going where the developer interest is if we can make it work. I see @Ian Lake lurking in the emoji reactions above so if he feels strongly against it he can correct me here ๐Ÿ˜›
i
What makes you think the current approach isn't tenable in a large project? Note that you don't have to build your graph in one file - you can call methods, use a for loop to go through subgraph providers, etc. You have all the power of Kotlin code
a
to a first approximation though, the current setup of navigation with arguments is already this same kind of functional mapping
b
Maintaining a routing table for every view in large app is a big ask
i
For all the reasons Adam mentioned above, defining the graph ahead of time is a conscious decision by the Navigation Component
b
Obviously theres no other solution, so thats what we'll do. Its just creates a more rickety app in the end.
i
I think if you approach it in terms of building small, modular features with their own subgraphs, each individual graph becomes a lot more manageable
a
many of the patterns that are applicable to something like ktor or spring boot also can be used here; declaring extension functions on the navigation scope, etc.
b
Yes it seems like a web server approach to a UI application
a
I'm fascinated by the fear that this forms something "rickety" and would like to know more ๐Ÿ™‚
my inclination is the opposite; that calling
Copy code
navigator.navigate {
  SomeOtherComposable(params)
}
from arbitrary event handlers would be very difficult to maintain as an app grows. I'd love to understand more of the perspective you're approaching this from
b
Well you seem quite set in your beliefs so I doubt I will convince but here goes For starters comparing
navigator.navigate { SomeOtherComposable(params) }
to
navigator.navigate("some/route?id=3")
. The first one is clearer and you get the benefits of the compiler checking the view and route works. The second one while it taps into a central routing system, it's more arbitrary than the first. 1. Having a centralized routing table works for server side development because routing between "views" already has a large level of indirection. Also with a web server theres not often much connection between individual routes. UI Applications routing between views is a direct operation and its often time theres a strong connection between views. 2. Having an explicit "entry" points for a "view" is a must for a large project. This is an area Android has traditionally suffered. The navigation component team has improved this with named generated routes. This has been super helpful. 3. When managing an App with 100 views and roughly 2 entries per view, thats 200 different routes. A views individual route should almost always be stored along side the view itself. This has been done by having static constructors with Fragments or just named constructors for VCs on the iOS side. This isn't always the case, but it is most of the time. So the current "best practice" with Compose's navigation system is: 1. A individual Compose view includes its possible routes in its own file 2. A module or subset of Compose views collects all the possible routes into a list. You probably include your route manipulation logic here if needed. 3. The app collects all possible modules routes into one master list. 4. You then route via a string path vs 1. A individual Compose view includes its possible routes in its own file 2. You route by directly using the exposed Compose view routes Other aspects this improves 1. When figuring out how to route to a view, there is only one source of truth 2. Adding and removing view does not involve editing a high traffic file 3. Removing a view causes a compiler error I get the process death requirement, I just wish there was a better solution. Thanks ๐Ÿ™‚
โ˜๏ธ 2
a
Thanks! I don't have a terribly strong opinion here beyond meeting the process death requirement, so I'd consider anything on the table; it's still early days here
I could imagine some sort of setup where you push some sort of object implementing an interface or similar that can serialize its captured parameters as a back stack entry rather than a raw composable lambda, which might get closer to what you're looking for here. It also starts looking suspiciously like a fragment with an abstract composable method depending on your angle
b
Yes but there needs to be some sort of abstracted "ViewState" that handles taking in inputs and surfacing outputs
With Compose the benefit is that the two can finally be cleanly separated
and connecting the two together is a lot simpler
Copy code
similar that can serialize its captured parameters as a back stack entry rather than a raw composable lambda
That would work, I guess you would serialize a class type which surfaces a the Composable function
a
Yeah, something like that. The trouble with arbitrary lambdas is that they're so useful, half the stuff you end up capturing just works and it starts building expectations
And then you're hanging onto an arbitrarily large object graph
b
Ya especially when Android's process death is such a pervasive issue that direct injection of arguments into "ViewState"s shouldn't happen
a
But we've been burned endlessly by saving a class type and instantiating by reflection. After a decade of it on android I think we're over it; explicit serialization/factories are generally the better bet, but then it's more boilerplate to write
And we've been really hesitant to tell people that they need a DI framework to tie their shoes
b
My own implementation wasn't meant to be taken as a good one btw
a
It's a real one though; it expresses what you reached for and that's useful data
b
Ya but Androids application framework is set up to somewhat require a DI framework
a
Mostly to solve some particular own-goals we performed over the years
I'm still holding out hope we can back up and reverse those root causes rather than give into it. Might be a fool's errand, but still
DI is great, but compose has us so close to Android being a platform you can credibly learn to program for the first time with. DI as a price of admission is pretty steep.
b
Ya
Compose has been great
a
Thanks ๐Ÿ˜„
b
I work a lot both iOS and Android
a
Some of the stuff here has me hopeful for DI-like use cases: https://youtrack.jetbrains.com/issue/KT-10468
b
I know you probably don't hear this to often, but I prefer Android as a dev platform
โค๏ธ 2
my biggest complaints are around activities, fragments, and navigation lol
y'all do great work โค๏ธ
a
Hah, yeah, and having worked in the guts of all three over the years, there's a ton of good reason for them to be complaints
No technology would get real good complaints without them being too useful to dismiss entirely though ๐Ÿ™‚
b
haha true true
a
Anyway, truly, I appreciate the thoughtful feedback here. I know sometimes it looks like we're set in our ways or holding the party line, but the ground truth feedback like this is the best kind we can get
b
Thanks!
๐Ÿ’ฏ 1
i
Yeah, it is always great to see different perspectives
For example, many developers that have reached the "hundreds of screens" level have gone to a many, many module segmentation where there's no compile time dependency between different features (this is particularly relevant when avoiding circular dependencies). This makes the indirection through routes a huge boon
So the approach of having each UI/feature module provide a graph, then composing them together in a higher level graph (and then up to your whole app level graph) has been a really natural thing for devs to be doing in Navigation for the last few years with good success. If anything, that is way easier to do in the Navigation Compose world and even easier to keep the Navigation code out of your composables themselves (as we mention as a best practice in our testing docs: https://developer.android.com/jetpack/compose/navigation#testing)
I think where ever we end up, what we provide will have to ensure that the user doesn't lose their state even in the unpredictable world of Android. That's not always easy, but I think we'll get to a better spot by looking at a wide range of viewpoints and ideas. One of the best things about communities like this โค๏ธ
โค๏ธ 1
c
@Adam Powell "those apps are deeply broken. All it takes is launching a web browser or the camera on most android devices and all other apps get killed in the background." I still wish Android made this easier to test. There should definitely be a big huge button in Android Studio that allows you to trigger process death. There's is a workaround in AS by pressing the stop button in the logcat pane, but even the functionality of that changed in some beta back in the day. I'd wager 99% of android devs don't even know of a reliable/easy way to trigger process death.
b
Hahaha totally, I remember opening up a ton of poorly optimized Wikia sites to force it
Thanks @Ian Lake We haven't broken our views up into many modules. I tried pushing for it, but I failed at selling its benefits lol
s
@Colton Idle @birdsofparadise Android has "Don't keep activities" option at Developer options menu. It does exactly what you need :) Also, ADB has an equivalent command to test that particular behavior. Happy New Year ๐Ÿฅณ
๐Ÿ™Œ 1
b
@Sergey Y. Thanks thanks. I do wish it was as easy as a single click, but its a billion times easier than loading up bad websites lol.
Happy new year!
๐ŸŽ‰ 1
a
Don't keep activities can be useful, but it's also not a full process restart. If anything is relying on live references in the process you won't catch it
c
@Sergey Y. yea. my point wasn't that it doesn't exist, it's that it's not as easy as it should be. IMO if the android team cared about process death restoration then they would give you an easy way to trigger it. 5 years of working on Android apps and none of the teams have cared about process death. (i do, it's just that my teams haven't). Maybe just bad luck of teams on my part.
s
would give you an easy way to trigger it
Come on, you are developer, an engineer, 5 min to read official documentation and you are good to go ๐Ÿ™‚ Sometimes I regret that the official development language is not C++, there is no place for tenderness. Only brutal skills and reading manuals. Ah, modern developers... ๐Ÿ˜ž
a
If I had a nickel for every time I heard, "if the android team cared about X..." ๐Ÿ˜‚
c
@Adam Powell I'm unsure it's unfair of me to say. But process death has just become a thing that no one cares about seemingly. Idk maybe just my point of view tho. /Shruggie
a
Most of the reason for that is because we've spent years building tools and libraries that facilitate ignoring it. From activity stacks being managed by the system and stock views implementing savedInstanceState, to fragment back stacks handling it, to Room and encouraging more robust disk persistence rather than relying only on savedInstanceState, to arch components ViewModel and SavedStateHandle and now the savedInstanceState APIs for Compose. The reason Activity recreation/state restoration works the way it does is in no small part to encourage supporting the full process death case correctly and to deliberately make an app look more broken more often when it doesn't. ๐Ÿ™‚
c
Yeah. I think I read that from Dianne way back on stack overflow. A hill I'd still die on is that process death should be a button in AS. I think I submitted a bug on issue tracker once they came out with the new process death apis. ๐Ÿ˜ I only mention it because I care about process death restoration but I'm always on teams that don't care.
a
adb shell kill -9
has worked for longer than android studio has existed ๐Ÿ˜›
๐Ÿ˜ฌ 1
There's an ongoing discussion about the big red stop button in AS with the WorkManager team though
Currently it performs a full force stop, not just a process kill. Sticky services don't restart, etc.
And system jobs are cancelled
If you're testing background work like with WorkManager, this is deeply disruptive and they don't like this behavior
But if you're working on an app that is misbehaving and it just keeps restarting itself and continuing to misbehave, that's no good either
The possible use cases for stopping an app from running and just what a developer means and expects when they say those words out loud are not universal
c
Yeah. That's what I mean. There's different things available to us like that. Swipe the app away, force stop in settings > app, kill via adb, stop button in AS. It would just be nice to have a little pane in AS that helps test these situations deterministically. That's all I'm advocating for. ๐Ÿ˜
a

http://m.quickmeme.com/img/f5/f5bb63fcd28e68c2305fc6276babf73a7d2417e8ece8901e08e2afe3f328e8ab.jpgโ–พ

๐Ÿ˜
To be clear, I'm not dismissing the idea or request, but there is a nontrivial UX question involved that can't be handwaved either
๐Ÿ’ฏ 1