Is it possible to generate a Compose-Multiplatform...
# compose
d
Is it possible to generate a Compose-Multiplatform UI at runtime? For example, I would like to build an app that I point at a server, fetch its Swagger/OpenAPI spec, and build a UI at runtime based on that spec. For example, if the user chooses a particular POST API from a list of available APIs in the spec, I would like to generate a UI at runtime that shows appropriate UI fields for the API request. I looked at this presentation about the compose runtime, and learnt about the Compose Compiler, Composer, Slot Table, and Applier. I guess I can’t use the Compose Compiler since I need to specify the UI at runtime based on the fetched spec. Can I achieve this with the Composer? I hope I don’t have to use the Slot Table directly. From the presentation, it appears I could use the Applier (interface and a concrete implementation), but I would have to implement state management and recompositions myself.
h
How is this usecase different to other usecases showing UI based on data? You only need a (dynamic) schema and show the UI based on the response data. You don’t need any special internal compose parts.
2
d
In the use cases you are referring to, the possible fields and the conditions when they are visible/hidden are known at compile time. i.e. the schema for the data is known at compile time. In my use case, the schema is known only at runtime. I can point the app to a different server and get a different schema (essentially an API browser that can be used with different API schemas)
But you mention dynamic schema. So maybe I am not understanding how it can be done
h
That’s true, but you need to map the response regardless compose or any other UI framework. And you mentioned openapi, so you do have a schema.
d
True, after building a UI at runtime, I will need to map actual data to it. That would be the next thing I would have to figure out. While there is an OpenApi spec, I want to build an app that can work with any spec, not just one particular spec. If it was one particular spec, I could build a compose app manually, or generate a compose app from the spec. But both these options will require a compile step. But I don’t want the app to work with only one spec. I want it to be a generic app that I can point to any OpenApi spec at runtime and have it show a UI for that spec and bind it to the actual data retuned by a server that implements that spec.
Postman is an example of such an API browser, but requests and responses are shown as JSON text. I want each field of in the JSON to have a dedicated UI component that is appropriate for the type of the field. A text field for string data, a dropdown for enum values, a checkbox for Boolean fields, etc.
h
You still need an algorithm to map the runtime schema you get from the openapi server to some UI schema defined by you during compiling, which is actually used to create your UI, eg by using compose in the regular way.
k
Instead of emitting
Text
and compiling it at runtime, you can have that
Text
emitted as a regular composable node. If you can be generic for the proposed runtime compilation, you can be generic without it with the exact same logic.
c
An OpenAPI schema might have some “dynamic” parts, but it still follows a predictable format. You just need to find a way to model the OpenAPI JSON file in a way that allows you to work with those dynamic parts, and them build a normal Compose UI to read that. For example, something like this might help you get started understanding the general idea. Assuming you have a library or some other way to fetch the OpenAPI document, you just iterate over its paths/methods/parameters/etc. For each of those, you can use a
when
block to handle the individual cases for method type, parameter types, or anything else.
Copy code
@Composable
fun SwaggerUi(documentUrl: String) {
    val openApiDocument by produceState<OpenApiDocument?>(initialValue = null, documentUrl) {
        value = fetchOpenApiDocument(documentUrl)
    }

    if (openApiDocument == null) {
        CircularProgressIndicator()
    } else {
        openApiDocument!!.paths.forEach { (apiPath, pathBody) ->
            SwaggerUiPath(apiPath, pathBody)
        }
    }
}

@Composable
fun SwaggerUiPath(apiPath: String, pathBody: OpenApiPath) {
    Text(apiPath)
    pathBody.methods.forEach { (methodName, methodBody) ->
        when (methodName.lowercase(Locale.ROOT)) {
            "get" -> SwaggerUiGet(apiPath, pathBody, methodBody)
            "post" -> SwaggerUiPost(apiPath, pathBody, methodBody)
            "put" -> SwaggerUiPut(apiPath, pathBody, methodBody)
            "delete" -> SwaggerUiDelete(apiPath, pathBody, methodBody)
        }
    }
}
d
Thanks @hfhbd and @Casey Brooks. I have not built anything with Compose yet (still investigating if it is a fit for my use case) so the use of for-loops and if/when expressions did not occur to me. I now understand the approach you both are proposing and looks like the standard compiled approach will work for me. Thanks!
Thanks @Kirill Grouchnikov. I understand now how the compiled approach can be made to work, and your explanation helped as well. I would also like to understand the non-compiled approach you mentioned. If I emit nodes at runtime, am I interfacing with the Composer or the Slot Table or the Applier? i.e. to which interface/library/framework am I emitting these nodes? And do I get the benefits of optimized data-binding and re-compositions?
c
Compose only works in conjunction with the compiler, and even supporting custom Composable trees different than Compose UI is still only interacting with the runtime components of Compose. You don’t change the Compose compiler or create a new compiler plugin to support new Composable use-cases. You essentially only need to write a new
Applier
and bootstrap the runtime, and everything else with the Slot Table, emitting nodes, recompositions, etc. is all handled by the Compose Compiler and core Compose Runtime library. But from what it sounds like you are trying to make, you almost certainly do not need to mess any of that; you’re just writing a regular Compose app. You only need to focus on learning about Compose State Management and the Compose Material libraries
d
Thanks @Casey Brooks! I will stick with the compiled approach.