https://kotlinlang.org logo
#ballast
Title
# ballast
u

ubuntudroid

11/01/2023, 3:16 PM
And lastly, only one of my two view models is picked up by the debugger, although they are configured exactly the same. 🤔
Generally, it looks like debugging kind of gets stuck after a few seconds. For instance, I have a side job which appears properly in the debugger. It finishes, and sends back its input properly (and my app’s UI and the logs in Logcat reflects that), but neither that input is tracked by the debugger, nor is the side job marked as finished in the debugger (it is still in progress in the debugger).
c

Casey Brooks

11/01/2023, 3:34 PM
Are you using the same
BallastDebuggerClientConnection
instance for both VMs? If not, you’ll probably end up with 2 connections instead of 2 VMs in the same connection
And for the crashing, its possible that it’s running into Intellij compatibility issues. The build process is buggy and it has been a constant battle getting Compose UI to work properly in the IntelliJ plugin. JB is rewriting the Gradle plugin which builds Intellij plugins from scratch which I’m hopeful will fix the problem. Does the entire Debugger UI become unresponsive once it “hangs” for you? And which version of Intellij/Android Studio are you using?
u

ubuntudroid

11/01/2023, 3:42 PM
Need to run now, will reply to your second message later. Re connection: yes, I am using one client connection for both VMs and I only see one connection in the debugger.
👍 1
Re crashing: • the entire UI becomes unresponsive and I need to close and reopen the debugger UI (closing the UI after such a hang sometimes also closes all other plugin/built-in windows at the bottom of the IDE and makes it so that they cannot be opened again at all 🤔- in that case only closing and reopening the project fixes things) • the UI becomes reliably unresponsive when clicking on one of the dropdown buttons (ViewModel or Connection). Clicking on the text in the fields reliably opens the drop downs without the hang though. My Android Studio info: Android Studio Iguana | 2023.2.1 Canary 11 Build #AI-232.10072.27.2321.11006994, built on October 26, 2023 Runtime version: 17.0.8+0-17.0.8b1000.22-10799086 x86_64 VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o. macOS 14.0 GC: G1 Young Generation, G1 Old Generation Memory: 4096M Cores: 16 Metal Rendering is ON Registry: external.system.auto.import.disabled=true ide.text.editor.with.preview.show.floating.toolbar=false ide.experimental.ui=true Non-Bundled Plugins: com.codeium.intellij (1.4.2) wu.seal.tool.jsontokotlin (3.7.4) com.jetbrains.kmm (0.8.0(232)-11) org.jetbrains.compose.desktop.ide (1.5.3) com.copperleaf.ballast.Ballast (4.0.0)
• opening e.g. Logcat or the debugger View to the left of the debugger while there is an active debugging connecting kinda restarts the plugin and makes not pick up all things (e.g. my Router VM isn’t picked up, so is the current route) - one has to restart the app under test to make things work again • the side-job really seems to be a culprit - the built-in debugger plugin logs simply stop printing stuff after “sideJob started”
c

Casey Brooks

11/01/2023, 7:00 PM
Yeah, those both sound like the standard Compose issues I’ve been seeing. Basically, what’s going wrong is that the Gradle plugin used to compile Intellij plugins is supposed to allow me to use whatever versions of Kotlin, Coroutines, etc. I want, but because of an issue with that Gradle plugin, the dependencies don’t get loaded by Intellij’s Classloader as expected, and the versions bundled with Intellij are actually used at runtime. For example, it should be using the Kotlin 1.9.10 stdlib, but instead the version of Kotlin bundled with Intellij (I think 1.7.20) is actually used. JB just released their first beta of the new Gradle plugin which is supposed to fix these kinds of issues for good, but I haven’t been able to try it out yet. I have also noticed the dropdown crash. Same issue as descibed above, I think something with the button’s ripple effect is accessing a new Kotlin or Coroutines API. I’ll dig into the side job issues and see if I can reproduce. I’m pretty sure they’re working in my version of Intellij Ultimate, but it might be something with that version of AS.
u

ubuntudroid

11/01/2023, 7:02 PM
Great, let me know if I can help somehow.
Do you think the side job also causes the “missing VM” issue?
Btw, my current ballast project is just a little test project. I plan to make it public eventually, but in the meantime I can give you read access, so you can reproduce, if you want.
c

Casey Brooks

11/01/2023, 7:06 PM
Possibly. There was an issue earlier where the same classloader issues make it so a client couldn’t even connect to the debugger. The websocket would crash as soon as a VM tried to connect. It’s strange that one VM is working, though. Also, is the Debugger interceptor installed in both VMs? Just like it needed to be added for the Router, it needs to be installed individually in each VM you want to see in the Debugger UI
Access to that project would be quite helpful, so yes, please invite me to it!
u

ubuntudroid

11/01/2023, 7:07 PM
yes, the debugger interceptor is now installed in all VMs, double checked 😅
alright, let me do some cleanup and push stuff and then I’ll add you
c

Casey Brooks

11/01/2023, 7:11 PM
Sounds good. And thank you for your patience working through this debugger issue! Hopefully I can get everything fixed soon
❤️ 1
u

ubuntudroid

