I have to create dynamic ScrollableTabRow where i will intially have 3 tab but i can add multiple tab at the runtime also all the newly add tab will have close Icon to remove the tab. and also each new Screen will have seperate BottomNavigation(each screen will have 6 to 7 bottom Navitems scrollable). how can i make this efficiently
i am facing one issue it opening the same instance on adding the Tab.
@Composable
fun HomeScreenWithScrollableTabRow(
modifier: Modifier= Modifier,
tabsViewModel: TabLayoutVM = viewModel()){
val scope = rememberCoroutineScope()
Column(
modifier = modifier.fillMaxSize()) {
ScrollableTabRow(
edgePadding = 4.dp,
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.onSurface),
selectedTabIndex = tabsViewModel.selectedTabIndex.intValue) {
tabsViewModel.tabs.forEachIndexed{index, title ->
Tab(
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
.padding(horizontal = 10.dp),
selected = tabsViewModel.selectedTabIndex.intValue == index,
onClick = {
tabsViewModel.selectTab(index)
},
text = {
Row(
verticalAlignment = Alignment.CenterVertically
){
Text(
title,
fontWeight = FontWeight.Bold
)
if (index >= 3) {
Spacer(modifier = Modifier.width(1.dp))
IconButton(
onClick = {
tabsViewModel.removeTab(index)
}){
Icon(Icons.Default.Close, contentDescription = "Close Icon")
}
}
}
}
)
}
}
TabContent(
tabs = tabsViewModel.tabs,
selectedIndex = tabsViewModel.selectedTabIndex.intValue,
onAddTab = { newTabName ->
tabsViewModel.addTab(newTabName)
scope.launch {
delay(100)
tabsViewModel.selectTab(tabsViewModel.tabs.size.minus(1))
}
},
onPress = {
tabsViewModel.currentSelectedDeviceForGraph(it)
},
currentSelectedDevice = tabsViewModel.currentSelectedDevice.value,
tabsViewModel = tabsViewModel
)
}
}
@Composable
fun TabContent(
selectedIndex:Int,
tabs: List<String>,
onPress:(String)->Unit,
onAddTab: (String) -> Unit,
currentSelectedDevice:String?,
tabsViewModel: TabLayoutVM){
Box(
modifier = Modifier
.fillMaxSize()
.padding(8.dp),
contentAlignment = Alignment.TopStart){
when (selectedIndex) {
0 -> ScannerScreen(currentSelectedDevice,onPress,onAddTab)
1 -> BondedScreen()
2 -> AdvertiserScreen()
else -> {
TabScreen()
}
}
}
}
class TabLayoutVM: ViewModel() {
private val _tabs = mutableStateListOf("Scanner", "Bonded", "Advertiser")
val tabs: List<String> = _tabs
private var _selectedTabIndex = mutableIntStateOf(0)
val selectedTabIndex: MutableIntState = _selectedTabIndex
var currentSelectedDevice = mutableStateOf<String?>(null)
private set
fun addTab(deviceName:String) {
_tabs.add(deviceName)
}
fun removeTab(index: Int) {
if (_tabs.size > 3) {
_tabs.removeAt(index)
if (selectedTabIndex.intValue >= _tabs.size) {
selectedTabIndex.intValue = _tabs.size - 1
}
}
}
fun selectTab(index: Int) {
selectedTabIndex.intValue = index
}
fun currentSelectedDeviceForGraph(device: String?){
currentSelectedDevice.value = device
}
@Composable
fun TabScreen(deviceName:String, genericVM: GenericVM= viewModel()) {
val bottomNavIndex = genericVM.bottomNavIndex.value
val tabs = listOf("GPS", "GSM", "CAN", "ACC", "DEVICE")
Scaffold(
topBar = {
//Add Top App Bar inside Tab Layout if have
},
bottomBar = {
ScrollableTabRow(
selectedTabIndex = bottomNavIndex,
modifier = Modifier.fillMaxWidth(),
edgePadding = 4.dp
) {
tabs.forEachIndexed { index, title ->
Tab(
text = { Text(title) },
selected = bottomNavIndex == index,
onClick = { genericVM.setBottomNavIndex(index)},
icon = {
when (index) {
0 -> Icon(imageVector = Icons.Default.GpsFixed, contentDescription = null)
1 -> Icon(imageVector = Icons.Default.SettingsInputAntenna, contentDescription = null)
2 -> Icon(imageVector = Icons.Default.Settings, contentDescription = null)
3 -> Icon(imageVector = Icons.Default.Lock, contentDescription = null)
4 -> Icon(imageVector = Icons.Default.Devices, contentDescription = null)
}
}
)
}
}
}
) { paddingValues ->
Box(
modifier = Modifier
.padding(paddingValues)
.fillMaxSize(),
contentAlignment = Alignment.Center
) {
when (bottomNavIndex) {
0 -> Text("${tabs[0]} Content")
1 -> Text("${tabs[1]} Content")
2 -> Text("${tabs[2]} Content")
3 -> Text("${tabs[3]} Content")
4 -> Text("${tabs[4]} Content")
}
}
}
}
Just move your tabs
to the view model:
private val _tabs: MutableStateFlow<List<String>> =
MutableStateFlow(listOf("GPS", "GSM", "CAN", "ACC", "DEVICE"))
val tabs = _tabs.asStateFlow()
fun addTab(tab: String) {
_tabs.update { it + tab }
}
fun removeTab(tab: String) {
_tabs.update { it - tab }
}
In TabScreen
you can then replace
val tabs = listOf("GPS", "GSM", "CAN", "ACC", "DEVICE")
by
val tabs by genericVM.tabs.collectAsStateWithLifecycle()
That’s it.
If you want to adjust the number of tabs call the view model functions accordingly:
genericVM.addTab("new Tab")
genericVM.removeTab("GPS")
Just make sure that each screen gets its own view model instance (maybe by using the navigation library to navigate to each screen as a separate destination. See the documentation for more). Also you might want to assign icons to the tabs not by their index because the index will change over time when tabs are added and removed. You should use a unique identifier, like the tab’s name instead.
1