How to make Dynamic ScrollableTabRow in Jetpack compose

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")
            }
        }
    }
}

The issue you’re facing—where each new tab opens the same instance—might be due to how you’re managing the state and instantiating screens for each tab. Each time you create a new tab, you’re probably rendering the same screen without differentiating between them, leading to the same instance being reused.

Here are a few potential areas to improve the structure and efficiency of your tab management:

1. Key Issue: Managing Screen Instances for Each Tab

The primary issue seems to be that when you’re adding new tabs, each tab is not properly differentiated, so the same screen is being reused. You can resolve this by ensuring each newly added tab has a unique screen instance, potentially based on its unique content or parameters.

2. Dynamic Tab and Screen Management

You’ll want to modify how you manage and instantiate screens for each tab. Instead of a fixed switch statement, map each tab dynamically to its own content. Here’s how to update your code:

Updated TabContent with Dynamic Screen Management:

You can use a dynamic approach with a mapping to handle the different content for each tab.

@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
    ) {
        if (selectedIndex in tabs.indices) {
            when (selectedIndex) {
                0 -> ScannerScreen(currentSelectedDevice, onPress, onAddTab)
                1 -> BondedScreen()
                2 -> AdvertiserScreen()
                else -> TabScreen(deviceName = tabs[selectedIndex])
            }
        } else {
            Text("Invalid tab index")
        }
    }
}

3. Handle New Tabs

Ensure that each new tab has its own screen instance when created. Modify the TabScreen to accept unique parameters for each tab (e.g., the device name or any other relevant data):

@Composable
fun TabScreen(deviceName: String, genericVM: GenericVM = viewModel()) {
    val bottomNavIndex = genericVM.bottomNavIndex.value

    // Define your bottom navigation items
    val tabs = listOf("GPS", "GSM", "CAN", "ACC", "DEVICE")

    Scaffold(
        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
        ) {
            // Show different content based on selected bottom tab
            when (bottomNavIndex) {
                0 -> Text("${tabs[0]} Content for $deviceName")
                1 -> Text("${tabs[1]} Content for $deviceName")
                2 -> Text("${tabs[2]} Content for $deviceName")
                3 -> Text("${tabs[3]} Content for $deviceName")
                4 -> Text("${tabs[4]} Content for $deviceName")
            }
        }
    }
}

4. Managing Bottom Navigation for Each Tab

Each screen you create with a new tab can have its own BottomNavigation. To ensure that each tab has its own navigation state, ensure you are managing a unique bottomNavIndex per tab. This can be done by storing the bottom navigation state in your TabLayoutVM or a separate GenericVM.

Example ViewModel Update to Handle Bottom Navigation State:

class GenericVM : ViewModel() {
    // Use a mutable state to store the selected bottom navigation index
    private var _bottomNavIndex = mutableStateOf(0)
    val bottomNavIndex: State<Int> = _bottomNavIndex

    // Function to update the index when a bottom nav item is clicked
    fun setBottomNavIndex(index: Int) {
        _bottomNavIndex.value = index
    }
}

5. Optimization: Use Remember for Efficient Recomposition

When working with composable functions, ensure you use remember to optimize recomposition, especially when dealing with dynamic lists like tabs.

val tabs by remember { mutableStateListOf("Scanner", "Bonded", "Advertiser") }

Summary of Key Changes:

  1. Dynamic Tab and Screen Management: Update the tab content logic to dynamically map new tabs to unique content.
  2. Bottom Navigation State Management: Ensure each screen (tab) manages its own bottom navigation state.
  3. Efficient Recomposition: Use remember for managing state inside composables efficiently.

These adjustments should help ensure that each newly created tab gets its own unique screen instance, and you won’t see the issue of reusing the same instance for all tabs.