adrielcafe / voyager

🛸 A pragmatic navigation library for Jetpack Compose
https://voyager.adriel.cafe
MIT License
2.38k stars 114 forks source link

[Proposal] Pass default parameters into Screen Content() function #26

Open egorikftp opened 2 years ago

egorikftp commented 2 years ago

Hello, I'm trying to adopt your library into my pet project. I was faced with an issue, that the inner screen should consider padding from the parent screen.

Please take a look sample. I need to pass paddingValues into CurrentTab(). The Scaffold provides this value to prevent content overlapping.

TabNavigator(FlagsTab) { tabNavigator ->
            Scaffold(
                modifier = Modifier.systemBarsPadding(),
                topBar = {},
                content = { paddingValues ->
                    CurrentTab()
                },
                bottomBar = {
                    BottomNavigation {
                        TabNavigationItem(Tab1)
                        TabNavigationItem(Tab2)
                    }
                }
            )
        }

My suggestion is to make Screen interface like this with default parameters:

interface Screen : Serializable {

    val key: ScreenKey
        get() = this::class.qualifiedName
            ?: error("Default ScreenKey not found, please provide your own key")

    @Composable
    fun Content(
        modifier: Modifier = Modifier,
        paddingValues: PaddingValues = PaddingValues()
    )
}

Thanks a lot)

programadorthi commented 2 years ago

Hey guys, the voyager owner is very busy and his wife is pregnant. Updates will come soon. Stay up to date.

adrielcafe commented 2 years ago

Nice! Just tried that but got the error Abstract Composable functions cannot have parameters with default values.

I'll try to find another way, suggestions are welcome.

MFlisar commented 1 year ago

Maybe you find a solution in the future - for now I just wanted to share my solution if someone else is looking here.

I simply solve that by using local compositions to provide custom data to sub composables and this looks like following for this use case:

// define a custom local composition
val LocalContentPadding = compositionLocalOf { mutableStateOf(PaddingValues()) }

fun MainScreen() {

  // rememeber the paddings here inside the root level
  val paddingValues = remember { mutableStateOf(PaddingValues()) }

  // provide the padding state  inside the local provider
  CompositionLocalProvider(
    LocalContentPadding provides paddingValuesTab
  ) {

    MyAppTheme {
      BottomSheetNavigator(
        sheetContent = {
          Navigator(NavScreenBottomSheet)
        },
        content = {
          TabNavigator(NavScreenTabHome) {
            Scaffold(
              topBar = {

              },
              content = { pv ->
                // update the paddings
                paddingValues.value = pv
                CurrentTab()
              },
              bottomBar = {

              }
            )
          }
        }
      )
    }
  }
}

And inside your tabs and/or sub screens you can now do following:

@Parcelize
object NavScreenTabHome : Screen, Parcelable {

  @Composable
  override fun Content() {
    val paddingValues = LocalContentPadding.current
    // use the padding in your composables...
  }
}
ITmind commented 8 months ago

Create a new class ScreenX:

abstract class ScreenX: Screen {

    @Composable
    abstract fun Content(modifier: Modifier)

    @Composable
    final override fun Content() = Content(Modifier)
}

Inheriting from it the class of a specific screen:

object MainScreen : ScreenX() {
    @Composable
    override fun Content(modifier: Modifier) { 
       Column(modifier) {/* code */}
    }
}

create own CurrentScreen function with param:

@Composable
public fun CurrentScreen(modifier: Modifier) {
    val navigator = LocalNavigator.currentOrThrow
    val currentScreen = navigator.lastItem as ScreenX

    navigator.saveableState("currentScreen") {
        currentScreen.Content(modifier)
    }
}

and next in Scaffold:

Navigator(
        MainScreen
    ) {         
            Scaffold(content = {
                CurrentScreen(Modifier.padding(it))
            })
        }
)
nvkleban commented 4 months ago

We currently are using simpler solution

 Box(
    modifier = Modifier
        .padding(paddingValues)
        //.consumeWindowInsets(paddingValues), in case if you have internal scaffolds
) {
    CurrentTab()
}