I use the library from the Accompanist Jetpack Navigation Computer Animation, when I press the back button, the animation I prescribed is not performed, but some other strange one.
Video with animation
AnimatedNavHost(navController = navController,
startDestination = categoryScreenRoute,
enterTransition = {
slideIntoContainer(
AnimatedContentScope.SlideDirection.Left, animationSpec = tween(speedAnimation)
)
},
exitTransition = {
slideOutOfContainer(
AnimatedContentScope.SlideDirection.Left, animationSpec = tween(speedAnimation)
)
},
popEnterTransition = {
slideIntoContainer(
AnimatedContentScope.SlideDirection.Right, animationSpec = tween(speedAnimation)
)
},
popExitTransition = {
slideOutOfContainer(
AnimatedContentScope.SlideDirection.Right, animationSpec = tween(speedAnimation)
)
})
{
composable(route = categoryScreenRoute) {
CategoryScreen(navController = navController, viewModel = viewModel)
}
composable(route = itemsScreenRoute) {
ItemsScreen(navController = navController, viewModel = viewModel)
}
}
I tried to prescribe an animation for each composable function, completely removed the animation of the transition back.
define an extension method:
fun NavGraphBuilder.setComposable(
route: String,
arguments: List<NamedNavArgument> = emptyList(),
deepLinks: List<NavDeepLink> = emptyList(),
content: #Composable AnimatedVisibilityScope.(NavBackStackEntry) -> Unit){
return composable(
route = route,
arguments = arguments,
deepLinks = deepLinks,
enterTransition = {
slideIntoContainer(
AnimatedContentScope.SlideDirection.Left, animationSpec = tween(speedAnimation)
)
},
exitTransition = {
slideOutOfContainer(
AnimatedContentScope.SlideDirection.Left, animationSpec = tween(speedAnimation)
)
},
popEnterTransition = {
slideIntoContainer(
AnimatedContentScope.SlideDirection.Right, animationSpec = tween(speedAnimation)
)
},
popExitTransition = {
slideOutOfContainer(
AnimatedContentScope.SlideDirection.Right, animationSpec = tween(speedAnimation)
)
},
content = content
)
}
The method of use is as follows:
AnimatedNavHost(navController = navController, startDestination = categoryScreenRoute) {
setComposable(route = categoryScreenRoute) {
CategoryScreen(navController = navController, viewModel = viewModel)
}
setComposable(route = itemsScreenRoute) {
ItemsScreen(navController = navController, viewModel = viewModel)
}
}
Your CategoryScreen Example:
#Composable
fun CategoryScreen(viewModel: CategoryViewModel = hiltViewModel(), navController: NavController) {
val categoryState by remember { viewModel.getAllCategory() }
Box(modifier = Modifier.fillMaxSize()) {
when (categoryState) {
is ApiResult.Error -> {
Text(
text = (categoryState as ApiResult.Error<List<Category>>).message.toString(),
modifier = Modifier.padding(16.dp),
fontSize = 24.sp,
fontWeight = FontWeight.W600
)
}
is ApiResult.Exception -> {
Text(
text = (categoryState as ApiResult.Exception<List<Category>>).e,
modifier = Modifier.padding(16.dp),
fontSize = 24.sp,
fontWeight = FontWeight.W600
)
}
is ApiResult.Success -> {
val list = (categoryState as ApiResult.Success).data
LazyVerticalGrid(columns = GridCells.Fixed(2), contentPadding = PaddingValues(16.dp)) {
items(list) { category ->
CategoryItem(category) {
viewModel.selectCategory(category)
navController.navigateToItemsScreen()
}
}
}
}
}
}
}
Related
It's not recognizing the parameter and invocations related to the drawer
#Composable
fun PantallaPrincipal(userViewModel: UserViewModel) {
val navController = rememberNavController()
val scaffoldState = rememberScaffoldState(rememberDrawerState(initialValue = DrawerValue.Closed))
val scope= rememberCoroutineScope()
Scaffold(scaffoldState = scaffoldState,
topBar= { TopBar(scope,scaffoldState=scaffoldState)},
bottomBar= { BottomBar(navController=navController)},
drawerContent = { Drawer(scope=scope,scaffoldState=scaffoldState,navController=navController)},
floatingActionButton = {FloatingBottom(navController=navController)}
){
Navigation(navController = navController, userViewModel = userViewModel)
}
}
#Composable
fun DrawerItem (item: navigationDrawer, selected:Boolean, onclick:(navigationDrawer)->Unit) {
val itemBackground=if(selected) Color.Gray else Color.Transparent
Row(modifier = Modifier
.fillMaxWidth()
.height(45.dp)
.background(itemBackground)
.padding(10.dp)
.clickable { onclick(item) },
verticalAlignment = Alignment.CenterVertically
) {
Image(
imageVector = item.icono,
contentDescription = item.texto,
colorFilter = ColorFilter.tint(Color.Black),
modifier = Modifier
.height(35.dp)
.width(35.dp)
)
Spacer(modifier = Modifier.width(7.dp))
Text(text = item.texto,
fontSize = 18.sp,
color = Color.Black
)
}
}
#Composable
fun Drawer(scope: CoroutineScope, scaffoldState: ScaffoldState, navController: NavHostController) {
val listItems = listOf<navigationDrawer>(navigationDrawer.Inicio, navigationDrawer.Favoritos, navigationDrawer.Perfil)
val rutaActual = navController.currentBackStackEntry?.destination?.route
Column(modifier = Modifier
.background(Color.White)
){
Image(imageVector = Icons.Default.Face,
contentDescription = "logo",
modifier = Modifier
.background(Color.White)
.height(100.dp)
.fillMaxWidth()
.padding(10.dp)
)
Spacer(modifier = Modifier
.fillMaxWidth()
.height(5.dp)
)
listItems.forEach{
DrawerItem(item = it,
selected = (rutaActual==it.ruta),
onclick = {navController.navigate(it.ruta)
scope.launch {scaffoldState.drawerState.close()}
})
}
}
}
Important: You must use scaffold as ModalNavigationDrawer content.
Material 3 Example - Navigation Drawer with scaffold in compose
val scope = rememberCoroutineScope()
val drawerState = rememberDrawerState(DrawerValue.Closed)
val items = listOf(Icons.Default.Close, Icons.Default.Clear, Icons.Default.Call)
val selectedItem = remember { mutableStateOf(items[0]) }
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
ModalDrawerSheet {
Spacer(Modifier.height(12.dp))
items.forEach { item ->
NavigationDrawerItem(
icon = { Icon(item, contentDescription = null) },
label = { Text(item.name) },
selected = item == selectedItem.value,
onClick = {
scope.launch { drawerState.close() }
selectedItem.value = item
},
modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
)
}
}
},
content = {
Scaffold(
topBar = { TopBar(preesOnDrawerIcon = { scope.launch { drawerState.open() } }) },
bottomBar = {},
snackbarHost = {},
content = {},
)
}
)
#OptIn(ExperimentalMaterial3Api::class)
#Composable
fun TopBar(pressOnDrawer: () -> Unit){
...
}
In Material 3 it's called ModalNavigationDrawer. You can use it using a ModalDrawerSheet and ModalDrawerItems like in this example taken from the docs:
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
ModalDrawerSheet {
Spacer(Modifier.height(12.dp))
items.forEach { item ->
NavigationDrawerItem(
icon = { Icon(item, contentDescription = null) },
label = { Text(item.name) },
selected = item == selectedItem.value,
onClick = {
scope.launch { drawerState.close() }
selectedItem.value = item
},
modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
)
}
}
},
I'm trying to hide a reusable floating action button (on click - then show it again when the keyboard is not visible) and request focus for a text field I made but I'm not sure what I'm doing wrong. I used focusRequester.requestFocus() and AnimatedVisibility but nothing happens.
#file:OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MySearchViewDemoTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
MainScreen(showSearchButton = true)
}
}
}
}
sealed interface Shoes { ... }
// Reusable component
#Composable
fun MyReusableScaffold(scaffoldTitle: String,
scaffoldFab: #Composable () -> Unit,
scaffoldContent: #Composable (contentPadding: PaddingValues) -> Unit) {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
LargeTopAppBar(
title = { Text(text = scaffoldTitle) },
scrollBehavior = scrollBehavior
)
},
floatingActionButton = scaffoldFab,
content = { contentPadding ->
scaffoldContent(contentPadding = contentPadding)
}
)
}
// Reusable component
#Composable
fun MyExtendedFAB(expandedFab: Boolean,
onFabClick: () -> Unit
) {
ExtendedFloatingActionButton(
text = { Text(text = "Search") },
icon = { Icon(Icons.Rounded.Search, "") },
onClick = onFabClick,
expanded = expandedFab
)
}
// Reusable component
#Composable
fun <T> MyLazyColumn(modifier: Modifier,
lazyItems: Array<T>,
onClickItem: (T) -> Unit,
item: #Composable RowScope.(item: T) -> Unit,
listState: LazyListState
) {
var mText by remember { mutableStateOf("") }
val focusRequester = remember { FocusRequester() }
val focusManager = LocalFocusManager.current
val keyboardController = LocalSoftwareKeyboardController.current
Row {
LazyColumn(
modifier = modifier,
state = listState
) {
item {
if (showNotice){
Text(
text = textNotice,
modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
fontStyle = FontStyle.Italic,
style = MaterialTheme.typography.bodyLarge
)
}
}
item {
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester)
value = mText,
onValueChange = {
mText = it
},
label = {
Text(text = "Search")
},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
keyboardActions = KeyboardActions(
onSearch = {
keyboardController?.hide()
focusManager.clearFocus()
}),
)
}
items(lazyItems) { choice ->
Row(modifier = Modifier
.fillMaxWidth()
.clickable {
onClickItem(choice)
}) {
item(choice)
}
}
}
}
}
#Composable
fun MainScreen(
showSearchButton: Boolean
) {
val keyboardController = LocalSoftwareKeyboardController.current
val focusManager = LocalFocusManager.current
val listState = rememberLazyListState()
val expandedFab by remember {
derivedStateOf {
listState.firstVisibleItemIndex == 0
}
}
val focusRequester = remember { FocusRequester() }
MyReusableScaffold(
scaffoldTitle = "Shoes",
scaffoldIcon = {},
scaffoldFab = {
AnimatedVisibility(visible = showSearchButton) {
MyExtendedFAB(expandedFab) {
focusRequester.requestFocus()
}
}
},
scaffoldContent = { HomeScreenContent(listState = listState, contentPadding = it) }
)
}
#Composable
fun HomeScreenContent(
modifier: Modifier = Modifier,
listState: LazyListState,
contentPadding: PaddingValues = PaddingValues()
) {
val keyboardController = LocalSoftwareKeyboardController.current
val focusManager = LocalFocusManager.current
val shoeItems = remember { arrayOf( ... ) }
MyLazyColumn(
modifier = Modifier.padding(contentPadding),
lazyItems = shoeItems,
onClickItem = {},
item = { ReusableTitleSubtitle(it) },
listState = listState
)
}
}
I have a list of products. On the ProductsScreen I have some filter options, search box, also filter chips :
I'm using paging 3 library for the pagination. I want to scroll to the top when the search query or selected filter chip has changed. Here is my code :
ProductScreen
#Composable
fun ProductsScreen(
modifier: Modifier = Modifier,
navController: NavController,
viewModel: ProductsScreenViewModel = hiltViewModel(),
onProductItemClick: (String) -> Unit = {},
onProductFilterButtonClick: (ProductsFilterPreferences) -> Unit = {},
) {
//products
val products = viewModel.products.collectAsLazyPagingItems()
//load state
val isLoading = products.loadState.refresh is LoadState.Loading
val scrollToTop = viewModel.scrollToTop.observeAsState()
val scaffoldState = rememberScaffoldState()
val listState = rememberLazyListState()
val scope = rememberCoroutineScope()
val query by viewModel.query.collectAsState()
val context = LocalContext.current
//not working
LaunchedEffect(key1 = Unit) {
scrollToTop.value?.getContentIfNotHandled()?.let { scroll ->
if (scroll) {
listState.scrollToItem(0)
}
}
}
RubiBrandsScaffold(
modifier = modifier,
scaffoldState = scaffoldState,
topBar = {
ProductsScreenToolbar(
showFilterBadge = viewModel.showFilterBadge,
onFilterButtonClick = { onProductFilterButtonClick(viewModel.productsFilterPreferences.value) },
searchQuery = query,
onTrailingIconClick = {
viewModel.setQuery("")
},
onQueryChange = { query ->
viewModel.setQuery(query)
},
onSearchButtonClick = { query ->
viewModel.onSearch(query)
},
onChipClick = { chip ->
viewModel.onChipSelected(chip)
},
selectedChip = viewModel.selectedChip,
)
},
floatingActionButton = {
AnimatedVisibility(
visible = showFab,
enter = scaleIn(),
exit = scaleOut()
) {
FloatingActionButton(
backgroundColor = MaterialTheme.colors.primary,
onClick = {
scope.launch {
//working
listState.scrollToItem(0)
}
},
modifier = Modifier
.padding(
dimensionResource(id = R.dimen.dimen_16)
)
) {
Icon(
painter = painterResource(id = R.drawable.ic_up),
contentDescription = null,
tint = MaterialTheme.colors.onPrimary
)
}
}
}
) { paddingValues ->
SwipeRefresh(
indicator = { state, trigger ->
RubiBrandsSwipeRefreshIndicator(state = state, trigger = trigger)
}, state = swipeRefreshState, onRefresh = products::refresh
) {
LazyColumn(
state = listState,
modifier = Modifier.fillMaxSize()
) {
items(products) { product ->
product?.let {
ProductItem(
modifier = Modifier.clickable {
if (!isLoading) onProductItemClick(
product.id
)
},
childModifier = Modifier.shimmerModifier(isLoading),
productImage = product.productImage?.get(0),
productTitle = product.productTitle,
productCount = product.productCount,
productStatus = product.productStatus?.asString(context)
)
}
}
products.apply {
when {
loadState.source.refresh is LoadState.Loading -> {
items(10) {
ProductItem(
childModifier = Modifier.shimmerModifier(true),
productImage = null,
productTitle = "",
productCount = "",
productStatus = ""
)
}
}
loadState.source.append is LoadState.Loading -> {
item {
CircularProgressIndicator(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.wrapContentWidth(Alignment.CenterHorizontally)
)
}
}
loadState.source.refresh is LoadState.Error -> {
val e = products.loadState.refresh as LoadState.Error
item {
ErrorItem(
modifier = Modifier.fillParentMaxSize(),
message = e.error.localizedMessage
?: stringResource(id = R.string.something_went_wrong),
onRetryClick = products::retry
)
}
}
loadState.source.append is LoadState.Error -> {
val e = products.loadState.append as LoadState.Error
item {
ErrorItem(
modifier = Modifier
.fillParentMaxWidth()
.wrapContentHeight(),
message = e.error.localizedMessage
?: stringResource(id = R.string.something_went_wrong),
onRetryClick = products::retry
)
}
}
loadState.source.refresh is LoadState.NotLoading && loadState.source.refresh !is LoadState.Error && products.itemCount < 1 -> {
item {
RubiBrandsEmptyListView(modifier = Modifier.fillParentMaxSize())
}
}
}
}
}
}
}
}
Here is also my ProductsScreenViewModel :
#HiltViewModel
class ProductsScreenViewModel #Inject constructor(
private val productsScreenUseCase: ProductsScreenUseCase
) : ViewModel() {
var showFilterBadge by mutableStateOf(false)
private set
private val _query = MutableStateFlow<String>("")
val query: StateFlow<String> = _query
var selectedChip by mutableStateOf<ProductsChips>(ProductsChips.ALL)
private val _productsFilterPreferences = MutableStateFlow(ProductsFilterPreferences())
val productsFilterPreferences: StateFlow<ProductsFilterPreferences> = _productsFilterPreferences
private val _scrollToTop = MutableLiveData<Event<Boolean>>()
val scrollToTop: LiveData<Event<Boolean>> get() = _scrollToTop
val products =
_productsFilterPreferences.flatMapLatest { filterPreferences ->
showFilterBadge = filterPreferences.sort.value != null
|| filterPreferences.saleStatus.value != null
|| filterPreferences.stockStatus.value != null
|| filterPreferences.sku != null
|| filterPreferences.priceOptions.value != R.string.all
productsScreenUseCase.fetchProducts(params = filterPreferences)
}.cachedIn(viewModelScope)
fun setFilters(
filters: ProductsFilterPreferences
) {
_scrollToTop.value = Event(true)
_productsFilterPreferences.update {
it.copy(
sort = filters.sort,
state = filters.state,
saleStatus = filters.saleStatus,
stockStatus = filters.stockStatus,
sku = filters.sku,
priceOptions = filters.priceOptions
)
}
}
fun setQuery(query: String) {
if (query.isEmpty()) {
onSearch(query)
}
_query.value = query
}
fun onSearch(query: String) {
//if I press the search button I'm setting scroll to top to true
_scrollToTop.value = Event(true)
_productsFilterPreferences.update {
it.copy(query = query.trim().ifEmpty { null })
}
}
fun onChipSelected(chip: ProductsChips) {
selectedChip = chip
//if I change the chip I'm setting scroll to top to true
_scrollToTop.value = Event(true)
_productsFilterPreferences.update {
it.copy(state = chip.value)
}
}
}
There is a related question about that but it is valid for XML.
So whenever the search query or selected chip has changed I'm setting the scrollToTop value to true and then observing it from my ProductsScreen composable. But it is not working.
You can try using the LaunchedEffect and the scrollState of Lazycolumn to scroll it to top.
val scrollState = rememberLazyListState()
LazyColumn( state = scrollState){}
LaunchedEffect(true) {
scrollState.animateScrollToItem(0)
}
I've just put the LaunchEffect within the if block and the issue have been resolved. Here is the code:
scrollToTop?.getContentIfNotHandled()?.let { scroll ->
if (scroll) {
LaunchedEffect(key1 = Unit) {
listState.scrollToItem(0)
}
}
}
Link to the video of the problem: Video Link`
Sometimes many notes delete at once and the add functionality is not working. When I swipe the Lazy Column does not recompose and put everything in order again.
This is my code:
NoteButton:
#Composable
fun NoteButton(
modifier: Modifier = Modifier,
text: String,
enabled: Boolean = true,
shape: Shape = CircleShape,
onClick: () -> Unit
) {
Button(
modifier = modifier,
enabled = enabled,
shape = shape,
onClick = onClick
) {
Text(text)
}
}
NoteInputText:
#OptIn(ExperimentalComposeUiApi::class)
#Composable
fun NoteInputText(
modifier: Modifier = Modifier,
text: String,
label: String,
maxLines: Int = 1,
onTextChange: (String) -> Unit,
onImeAction: () -> Unit = {},
keyboardOptions: KeyboardOptions = KeyboardOptions.Default.copy(
imeAction = if (maxLines == 1) ImeAction.Done else ImeAction.None
)
) {
val keyboardController = LocalSoftwareKeyboardController.current
TextField(
modifier = modifier,
value = text,
onValueChange = onTextChange,
maxLines = maxLines,
label = { Text(text = label) },
keyboardOptions = keyboardOptions,
keyboardActions = KeyboardActions(
onDone = {
onImeAction()
keyboardController?.hide()
}
)
)
}
NoteItem:
private const val TAG = "NoteItem"
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun NoteItem(modifier: Modifier = Modifier, note: Note, onSwipe: (Note) -> Unit) {
val dismissState = rememberDismissState(initialValue = DismissValue.Default) {
if (it == DismissValue.DismissedToStart) {
Log.d(TAG, "NoteItem: ${note.id}")
onSwipe(note)
}
true
}
SwipeToDismiss(
state = dismissState,
directions = setOf(DismissDirection.EndToStart),
background = {}
) {
Card(
modifier = modifier
.padding(5.dp)
.fillMaxWidth(),
shape = RoundedCornerShape(topEnd = 20.dp, bottomEnd = 20.dp),
elevation = 5.dp
) {
Column(modifier = Modifier.padding(horizontal = 15.dp, vertical = 5.dp)) {
Text(text = note.title, style = MaterialTheme.typography.h5)
Text(text = note.description, style = MaterialTheme.typography.body1)
}
}
}
}
NoteList:
private const val TAG = "NoteList"
#Composable
fun NoteList(modifier: Modifier = Modifier, onSwipe: (Note) -> Unit) {
val noteViewModel: NoteViewModel = viewModel()
val notes = noteViewModel.getAllNotes()
if(notes.isNotEmpty()) {
LazyColumn(modifier = modifier.padding(5.dp)) {
items(notes) { note ->
NoteItem(note = note, onSwipe = { onSwipe(note) })
}
}
}
}
Note:
data class Note(
val id: Long = assignIdForNote(),
val title: String,
val description: String,
val entryDate: LocalDateTime = LocalDateTime.now()
)
fun getNoteData() = mutableListOf(
Note(title = "A good day", description = "We went on a vacation by the lake"),
Note(title = "Android Compose", description = "Working on Android Compose course today"),
Note(title = "Keep at it...", description = "Sometimes things just happen"),
Note(title = "A movie day", description = "Watching a movie with family today"),
Note(title = "A movie day", description = "Watching a movie with family today"),
Note(title = "A movie day", description = "Watching a movie with family today"),
Note(title = "A movie day", description = "Watching a movie with family today"),
Note(title = "A movie day", description = "Watching a movie with family today"),
Note(title = "A movie day", description = "Watching a movie with family today"),
Note(title = "A movie day", description = "Watching a movie with family")
)
NoteViewModel:
class NoteViewModel: ViewModel() {
private var notesList = mutableStateListOf<Note>()
init {
notesList.addAll(getNoteData())
}
fun addNote(note: Note) {
notesList.add(note)
}
fun deleteNote(note: Note) {
notesList.removeIf { it.id == note.id }
}
fun getAllNotes() = notesList
}
Navigation:
#Composable
fun Navigation() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = Screen.NotesScreen.name) {
composable(Screen.NotesScreen.name) {
NotesScreen(navController)
}
composable(Screen.AddScreen.name) {
AddScreen(navController)
}
}
}
AddScreen:
private const val TAG = "AddScreen"
#Composable
fun AddScreen(navController: NavController) {
val noteViewModel: NoteViewModel = viewModel()
var title by remember {
mutableStateOf("")
}
var description by remember {
mutableStateOf("")
}
Log.d(TAG, "AddScreen: Title: $title")
Log.d(TAG, "AddScreen: Description: $description")
Column {
TopAppBar(title = { Text(text = stringResource(id = R.string.app_name)) },
navigationIcon = {
IconButton(onClick = { navController.popBackStack() }) {
Icon(imageVector = Icons.Filled.ArrowBack, contentDescription = "Go back")
}
})
Column(
modifier = Modifier
.padding(10.dp)
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
NoteInputText(
modifier = Modifier.background(Color.White),
text = title,
label = "Title",
onTextChange = {
title = it
}
)
NoteInputText(
modifier = Modifier.background(Color.White),
text = description,
label = "Description",
onTextChange = {
description = it
}
)
NoteButton(text = "Add") {
if(title.isNotBlank() && description.isNotBlank()) {
Log.d(TAG, "AddScreen: Cool, $title, $description")
noteViewModel.addNote(Note(title = title, description = description))
navController.popBackStack()
}
}
}
}
}
NotesScreen:
private const val TAG = "NotesScreen"
#Composable
fun NotesScreen(navController: NavController) {
val noteViewModel: NoteViewModel = viewModel()
val notes = noteViewModel.getAllNotes()
Column {
TopAppBar(
title = { Text(text = stringResource(id = R.string.app_name)) },
actions = {
IconButton(onClick = { navController.navigate(Screen.AddScreen.name) }) {
Icon(imageVector = Icons.Filled.Add, contentDescription = "Add")
}
}
)
if (notes.isNotEmpty()) {
NoteList(onSwipe = {
noteViewModel.deleteNote(it)
})
} else {
Text(
modifier = Modifier.padding(10.dp),
text = "Click on the + icon to add a note",
style = MaterialTheme.typography.h5
)
}
}
}
Screen:
enum class Screen {
NotesScreen, AddScreen
}
Util:
private var previousId = 0L
fun assignIdForNote() = previousId++
MainActivity:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
NoteAppTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
Navigation()
}
}
}
}
}
#Preview(showBackground = true)
#Composable
fun DefaultPreview() {
NoteAppTheme {
Navigation()
}
}
The reason your add functionality is not working is you were operating on a independent NoteViewModel on AddScreen, which is different with NotesScreen's NoteViewModel. You can check both noteViewModel's hashCode to verify.
This is my Modal drawer when open
Code to implement it
#ExperimentalMaterialApi
#Composable
private fun TutorialContent() {
ModalDrawerComponent()
}
#ExperimentalMaterialApi
#Composable
private fun ModalDrawerComponent() {
val drawerState = rememberDrawerState(DrawerValue.Closed)
val coroutineScope = rememberCoroutineScope()
val openDrawer: () -> Unit = { coroutineScope.launch { drawerState.open() } }
val closeDrawer: () -> Unit = { coroutineScope.launch { drawerState.close() } }
var selectedIndex by remember { mutableStateOf(0) }
ModalDrawer(
drawerState = drawerState,
drawerContent = {
ModalDrawerContentHeader()
Divider()
ModelDrawerContentBody(
selectedIndex,
onSelected = {
selectedIndex = it
},
closeDrawer = closeDrawer
)
},
content = {
Column(modifier = Modifier.fillMaxSize()) {
ModalDrawerTopAppBar(openDrawer)
ModalContent(openDrawer)
}
}
)
}
#Composable
fun ModalDrawerTopAppBar(openDrawer: () -> Unit) {
TopAppBar(
title = {
Text("ModalDrawer")
},
navigationIcon = {
IconButton(onClick = openDrawer) {
Icon(
imageVector = Icons.Filled.Menu,
contentDescription = null
)
}
},
actions = {}
)
}
#Composable
fun ModalDrawerContentHeader() {
Column(
modifier = Modifier
.fillMaxWidth()
.height(180.dp)
.padding(20.dp)
) {
Image(
modifier = Modifier
.size(60.dp)
.clip(CircleShape),
painter = painterResource(id = R.drawable.avatar_1_raster),
contentDescription = null
)
Spacer(modifier = Modifier.weight(1f))
Text(text = "Android", fontWeight = FontWeight.Bold, fontSize = 22.sp)
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Text(text = "android#android.com")
Spacer(modifier = Modifier.weight(1f))
Icon(imageVector = Icons.Filled.ArrowDropDown, contentDescription = null)
}
}
}
}
#Composable
fun ModelDrawerContentBody(
selectedIndex: Int,
onSelected: (Int) -> Unit,
closeDrawer: () -> Unit
) {
Column(modifier = Modifier.fillMaxWidth()) {
modalDrawerList.forEachIndexed { index, pair ->
val label = pair.first
val imageVector = pair.second
DrawerButton(
icon = imageVector,
label = label,
isSelected = selectedIndex == index,
action = {
onSelected(index)
}
)
}
}
}
#ExperimentalMaterialApi
#Composable
fun ModalContent(openDrawer: () -> Unit) {
LazyColumn {
items(userList) { item: String ->
ListItem(
modifier = Modifier.clickable {
openDrawer()
},
icon = {
Image(
modifier = Modifier
.size(40.dp)
.clip(CircleShape),
painter = painterResource(id = R.drawable.avatar_1_raster),
contentDescription = null
)
},
secondaryText = {
Text(text = "Secondary text")
}
) {
Text(text = item, fontSize = 18.sp)
}
}
}
}
val modalDrawerList = listOf(
Pair("My Files", Icons.Filled.Folder),
Pair("Shared with Me", Icons.Filled.People),
Pair("Starred", Icons.Filled.Star),
Pair("Recent", Icons.Filled.AccessTime),
Pair("Offline", Icons.Filled.OfflineShare),
Pair("Uploads", Icons.Filled.Upload),
Pair("Backups", Icons.Filled.CloudUpload),
)
What i actually desire is to have transparent status over the drawer as in the image below