11/01/2023, 7:12 PM
ah, no need to thank me: you did all the awesome work on the plugin and the framework and made it available to everyone, and I’m just muttering about random stuff 😉
Repo invite is out. 📬
Just type in any name and API key in the login screen (/login), you will see an error in the next screen (/pastes), but it is already enough to test the issues above.
c

Casey Brooks

11/02/2023, 5:40 PM
Ok, I think I see the issue with this. I’ll describe the problem briefly here so others might also benefit from the solution. In the repo, you’re using an EventHandler to observe changes to the backstack, and redirect to the login screen when a user tried to visit a page but is not authenticated. This is causing issues because the event handler is running asynchronously to the UI. The intent of the EventHandler is to be a bridge from the pure MVI world to the (often imperative) platform-specific non-MVI world. The confusion here is that the
BackstackChanged
event does get updated with every change to the Router state, but it is not itself intended to be considered State because it’s not actually fully in sync with the state. Only the VMs StateFlow can accurately represent the state. The order of events using this redirect is causing the issue. Here’s the general sequence: 1. The app loads, and an Input for the initial route is sent 2. The Input gets processed and a new State is generated, which includes that route. Following the state change, a
BackstackChanged
event is also sent to the EventHandler. 3. The Compose UI immediately updates, and it loads the screen corresponding to the route. 4. In parallel to the UI, the EventHandler eventually receives the
BackstackChanged
event and sends an Input to redirect to the login screen. This redirect is working fine. But notably, this event is handled at some point after the UI state has already changed. As a result, there was a brief time when the router state shows the initial route as the current route, and during that time the screen gets briefly displayed and its VM starts running. 5. That VM sends an Input to initialize itself, which fetches data from a Repository in a sideJob. But since the user is not authenticated, an error is thrown by the repository, because it doesn’t have any user credentials. 6. Shortly after this, the redirect is processed and the user is sent back to the Login screen. This cancels the coroutineScope that the screen’s VM was running in, and I believe it’s getting cancelled before the VM can notify the debugger that the sideJob failed. Thus, the debugger appears to hang, because it never received a notification that the sideJob completed with an error. The problem is that the UI is still running and displaying UI without caring whether the user is authenticated or not. What should be done instead is include the authentication/redirection logic directly in the Compose UI, not an EventHandler. This will allow you to actually hide UI that requires auth (and thus prevent those VMs from running at all), while also handling the redirect.
The following snippet is a better mechanism for protected authenticated routes:
Copy code
public object AuthRequired : RouteAnnotation
enum class Screen(
    routeFormat: String,
    override val annotations: Set<RouteAnnotation> = emptySet(),
): Route {
    AuthenticatedRoute("/pastes", annotations = setOf(AuthRequired)),
    Login("/login"),
    ;

    override val matcher: RouteMatcher = RouteMatcher.create(routeFormat)
}

routerState.renderCurrentDestination(
    route = { screen ->
        val displayRoute = @Composable {
            when(screen) {
                Screen.Pastes -> {
                    PastesScreen.Content(router)
                }
                Screen.Login -> {
                    LoginScreen.Content(router)
                }
            }
        }

        val screenRequiresAuth = AuthRequired in annotations
        if(screenRequiresAuth) {
            val isAuthenticated = remember(screen) {
                userRepository.isAuthenticated
            }

            if(isAuthenticated) {
                displayRoute()
            } else {
                LaunchedEffect(screen) {
                    router.trySend(RouterContract.Inputs.GoToDestination(Screen.Login.directions().build()))
                }
            }
        } else {
            displayRoute()
        }
    },
    notFound = {
        // TODO add "not found" screen
    }
)
This also explains the problem of not having every VM displayed in the debugger. When a VM is scoped to a point in the Composition, it is only created when that UI is visible. Not all VMs are created at app startup. So unless you actually visit a screen, you won’t see a VM reported in the debugger. The fact that both VMs are displayed initially is because of the redidrection logic as described above. Also, depending on the timing of events sent from the app, if the debugger UI wasn’t ready by the time the initial VM is loaded, you wouldn’t see it in the debugger UI until you start intaracting with the VM
u

ubuntudroid

11/02/2023, 6:03 PM
Thanks for the elaborate explanation, Casey! 🙏 I mostly understand and agree with what you are saying - your code snippet definitely is an improvement over my implementation and it now makes sense to not put the rerouting logic into the event handler. There are two things, though, which are not in line with what you are stating (or I’m just misunderstanding): 1. I never see the LoginViewModel in the debugger UI, not briefly, not in the beginning, and not when manually navigating to the login UI later from the pastes screen (via the settings icon). 2. The logs in Logcat state that the side job loading the pastes data actually finishes successfully even if the corresponding UI isn’t visible at that point - it finishes with sending an error state, yes, but that’s wrapped in a the Yield object, so no exception is thrown in the process.
c

Casey Brooks

11/02/2023, 6:37 PM
Hmm, so on a second look, it seems that nothing gets sent to the debugger after landing on the Pastes screen. The Router’s current URL doesn’t update, nor do the Inputs from either screen get sent to the debugger. The Debugger UI itself is still responsive, so it seems like there’s an issue somewhere in the device. I’ll dig into this later to see what might be the problem
🚀 1
u

ubuntudroid

11/02/2023, 7:03 PM
ah, and I just noticed, that even if the app starts with the Pastes screen right away the sidejob is reported as loading forever. So it’s not just an in-background issue.