I just started with Jetpack Compose. My app has many screens, it shows StatusBar with color, which is defined in the theme, but on some screens, I want to make StatusBar color is transparent and Image show under StatusBar.
Please guide me step by step.
Thank you in advance.
Just write this line before setContent
window.setFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
)
Firstly in your res > theme remove action bar;
<style name="Theme.app_android"
parent="Theme.MaterialComponents.Light.NoActionBar">
Then call your composable in MainActivity, setting WindowCompat before as shown below
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
App()
}
}
}
Then using the ProvideWindowInsets function to remember a systemUiController that's called inside a SideEffect function that sets the system status bar to transparent
#Composable
fun App() {
AppTheme {
ProvideWindowInsets {
val systemUiController = rememberSystemUiController()
SideEffect {
systemUiController.setSystemBarsColor(Color.Transparent,
darkIcons = false)
}
val navController = rememberNavController()
val coroutineScope = rememberCoroutineScope()
val navBackStackEntry by
navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
Scaffold() { innerPadding ->
Box(modifier = Modifier.padding(innerPadding)) {
NavGraph(navController = navController)
}
}
}
}
}
If you clone the compose samples from here https://github.com/android/compose-samples they show you this way to implement it
Before setContent{} on Activity onCreate() do:
WindowCompat.setDecorFitsSystemWindows(window, false)
Related
I have an Activity and MyApp composable function. In the function I need to show either list with details or just list screen depending on the available width. How to determine available width for the content I want to show using Jetpack Compose? What is a good practice for this using Compose?
class MyActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ProjectTheme {
MyApp()
}
}
}
}
#Composable
fun MyApp() {
val isLarge: Boolean = ...// how to determine whether the available width is large enough?
if (isLarge) {
ListScreenWithDetails()
} else {
ListScreen()
}
}
#Composable
fun ProjectTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: #Composable () -> Unit) {
// ... project theme
}
You can use :
val configuration = LocalConfiguration.current
Then:
val isLargeWidth = configuration.screenWidthDp > 840
For a generic solution, consider using the Window Size Classes, see this and this for reference.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val windowSizeClass = calculateWindowSizeClass(this)
MyApp(windowSizeClass)
}
}
I'm using branch.io for handling deep links. Deep links can contain custom metadata in a form of JsonObject. The data can be obtained by setting up a listener, inside MainActivity#onStart() which is triggered when a link is clicked.
override fun onStart() {
super.onStart()
Branch
.sessionBuilder(this)
.withCallback { referringParams, error ->
if (error == null) {
val eventId = referringParams?.getString("id")
//Here I would like to navigate user to event screen
} else {
Timber.e(error.message)
}
}
.withData(this.intent?.data).init()
}
When I retrieve eventId from referringParams I have to navigate the user to the specific event. When I was using Navigation components with fragments I could just do:
findNavController(R.id.navHost).navigate("path to event screen")
But with compose is different because I can't use navController outside of Composable since its located in MainActivity#onCreate()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
//I cant access navController outside of composable function
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "HomeScreen",
) {
}
}
}
My question is, how can I navigate the user to a specific screen from MainActivity#onStart() when using jetpack compose navigation
rememberNavController has pretty simple implementation: it creates NavHostController with two navigators, needed by Compose, and makes sure it's restored on configuration change.
Here's how you can do the same in your activity, outside of composable scope:
private lateinit var navController: NavHostController
private val navControllerBundleKey = "navControllerBundleKey"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
navController = NavHostController(this).apply {
navigatorProvider.addNavigator(ComposeNavigator())
navigatorProvider.addNavigator(DialogNavigator())
}
savedInstanceState
?.getBundle(navControllerBundleKey)
?.apply(navController::restoreState)
setContent {
// pass navController to NavHost
}
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putBundle(navControllerBundleKey, navController.saveState())
super.onSaveInstanceState(outState)
}
I've a requirement to create a navigation where the flow is completely depends on the API response. Sometimes it could be A->B->C->D->E or B->C->D->E or A->B->C->E. Already implemented the same stack management with ArrayDeque which is working fine. But want to replace with navigation compose.
I know we can change the startDestination in navigation graph. Is it possible in jetpack compose? Also, I want to make it from ViewModel.
It should be easy in Compose.
Haven't tested this, but this mostly would work.
MainActivity.kt
override fun onCreate(
savedInstanceState: Bundle?,
) {
super.onCreate(savedInstanceState)
setContent {
MyTheme {
MyApp(
finishActivity = {
finish()
},
)
}
}
}
MyApp.kt
#Composable
fun MyNavGraph(
finishActivity: () -> Unit,
) {
MyAppView(
activityViewModel = activityViewModel,
)
}
MyNavGraph.kt
#Composable
fun MyNavGraph(
activityViewModel: MainActivityViewModel,
) {
val navHostController = rememberNavController()
NavHost(
navController = navHostController,
startDestination = activityViewModel.getStartDestination(),
) {
composable(
route = Screen.Home.name,
) {
HomeScreen(
activityViewModel = activityViewModel,
)
}
// Other composables
}
}
activityViewModel.getStartDestination() would make a network call a get the starting destination.
Add a comment if there are any issues with this.
I use LazyColumn inside BottomSheetDialogFragment, but if to scroll LazyColumn list UP then Bottom Sheet Dialog scrolls instead of LazyColumn list. Seems like BottomSheetDialogFragment intercepts user touch input.
That's how it looks:
How to properly use LazyColumn inside BottomSheetDialogFragment?
MyBottomSheetDialogFragment.kt:
class MyBottomSheetDialogFragment : BottomSheetDialogFragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setContent {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text("Header", color = Color.Black)
LazyColumn(
Modifier
.weight(1f)
.fillMaxWidth()) {
items(100) {
Text("Item $it", Modifier.fillMaxWidth(), Color.Black)
}
}
}
}
}
}
}
And show it using this code:
MyBottomSheetDialogFragment().show(activity.supportFragmentManager, null)
When we used the XML RecyclerView list, to fix this issue we had to wrap the RecyclerView list with NestedScrollView like described here, but how to fix it with Jetpack Compose?
Since compose 1.2.0-beta01 problem can be solved by using
rememberNestedScrollInteropConnection:
Modifier.nestedScroll(rememberNestedScrollInteropConnection())
In my case BottomSheetDialogFragment is standard View and it has ComposeView with id container. In onViewCreated I do:
binding.container.setContent {
AppTheme {
Surface(
modifier = Modifier.nestedScroll(rememberNestedScrollInteropConnection())
) {
LazyColumn {
// ITEMS
}
}
}
}
And now the list is scrolling in a correct way.
You might give a try to this https://gist.github.com/chrisbanes/053189c31302269656c1979edf418310.
This is a workaround for https://issuetracker.google.com/issues/174348612, which means that nested scrolling layouts in Compose do not work as nested scrolling children in the view system.
Sample usage in your case :
class MyBottomSheetDialogFragment : BottomSheetDialogFragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setContent {
Surface(
modifier = Modifier.nestedScroll(rememberViewInteropNestedScrollConnection())
){
LazyColumn(
Modifier
.weight(1f)
.fillMaxWidth()) {
items(100) {
Text("Item $it", Modifier.fillMaxWidth(), Color.Black)
}
}
}
}
}
}
}
If you’re using compose within the activity that launched the bottom sheet dialog fragment, you might be better off simply sticking with a purely compose implementation and leveraging the compose equivalent bottom sheet component: ModalBottomSheetLayout
https://developer.android.com/reference/kotlin/androidx/compose/material/package-summary#ModalBottomSheetLayout(kotlin.Function1,androidx.compose.ui.Modifier,androidx.compose.material.ModalBottomSheetState,androidx.compose.ui.graphics.Shape,androidx.compose.ui.unit.Dp,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,kotlin.Function0)
I found an excellent answer to this issue. We can use Jetpack Compose bottom sheet over Android view using Kotlin extension.
More details about how it works are here.
Here is all code we need:
// Extension for Activity
fun Activity.showAsBottomSheet(content: #Composable (() -> Unit) -> Unit) {
val viewGroup = this.findViewById(android.R.id.content) as ViewGroup
addContentToView(viewGroup, content)
}
// Extension for Fragment
fun Fragment.showAsBottomSheet(content: #Composable (() -> Unit) -> Unit) {
val viewGroup = requireActivity().findViewById(android.R.id.content) as ViewGroup
addContentToView(viewGroup, content)
}
// Helper method
private fun addContentToView(
viewGroup: ViewGroup,
content: #Composable (() -> Unit) -> Unit
) {
viewGroup.addView(
ComposeView(viewGroup.context).apply {
setContent {
BottomSheetWrapper(viewGroup, this, content)
}
}
)
}
#OptIn(ExperimentalMaterialApi::class)
#Composable
private fun BottomSheetWrapper(
parent: ViewGroup,
composeView: ComposeView,
content: #Composable (() -> Unit) -> Unit
) {
val TAG = parent::class.java.simpleName
val coroutineScope = rememberCoroutineScope()
val modalBottomSheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
var isSheetOpened by remember { mutableStateOf(false) }
ModalBottomSheetLayout(
sheetBackgroundColor = Color.Transparent,
sheetState = modalBottomSheetState,
sheetContent = {
content {
// Action passed for clicking close button in the content
coroutineScope.launch {
modalBottomSheetState.hide() // will trigger the LaunchedEffect
}
}
}
) {}
BackHandler {
coroutineScope.launch {
modalBottomSheetState.hide() // will trigger the LaunchedEffect
}
}
// Take action based on hidden state
LaunchedEffect(modalBottomSheetState.currentValue) {
when (modalBottomSheetState.currentValue) {
ModalBottomSheetValue.Hidden -> {
when {
isSheetOpened -> parent.removeView(composeView)
else -> {
isSheetOpened = true
modalBottomSheetState.show()
}
}
}
else -> {
Log.i(TAG, "Bottom sheet ${modalBottomSheetState.currentValue} state")
}
}
}
}
I use the navigation of official example OwlApp as my main navigation system, and I want to achieve the camerax in my project. But there is no easy way to combine camera in compose, so I think if have a way to let a activity which have a camera screen act as a compose destination, then I can navigate it normally as the app does.
You could handle this by passing a lambda into the composable function and implementing the logic outside of the composable function (inside your activity).
class MainActivity : AppCompatActivity() {
lateinit var onCameraClick : () -> Unit
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
SampleTheme {
Surface(color = MaterialTheme.colors.background) {
MainView(onCameraClick = onCameraClick)
}
}
}
onCameraClick = {
// Camera logic inside activity scope.
}
}
}
#Composable
fun MainView(onCameraClick : () -> Unit) {
Button(onClick = onCameraClick) {
Text(text = "Camera")
}
}