Search code examples
kotlincompose-desktop

Am I able to switch views using Kotlin Compose for Desktop Applications (and how)?


(After looking for an answer all of yesterday, I have decided to ask this question as I could not find it anywhere else.)

I have a small example app that I want to use in order to test the way that Compose works for Desktop Applications.

I have been able to create an application UI with the 'fun main() = application {', etc. But this, at least for the moment, seems to limit me to only the one view.

Is there a way to develop multiple different views and then change from one to the other (for a Desktop Application)?

I have tried multiple ways in order to access the other custom views that I am looking for but they are not working as intended.

My higher intention was to create an MVC Architecture for this small Kotlin project (where I could swap to many different views that hold different functionality), but I am happy to focus on the smaller question of the development of the views and the changing from one to the other, for now (hopefully I can use an example and extract the functionality later).

Example (without proper code):

View 1 - initial startup view - Displays buttons with text

View 2 - view that can be changed to (expected that the views can swap) - Displays Tables with text

Common for each - Button or Menu that can be used to swap between the different views.

I have tried (unsuccessfully) to:

  1. use the Composable functions, but am not able to switch views from the onClick function - error : @Composable invocations can only happen from the context of a @Composable function
  2. Search other tutorials that provide this way of working. I can only see single view projects/examples - nothing that would be able to change effectively. Assumed looking for the wrong search terms.

My Sample Project - Please excuse if it doesn't work, I have modified it to reduce the problem.

@OptIn(ExperimentalComposeUiApi::class)
fun main() = application {


    var action by mutableStateOf("Last action: None")

    Window(
            onCloseRequest = ::exitApplication,
            title = "TestProject",
            state = WindowState(width = 1300.dp, height = 750.dp)
    ) {
        Column() {
            TextButton(onClick = {}, modifier = Modifier.padding(8.dp)) {
                Text(text = "Change View")
            }
        }
        MenuBar {
            Menu("Tasks", mnemonic = 'T') {
                Item(
                        "Create new Task",
                        onClick = { action = "Last action: Create new Task" },
                        shortcut = KeyShortcut(Key.C, ctrl = true)
                )
            }
            

        }
        Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
            Text(text = action)
        }
    }
}

As above in 'tried 1' - added setView() - as an example, I would for the moment assume that there are only 2 views and haven't generalised the function, in this example, to incorporate 2+ views or realistically to pass any one view at a time - setView(View):

  TextButton(onClick = {setView()}, modifier = Modifier.padding(8.dp)) {
                Text(text = "Change View")
            }

I had seen that there were 'when' statements that involved states, but wasn't sure if that was appropriate for Desktop Applications, etc. (I had hoped that I wouldn't have to load all of the views before switching them and only relying on the one view to be used at a time) Most of the tutorials I had seen were for Android rather than custom views for Desktop.

Any help would be appreciated. Thanks (if you got this far :) )


Solution

  • As suggested by @Arkadii Ivanov, it was Navigation that was the correct area in order to move from one view to the next.

    In order to actually use Navigation I had to figure out how to use the functions that were already available commonly and adapt my application in order to use them.

    Which brings me to the official Compose Navigation documentation https://developer.android.com/jetpack/compose/navigation

    There are examples that show you how to use the functions but not how they fit together. I used the below example in order to help me out in being able to:

    1. Load up an application that works
    2. Be able to understand the application in order to use it.

    (https://github.com/itheamc/navigation-for-compose-for-desktop - basic application for desktop, used as a base for further adaptation. This example also gives an MVC Architecture and so was aligned to what I wanted to look into)

    I believe that the library suggested (https://github.com/arkivanov/Decompose) would be very suitable, but is harder for beginners to understand straight away. It would be nice to have a beginner walkthrough on the basic setup so there is less of a barrier to entry in using this library, as things are named slightly differently than the initial Compose documentation. - This is something for me to investigate down the line when I get a handle on how to actually use the UI properly.

    Basic Example - How to complete for the view: In order to Navigate you need to create the composable views and then add them into the NavigationHost class, as stated within the Compose Navigation Documentation

    1. Create View:

    @Composable
    fun Profile(navController: NavController) {
        /*...*/
        Button(onClick = { navController.navigate("friendslist") }) {
            Text(text = "Navigate next")
        }
        /*...*/
    }
    

    2. Create Navigation Host: - adding your newly created view and linking back to your NavController

    NavHost(navController = navController, startDestination = "profile") {
      composable("profile") { Profile(navController) }
      composable("friendslist") { FriendsList(/*...*/) }
      /*...*/
    }
    

    or in the Desktop example:

    @Composable
    fun CustomNavigationHost(navController: NavController, project: Project) {
        NavigationHost(navController) {
                    composable(Screen.HomeScreen.name) { HomeScreen(navController) }
    
                    composable(Screen.NotificationsScreen.name) { NotificationScreen(navController) }
             .build()
    }
    

    3. Navigate from a view:

    @Composable
    fun HomeScreen(
        navController: NavController
    ) {
        Column(
            modifier = Modifier.fillMaxSize(),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text(navController.currentScreen.value)
            Button(
                onClick = {
                    navController.navigate(Screen.ProfileScreens.name)
                }) {
                Text("Navigate to Profile")
            }
        }
    }
    

    Focus is on the Button and the navigate function:

      Button(
                    onClick = {
                        navController.navigate(Screen.ProfileScreens.name)
                    })
    

    There is a little bit more left to do as you need to be able to follow this path in the application in order to run it properly, but the initial flow of View to NavHost is the main part of the Navigation that I was looking for, as this can be used for multiple different views/composables.

    How to do it is a part of the examples given and was for me to figure out my own adaptation in order to fit my purposes.