Skip to content

Question: adding child views at runtime to FlexLayout or StackLayout? #18

@gaelian

Description

@gaelian

Hello! I'm back with another possibly silly question.

I have a situation where I need to add child views to - preferably - a FlexLayout or StackLayout, at runtime, after the first init call, based on user input. The child views can be of different types, depending on the user input and values that are periodically retrieved/updated from a remote source and saved into an SQLite database in the app.

I had hoped that I could just wrap a FlexLayout in a ScrollView and when I insert the new child views, things would expand to accomodate, even if the overall height ends up taller than the device screen. It seems that FlexLayout does not do this. What I'm seeing is that the ScrollView does seem to account for the height of the newly inserted elements but neither a FlexLayout or StackLayout do. So the result is that after the new child views are inserted, any child views that end up below the overall height of the screen are not shown, but the ScrollView none the less acts like they are there and one can scroll "below the fold" to see only empty space.

Before I start tearing out code wholesale to try and find a completely different way to do this, can anyone point me in the direction of a good way to dynamically insert heterogeneous child views like this, with Fabulous?

A simplified and contrived example of what I'm trying to do (based off the Fabulous example app):

// Copyright Fabulous contributors. See LICENSE.md for license.
namespace FabXamApp

open System
open Fabulous
open Fabulous.XamarinForms
open Xamarin.Forms

module App = 
    type Model = 
      { Values: Tuple<string, string> list }

    type Msg = 
        | Insert
        | Reset

    let initModel = { Values = [] }

    let init () = initModel, Cmd.none

    let update msg model =
        match msg with
        | Insert ->
            // In reality these values come from a database and can change from time to time.
            // Sometimes there could be quite a lot of values, other times not so much.
            let values = [ ("Label 1", "Placeholder 1"); ("Label 2", "Placeholder 2"); ("Label 3", "Placeholder 3"); ("Label 4", "Placeholder 4") ]

            { model with Values = values }, Cmd.none
        | Reset -> init ()

    let insertView value = 
        match value with
        | v when v = ("Label 2", "Placeholder 2") ->
            View.Grid(
                width = 250.,
                height = 200.,
                rowdefs = [ Absolute 200. ],
                coldefs = [ Star; Star ],
                children = [
                    View.Label(
                        verticalOptions = LayoutOptions.FillAndExpand,
                        verticalTextAlignment = TextAlignment.Center,
                        text = (v |> fst)
                    ).Row(0).Column(0)
                    View.Switch(
                        verticalOptions = LayoutOptions.FillAndExpand,
                        horizontalOptions = LayoutOptions.End,
                        isToggled = true
                    ).Row(0).Column(1)
                ]
            )
        | _ ->
            View.StackLayout(
                height = 200.,
                horizontalOptions = LayoutOptions.Center,
                padding = Thickness (0., 10., 0., 10.),
                children = [
                    View.Label(
                        height = 42.,
                        width = 200.,
                        text = (value |> fst)
                    )
                    View.Editor(
                        height = 42.,
                        width = 200.,
                        placeholder = (value |> snd)
                    )
                ]
            )

    let view (model: Model) dispatch =
        View.ContentPage(
          content = 
            View.ScrollView(
                View.FlexLayout(
                    direction = FlexDirection.Column,
                    alignItems = FlexAlignItems.Center,
                    justifyContent = FlexJustify.SpaceEvenly,
                    children = [ 
                        // In reality, user input is more variable than just a button and this input will ultimately 
                        // decide whether labels, switches, entries, etc. are inserted.
                        View.Button(text = "Insert", horizontalOptions = LayoutOptions.Center, command = (fun () -> dispatch Insert))

                        View.Button(text = "Reset", horizontalOptions = LayoutOptions.Center, command = (fun () -> dispatch Reset))

                        match model.Values.Length with
                        | l when l > 0 ->
                            for value in model.Values do
                                insertView value
                        | _ -> ()
                        View.Label(text = "I'm near the bottom and may not be visible after you tap 'Insert'", horizontalOptions = LayoutOptions.Center, width=200.0, horizontalTextAlignment=TextAlignment.Center)
                    ]
                )
            )
        )

    // Note, this declaration is needed if you enable LiveUpdate
    let program =
        XamarinFormsProgram.mkProgram init update view
#if DEBUG
        |> Program.withConsoleTrace
#endif

type App () as app = 
    inherit Application ()

    let runner = 
        App.program
        |> XamarinFormsProgram.run app

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions