How is the paramter onScreenChange : (String) -> Unit passed to the function body : @Composable ((String) -> Unit) -> Unit?
The Code A is from the main branch of the official sample project.
There are three subclass Overview
, Accounts
and Bills
of the enum class RallyScreen
in the project.
There is a function fun content(onScreenChange: (String) -> Unit) { body(onScreenChange) }
which accept the paramater onScreenChange : (String) -> Unit
in the class RallyScreen
.
Although the statement is enum class RallyScreen(val body: @Composable ((String) -> Unit) -> Unit) {..}
, the body = { OverviewBody() }
in the class Overview
, the body = { AccountsBody(UserData.accounts) }
in the class Accounts
and the body = { BillsBody(UserData.bills) }
in the class Bills
don't require to pass the paramter (String) -> Unit)
, why can Kotlin run well and navigate by Tab well when the App launch fun content(onScreenChange: (String) -> Unit) { body(onScreenChange)}
?
Code A
@Composable
fun RallyApp() {
RallyTheme {
val allScreens = RallyScreen.values().toList()
var currentScreen by rememberSaveable { mutableStateOf(RallyScreen.Overview) }
Scaffold(
topBar = {
RallyTabRow(
allScreens = allScreens,
onTabSelected = { screen -> currentScreen = screen },
currentScreen = currentScreen
)
}
) { innerPadding ->
Box(Modifier.padding(innerPadding)) {
currentScreen.content(
onScreenChange = { screen ->
currentScreen = RallyScreen.valueOf(screen)
}
)
}
}
}
}
enum class RallyScreen(
val icon: ImageVector,
val body: @Composable ((String) -> Unit) -> Unit
) {
Overview(
icon = Icons.Filled.PieChart,
body = { OverviewBody() }
),
Accounts(
icon = Icons.Filled.AttachMoney,
body = { AccountsBody(UserData.accounts) }
),
Bills(
icon = Icons.Filled.MoneyOff,
body = { BillsBody(UserData.bills) }
);
@Composable
fun content(onScreenChange: (String) -> Unit) {
body(onScreenChange)
}
}
@Composable
fun OverviewBody(
onClickSeeAllAccounts: () -> Unit = {},
onClickSeeAllBills: () -> Unit = {},
onAccountClick: (String) -> Unit = {},
) {
...
}
@Composable
fun AccountsBody(
accounts: List<Account>,
onAccountClick: (String) -> Unit = {},
) {
...
}
@Composable
fun BillsBody(bills: List<Bill>) {
...
}
Added Content:
To Gabriel Pizarro: Thanks!
By the statement of clas Overview
, the body
needn't any parameter, it use default values, how is the onScreenChange
parameter passed ?
Overview(
icon = Icons.Filled.PieChart,
body = { OverviewBody() } //It needn't any parameter
)
@Composable
fun content(onScreenChange: (String) -> Unit) {
body(onScreenChange)
}
And more, the app can work well when I add two default parameters for fun OverviewBody(...)
.
@Composable
fun OverviewBody(
onClickSeeAllAccounts: () -> Unit = {},
onClickSeeAllBills: () -> Unit = {},
a1: (String) -> Unit = {}, // I add
onAccountClick: (String) -> Unit = {},
a2: (String) -> Unit = {}, //I add
) {
...
}
Maybe
body = { OverviewBody() }
should bebody = { OverviewBody(onAccountClick = it) }
inOverview
class, right?
Actually, NO!!!
The lambda that body
receives is different from OverviewBody.onAccountClick
. In body
's lambda, the String
refers to a name of RallyScreen
as you can see from its usage here. And in onAccountClick
lambda, the String
represents an accountName.
By the statement of class
Overview
, thebody
needn't any parameter, it use default values, how is theonScreenChange
parameter passed ?
onScreenChange
is not passed called anywhere and that's why navigation doesn't work.
You can make the SEE ALL
buttons in Overview screen work using:
Overview(
icon = Icons.Filled.PieChart,
body = {
OverviewBody(
onClickSeeAllAccounts = { it(Accounts.name) },
onClickSeeAllBills = { it(Bills.name) },
)
}
)
The onAccountClick
lambda in OverviewBody
is tricky to handle. We need to show the SingleAccountBody composable when AccountRow is clicked. Right now there is no RallyScreen
corresponding to that composable. So you will have to create a new enum value in RallyScreen
something like this:
Overview(
icon = Icons.Filled.PieChart,
body = {
OverviewBody(
onClickSeeAllAccounts = { it(Accounts.name) },
onClickSeeAllBills = { it(Bills.name) },
onAccountClick = { accountName -> >>>>>>
it(SingleAccount.name) |
} |
) |
} |
), |
Accounts( |
icon = Icons.Filled.AttachMoney, |
body = { |
AccountsBody(UserData.accounts) |
} |
), |
Bills( |
icon = Icons.Filled.MoneyOff, |
body = { BillsBody(UserData.bills) } |
), |
SingleAccount( |
icon = Icons.Filled.AttachMoney, |
body = { |
SingleAccountBody(UserData.getAccount(???accountName???)) <<<--
}
);
Here we need to send that accountName
received from Overview
screen to SingleAccount
screen to make the navigation work. But there is no simple way to do that with the current code structure and I don't want to complicate this answer much. In the final code of this codelab, this accountName
has been passed using NavArguments.