dave08
01/07/2024, 3:50 PMRafael Costa
01/07/2024, 5:59 PMdave08
01/08/2024, 10:16 AMRafael Costa
01/08/2024, 1:37 PMRafael Costa
01/08/2024, 1:38 PMtopBar
, mainContent
, etc.Rafael Costa
01/08/2024, 1:39 PM@Destination
@Composable
fun MyScreen() = ScreenTemplate(
topBar = {
MyScreenTopBar()
},
mainContent = {
//...
},
// other areas potentially
)
Rafael Costa
01/08/2024, 1:40 PMdave08
01/08/2024, 1:42 PMRafael Costa
01/08/2024, 1:43 PMRafael Costa
01/08/2024, 1:44 PMdave08
01/08/2024, 1:46 PMRafael Costa
01/08/2024, 1:47 PMRafael Costa
01/08/2024, 1:50 PMRafael Costa
01/08/2024, 1:51 PMwhen
..dave08
01/08/2024, 1:52 PMRafael Costa
01/08/2024, 1:53 PMTopBar
Composable to the DestinationSpec
interface.Rafael Costa
01/08/2024, 1:54 PMRafael Costa
01/08/2024, 1:55 PMdave08
01/08/2024, 2:00 PM@Destination
@Composable
fun HomeScreen(
topBar: @Composable () -> Unit,
...
could be used somehow... (topBar
doesn't have to be hard-coded... one could specify any composable name, but maybe the allow composables could have an interface defined with some kind of annotation so that all the destinations can derive and override them.)?
Or any other parameter passed like that, the challenge would be how to define a base interface of common composables that could be specified like that and then be generated to be called on the scaffold?
But I guess with ksp this is probably not so realistic...dave08
01/08/2024, 2:01 PM@DestinationsBase
interface Screen {
val topBar: @Composable () -> Unit = {
// some default topBar
}
}
This could maybe be done like the args delegate class to have some class to handle which screen to use... but this would lack the context in the actual Screen composable...Rafael Costa
01/09/2024, 4:43 PM@DestinationExtension
interface TitleHolder {
@get:Composable
val title: String
}
@DestinationExtension
interface TopBarHolder {
@Composable
fun TopBar(navBackStackEntry: NavBackStackEntry)
}
object MyScreenExtensions : TitleHolder, TopBarHolder {
override val title: String
@Composable
get() = TODO("Not yet implemented")
@Composable
override fun TopBar(navBackStackEntry: NavBackStackEntry) {
}
}
@Composable
@Destination(
extensions = [
MyScreenExtensions::class
]
)
fun MyScreen() {}
Rafael Costa
01/09/2024, 4:45 PMMyScreenDestination
would implement both TopBarHolder
and TitleHolder
(by delegating to MyScreenExtensions object in this case).
Like:
public object MyScreenDestination : DirectionDestinationSpec,
TitleHolder by MyScreenExtensions,
TopBarHolder by MyScreenExtensions
Rafael Costa
01/09/2024, 4:46 PM...
topBar = {
(navBackStackEntry.destination() as? TopBarHolder)?.TopBar(navBackStackEntry)
}
...
Rafael Costa
01/09/2024, 4:49 PMnavBackStackEntry
would be a state we can get like this:
navController.currentBackStackEntryFlow.asState(...)
dave08
01/09/2024, 4:50 PMMyScreenExtensions
needs to be implemented for each screen?Rafael Costa
01/09/2024, 4:51 PMRafael Costa
01/09/2024, 4:51 PMdave08
01/09/2024, 4:52 PMdave08
01/09/2024, 4:52 PMRafael Costa
01/09/2024, 4:52 PMRafael Costa
01/09/2024, 4:53 PMRafael Costa
01/09/2024, 4:53 PMRafael Costa
01/09/2024, 4:53 PMRafael Costa
01/09/2024, 4:54 PMAnd really, that title might even depend on the screen content...Thats totally fine, you can make title a function (like I did with TopBar) that receives the NavBackStackEntry
Rafael Costa
01/09/2024, 4:55 PMRafael Costa
01/09/2024, 4:55 PMRafael Costa
01/09/2024, 4:55 PMdave08
01/09/2024, 4:59 PMglobal
parameter in @DestinationExtension
to specify if all the destinations should inherit from it (maybe excluding some, like dialogs, with a NoDestinationExtension::class
placeholder in extentions
...dave08
01/09/2024, 5:00 PMextentions
or a null
, that it shouldn't be included in global
extensionsdave08
01/09/2024, 5:00 PMRafael Costa
01/09/2024, 5:06 PMThis sounds really not bad... and it might also solve the BottomBar problem in a similar fashion...Exactly that was my goal.. This is really just adding behaviour(i.e methods, fields, whatever) to your generated Destination. Any thing you want.
Rafael Costa
01/09/2024, 5:07 PMThe only ugly thing is the casting, and I'd maybe also add aI was thinking something like this:parameter inglobal
to specify if all the destinations should inherit from it (maybe excluding some, like dialogs, with a@DestinationExtension
placeholder inNoDestinationExtension::class
...extentions
annotation class DestinationExtension(
val requireOn: KClass<out Annotation> = Nothing::class
)
and then in the title example:
@DestinationExtension(
requireOn = RootNavGraph::class
)
interface TitleHolder {
@get:Composable
val title: String
}
Rafael Costa
01/09/2024, 5:08 PMRafael Costa
01/09/2024, 5:08 PMRafael Costa
01/09/2024, 5:10 PMRafael Costa
01/09/2024, 5:11 PM@Composable
fun NavBackStackEntry.title() = (this.destination() as TitleHolder).Title()
Rafael Costa
01/09/2024, 5:12 PMRafael Costa
01/09/2024, 5:13 PMRafael Costa
01/09/2024, 6:32 PMWould there be access to dependencies too?The reason we need dependencies block for the main Composable part is that the library is calling your Composable for you. All these "extensions" are called by you, so any dependencies you need, you can just pass them down from the NavHost level until the Scaffold TopBar (for example).
dave08
01/10/2024, 12:59 PMdave08
01/10/2024, 1:01 PMRafael Costa
01/10/2024, 1:19 PMdave08
01/10/2024, 1:31 PMinterface TitleArgHolder {
val title: String
}
@DestinationExtension
interface TopBarHolder {
@Composable
fun TopBar(titleArgHolder: TitleArgHolder, navBackStackEntry: NavBackStackEntry)
}
@Destination(
extensions = [
MyScreenExtensions::class
]
)
@Composable
fun HomeScreen(
// This goes into the args and overrides title in TitleArgHolder
title: String,
topBar: @Composable () -> Unit,
...
I didn't really think it out till the end, but this is the general idea... then MyScreenExtensions could be re-used for all screens that have title
as an arg, w/o tons of boilerplate.Rafael Costa
01/10/2024, 1:36 PMdave08
01/10/2024, 1:38 PMdave08
01/10/2024, 1:40 PMdave08
01/10/2024, 1:42 PMdave08
01/10/2024, 1:43 PMRafael Costa
01/10/2024, 6:11 PM