Skip to content

Commit c469823

Browse files
TimLarivieregitbook-bot
authored andcommitted
GITBOOK-40: Timothé's Jan 28 changes
1 parent fc91a3f commit c469823

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+3295
-1
lines changed

.gitbook/assets/image (1).png

295 KB
Loading

.gitbook/assets/image (2).png

67.2 KB
Loading

.gitbook/assets/image (3).png

318 KB
Loading

.gitbook/assets/image (4).png

34.3 KB
Loading

.gitbook/assets/image.png

160 KB
Loading

README.md

+10-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,10 @@
1-
# docs.fabulous.dev
1+
# Welcome
2+
3+
Fabulous is a modern declarative UI framework for crafting cross-platform mobile and desktop apps in .NET. It aims to bring you a great development experience and confidence in your code by combining an expressive UI syntax, the simple & robust Model-View-Update (MVU) architecture, and functional programming.
4+
5+
### About Fabulous 
6+
7+
Fabulous aims to provide all the tools to let you create your own mobile and desktop apps using only F# and the [Model-View-Update architecture](https://guide.elm-lang.org/architecture/) (shorten to MVU), with a great F# DSL for building dynamic UIs.\
8+
The combination of F# and MVU makes for a great development experience.
9+
10+
Note that Fabulous itself does not provide UI controls, so you’ll need to combine it with another framework like Xamarin.Forms, .NET MAUI or AvaloniaUI.

SUMMARY.md

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Table of contents
2+
3+
* [Welcome](README.md)
4+
* [Get Started](get-started.md)
5+
* [Samples & Tutorials](samples-and-tutorials/README.md)
6+
* [Samples](samples-and-tutorials/samples.md)
7+
* [Videos](samples-and-tutorials/videos.md)
8+
* [Basics](basics/README.md)
9+
* [MVU](basics/mvu.md)
10+
* [Application state](basics/application-state/README.md)
11+
* [Commands](basics/application-state/commands.md)
12+
* [State validation](basics/application-state/state-validation.md)
13+
* [User interface](basics/user-interface/README.md)
14+
* [Widgets and modifiers](basics/user-interface/widgets-and-modifiers.md)
15+
* [Styling](basics/user-interface/styling.md)
16+
* [Animations](basics/user-interface/animations.md)
17+
* [Testing](basics/testing.md)
18+
* [Error monitoring](basics/error-monitoring.md)
19+
* [Advanced](advanced/README.md)
20+
* [Saving and Restoring app state](advanced/saving-and-restoring-app-state.md)
21+
* [Performance optimization](advanced/performance-optimization.md)
22+
* [Composing larger applications](advanced/composing-larger-applications/README.md)
23+
* [Splitting into independent MVU states](advanced/composing-larger-applications/splitting-into-independent-mvu-states.md)
24+
* [Integrating commands](advanced/composing-larger-applications/integrating-commands.md)
25+
* [Communicating between MVU states](advanced/composing-larger-applications/communicating-between-mvu-states.md)
26+
* [Architecture](architecture/README.md)
27+
* [Attribute definitions](architecture/attribute-definitions.md)
28+
* [Virtualized collections](architecture/virtualized-collections.md)
29+
* [Using nightly builds](architecture/using-nightly-builds.md)
30+
31+
## Xamarin.Forms <a href="#xamarinforms" id="xamarinforms"></a>
32+
33+
* [Get Started](xamarinforms/get-started.md)
34+
* [User interface](xamarinforms/user-interface/README.md)
35+
* [Custom controls](xamarinforms/user-interface/custom-controls.md)
36+
* [Effects](xamarinforms/user-interface/effects.md)
37+
* [Navigation](xamarinforms/user-interface/navigation.md)
38+
* [Pop-ups](xamarinforms/user-interface/pop-ups.md)
39+
* [Extensions](xamarinforms/extensions/README.md)
40+
* [FFImageLoading](xamarinforms/extensions/ffimageloading.md)
41+
* [OxyPlot](xamarinforms/extensions/oxyplot.md)
42+
* [SkiaSharp](xamarinforms/extensions/skiasharp.md)
43+
* [VideoManager](xamarinforms/extensions/videomanager.md)
44+
* [Xamarin.Forms.Maps](xamarinforms/extensions/xamarin.forms.maps.md)
45+
46+
## .NET MAUI <a href="#maui" id="maui"></a>
47+
48+
* [Get Started](maui/get-started.md)
49+
50+
## Avalonia
51+
52+
* [Get Started](avalonia/get-started.md)

advanced/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Advanced
2+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Composing larger applications
2+
3+
Composing larger MVU applications is covered in great details in [The Elmish Book](https://zaid-ajaj.github.io/the-elmish-book/#/chapters/scaling/). This documentation will cover mainly how to integrate those concepts into Fabulous apps.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
---
2+
description: >-
3+
Learn how to use the Intent pattern to communicate between independent MVU
4+
states
5+
---
6+
7+
# Communicating between MVU states
8+
9+
Like we saw in the previous sections, most MVU apps adopt the following signatures to model how their state changes and the side effects to be executed:
10+
11+
```fsharp
12+
val init : unit -> Model * Cmd<Msg>
13+
val update : Msg -> Model -> Model * Cmd<Msg>
14+
```
15+
16+
MVU programs are meant to be well isolated from the outside world to be reliable and predictable, producing only pure init and update functions. But how does a program communicate with other programs when something goes out of its scope such as navigating to another page?
17+
18+
### Communicating from child to parent
19+
20+
While technically the parent could peek into the child's Model to eventually react to a specific state and do additional work (see [Splitting into independent MVU states](splitting-into-independent-mvu-states.md)). This approach is pretty bad because it depends greatly on the internal implementation of the child which can change at any time.
21+
22+
Since we know the parent will always call the child's init and update functions, we can take advantage of this by returning a new value specifically for the parent.
23+
24+
Comes in the Intent pattern:
25+
26+
```fsharp
27+
val init: unit -> Model * Cmd<Msg> * Intent option
28+
val update: Msg -> Model -> Model * Cmd<Msg> * Intent option
29+
```
30+
31+
As you can see, we are returning one more value after executing init and update: Intent option.
32+
33+
The Intent is a discriminated union created by you just like Msg. Its goal is to notify the caller of extra intention that needs to be taken care of outside the child.
34+
35+
Let's take the example of an app going through several pages of forms that the user needs to fill in.
36+
37+
The App module will take care of the whole workflow between the pages, but each individual page has its own MVU state. By returning an intent, the page can notify the app to do cross-page actions.
38+
39+
{% code title="Form1.fs" %}
40+
```fsharp
41+
type Msg =
42+
| TextChanged of string
43+
| Complete
44+
45+
type Intent =
46+
| SaveDraft of string
47+
| GoToNextStep
48+
49+
let init () =
50+
{ ... }, Cmd.none, None
51+
52+
let update msg model =
53+
match msg with
54+
| TextChanged newValue -> { model with Text = newValue }, Cmd.none, Some (SaveDraft draft)
55+
| Complete -> model, Cmd.none, Some GoToNextStep
56+
```
57+
{% endcode %}
58+
59+
{% code title="App.fs" %}
60+
```fsharp
61+
type Msg =
62+
| NextStep
63+
| ...
64+
65+
let saveDraftOnDisk draft =
66+
Cmd.ofAsyncMsg(async { ... })
67+
68+
let update msg model =
69+
match msg with
70+
| Form1Msg f1 ->
71+
let m, c, i = Form1.update f1 model.Form1Model
72+
let intentCmd =
73+
match i with
74+
| Some Form1.Intent.SaveDraft draft -> saveDraftOnDisk draft
75+
| Some Form1.Intent.GoToNextStep -> Cmd.ofMsg NextStep
76+
| _ -> Cmd.none
77+
78+
{ model with Form1Model = m },
79+
Cmd.batch [
80+
intentCmd
81+
Cmd.map Form1Msg c
82+
]
83+
```
84+
{% endcode %}
85+
86+
### Communicating between siblings
87+
88+
Just like parent-child communication, communicating between siblings involves using an intent. Except the intent this time tells the parent to forward a message to the child's sibling.
89+
90+
{% code title="Form1.fs" %}
91+
```fsharp
92+
type Intent =
93+
| SelectNextField
94+
95+
let update msg model =
96+
match msg with
97+
| FocusNextForm -> model, Cmd.none, Some SelectNextField
98+
```
99+
{% endcode %}
100+
101+
{% code title="Form2.fs" %}
102+
```fsharp
103+
let update msg model =
104+
match msg with
105+
| FocusFirstField -> { model with ... }, Cmd.none, None
106+
```
107+
{% endcode %}
108+
109+
{% code title="App.fs" %}
110+
```fsharp
111+
let update msg model =
112+
match msg with
113+
| Form1Msg f1 ->
114+
let m, c, i = Form1.update f1 model.Form1Model
115+
let intentCmd =
116+
match i with
117+
| Some Form1.Intent.SelectNextField -> Cmd.ofMsg (Form2Msg Form2.Msg.FocusFirstField)
118+
| _ -> Cmd.none
119+
{ model with Form1Model = m },
120+
Cmd.batch [
121+
intentCmd
122+
Cmd.map Form1Msg c
123+
]
124+
125+
| Form2Msg f2 ->
126+
let m, c, i = Form2.update f2 model.Form2Model
127+
let intentCmd =
128+
match i with
129+
| _ -> Cmd.none
130+
{ model with Form2Model = m },
131+
Cmd.map Form2Msg c
132+
```
133+
{% endcode %}
134+
135+
To learn more about the Intent pattern, please read [The Elmish Book - The Intent Pattern](https://zaid-ajaj.github.io/the-elmish-book/#/chapters/scaling/intent).
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
---
2+
description: >-
3+
Learn how to use Cmd.map and Cmd.batch to compose Cmds from independent MVU
4+
states
5+
---
6+
7+
# Integrating commands
8+
9+
In [Splitting into independent MVU states](splitting-into-independent-mvu-states.md), we learned how to decompose larger apps into individual MVU states by using simple init and update functions returning only a Model, like in those signatures:
10+
11+
```fsharp
12+
val init: unit -> Model
13+
val update: Msg -> Model -> Model
14+
```
15+
16+
Though most apps will need more complex signatures to handle side effects such as processing data, network calls, access to the camera or storage, etc. Those apps will use commands to model those side-effects:
17+
18+
```fsharp
19+
val init: unit -> Model * Cmd<Msg>
20+
val update: Msg -> Model -> Model * Cmd<Msg>
21+
```
22+
23+
If we simply do the same than in the previous section, we will face the same compilation issue we saw by composing views with different Msg types.
24+
25+
```fsharp
26+
let update msg model =
27+
match msg with
28+
| Form1Msg f1 ->
29+
let m, c = Form1.update f1 model.Form1Model
30+
{ model with Form1Model = m }, c
31+
32+
| Form1Msg f2 ->
33+
let m, c = Form2.update f2 model.Form2Model
34+
{ model with Form2Model = m }, c // ERROR: Expected Cmd<Form1.Msg>, got Cmd<Form2.Msg>
35+
```
36+
37+
We solved that issue with View.map previously. Since the issue is similar, the solution is also similar.
38+
39+
### Converting a command's Msg type
40+
41+
Just like View.map to convert a widget's Msg type to the parent's Msg type to allow for composition, Fabulous has Cmd.map to do the same for commands.
42+
43+
```fsharp
44+
val Cmd.map : ('oldMsg -> 'newMsg) -> Cmd<'oldMsg> -> Cmd<'newMsg>
45+
```
46+
47+
Using Cmd.map is very similar to View.map.
48+
49+
```fsharp
50+
Cmd.map Form1Msg form1Cmd
51+
```
52+
53+
{% code title="App.fs" %}
54+
```fsharp
55+
type Msg =
56+
| Form1Msg of Form1.Msg
57+
| Form2Msg of Form2.Msg
58+
59+
let init () =
60+
let m, c = Form1.init()
61+
{ Form1Model = m }, Cmd.map Form1Msg c
62+
63+
let update msg model =
64+
match msg with
65+
| Form1Msg f1 ->
66+
let m, c = Form1.update f1 model.Form1Model
67+
{ model with Form1Model = m }, Cmd.map Form1Msg c
68+
69+
| Form2Msg f2 ->
70+
let m, c = Form1.update f2 model.Form2Model
71+
{ model with Form2Model = m }, Cmd.map Form2Msg c
72+
```
73+
{% endcode %}
74+
75+
### Batching several Cmds together
76+
77+
Sometimes, an app needs to execute several side effects at the same time. To enable that, Fabulous provides a Cmd.batch function to merge several commands into a single one.
78+
79+
```fsharp
80+
val Cmd.batch : Cmd<'msg> list -> Cmd<'msg>
81+
```
82+
83+
We can combine Cmd.batch with Cmd.map as we want.
84+
85+
```fsharp
86+
let init () =
87+
let m1, c1 = Counter.init()
88+
let m2, c2 = Input.init()
89+
{ CounterModel = m1
90+
InputModel = m2 },
91+
Cmd.batch [
92+
Cmd.ofAsyncMsg(loadDataAsync())
93+
Cmd.map CounterMsg c1
94+
Cmd.map InputMsg c2
95+
]
96+
```
97+
98+
To learn more about integrating commands, read [The Elmish Book - Integrating Commands](https://zaid-ajaj.github.io/the-elmish-book/#/chapters/scaling/integrating-commands).

0 commit comments

Comments
 (0)