🥕🥕🥕 This is a fork of pkamppur/mobile-sdk-comparison-2020 with original readme found here 🥕🥕🥕
Adding to the declarative UI approach of these mobile SDKs with Elm UI:
Pros:
- 👍 It's not CSS
- 👍 There is no App Store or Play Store to deal with
Cons:
- 👎 ... hold on, none
Ok done. Thanks for reading!
Similarly to what @pkamppur describes, the promise of a better developer experience, but just on the web, brought me to Elm and pretty soon thereafter to Elm UI. I've somehow managed to avoid writing much frontend web stuff and learning it with increasing demands on my time from tiny fleshy daemons crawling onto my shoulders there is no light at the end of that particular tunnel, for the time being.
What https://elm-lang.org describes Elm having—"No runtime exceptions", "Fearless refactoring", "Understand anyone's code", "Fast and friendly feedback" (from the compiler)—is the general gist but what entices me in my surroundings is to have a concise collection of tools and concepts to wrangle something up to the web for customers to use (and keep using with no runtime errors, see "Fearless refactoring"), whilst working in a small team with sprawling demands, no less.
What Elm UI gives oneself is likewise a concise collection of elements (among others, row, column, width fill, height shrink, and in desperate times, a moveLeft (px 2)) to describe your UI in. The declaration of how an element should look is right there in the function call, not spookily actioning upon your element from a distant CSS file. The model-view-update loop of Elm, or TEA, will take care of the rest, where its Promise (if you will in JS territory) is not a wriggly toad thrown into your lap which will either produce a diamond, or a poisonous turd which both might end up on the floor if your mind slips in the slime, but that toad is neatly placed in a box, atop another box, atop... where they all croak at you only when the runtime lets them and neither the diamond will cut your hand nor the turd poison you.
I'm talking about the update method of Elm, where we handle the end-result of each message we sent out to the runtime.
Upon navigating to the root, we tell in init for the runtime to perform a
getGames : Cmd Msg
getGames =
Http.get
{ url = baseUrl ++ "/the-hotness"
, expect = Http.expectJson (RemoteData.fromResult >> GotGames) decodeHotness
}
Should it all succeed in the network, the GotGames constructor function will be called with a RemoteData, which is
type RemoteData e a
= NotAsked
| Loading
| Failure e
| Success a
With Elm you can easily model your application around these rich types instead of having a null, empty list, an error of some type perhaps all in one dynamic variable which you try to not trip over.
With that RemoteData having been placed into our model in update, view is called and from there we case over the RemoteData in gameListView, where the UI starts to take form
case wlg of
Success games ->
container (List.indexedMap (\i -> \g -> gameCell g (i == List.length games - 1) m.detailPushed) games)
Failure e ->
container [ el [] (text "Failure") ]
Loading ->
container [ el [] (text "Loading") ]
NotAsked ->
container [ el [] (text "Kissat koiria sflsfalöfsakflö") ]
And produce a list view, where each gameCell is
gameCell : Game -> Bool -> Element Msg
gameCell g isLast =
let
cellH =
46
dividerH =
1
contentH =
cellH - dividerH * 2
dividerTop =
row [ width fill, height (px 1), BG.color (rgba 0 0 0 0.2) ] []
dividerBottom =
if isLast then
row [ width fill, height (px 1), BG.color (rgba 0 0 0 0.2) ] []
else
Element.none
in
wrappedRow [ width fill, height (pt cellH), Events.onMouseUp (TappedGame g) ]
[ column [ width fill, height (pt cellH) ]
[ row [ width fill, height (px dividerH) ]
[ dividerTop ]
, row [ width fill, height (pt contentH) ]
[ column [ width (fillPortion 2), height (pt contentH), Font.center, clip ]
[ image [ centerX, centerY, width (pt <| contentH - 4), height (pt <| contentH - 4), moveLeft 3, moveDown 2, Border.width 0, Border.rounded (siz 4), clip ]
{ src = g.thumbnailUrl, description = "Logo of " ++ g.name } ]
, column [ width (fillPortion 5), height (pt contentH) ]
[ el [ centerY ] <|
column [ paddingXY 10 0 ]
[ row (fontWithSize 9 ++ [ Font.alignLeft, width shrink, height shrink ])
[ paragraph [] [ text g.name ] ]
, row (fontWithSize 6 ++ [ Font.alignLeft, Font.color <| rgba 0 0 0 0.4, width shrink, height shrink, paddingEach { top = 2, left = 0, bottom = 0, right = 0 } ])
[ text g.yearPublished ]
]
]
, column [ width (fillPortion 1), height (pt contentH) ]
[ text "" ]
]
, row [ width fill, height (pt contentH) ]
[ dividerBottom ]
]
]
For people lucky to have been familiarized with endless brackets with Objective-C the expressions inside expressions ((())
) might not cause that much dizziness, but you can use the pipe operators |>
and <|
to make the code flow more nicely. Borrowing an example from Practical Elm for a busy developer, by Alex Korban (https://korban.net/elm/book/):
text
<| toString
<| List.filter canAddUsers
<| getActiveUsers projectId users
vs.
users
|> getActiveUsers projectId
|> List.filter canAddUsers
|> toString
|> text
where the first example is equivalent to this...
(text (toString (List.filter canAddUsers (getActiveUsers projectId users))))
Touch-upping on a gameCell
gets us to TappedGame g
message, details fethed and the subsequent call to detailPage
, where I cheated and used a very usable 0.2 version of https://github.com/passiomatic/elm-designer, which resembles something out of Xcode's Interface Builder innards and produces elm-ui code. If feeling like your brain could use a rest, wire the frames of the UI out with it and hash out the details later.
What a sharp-eyed code reader might notice is the htmlImg url maxw maxh = ...
function. I couldn't find a way to scale the image as I needed with elm-ui, so I had to escape into CSS, which is straightforward.
How is this all set up?
- download the installer from https://guide.elm-lang.org/install/elm.html
- initialize the project while in
mobile-sdk-comparison-2020/Elm
elm init
- install elm-ui, remotedata and other packages:
for pkg in elm/url elm/json elm/http mdgriffith/elm-ui krisajenkins/remotedata NoRedInk/elm-json-decode-pipeline; do elm install $pkg; done
- install a helpful local development server with hot reloading and time traveling debugger:
npm install -g elm-live
- run the thing and navigate to http://localhost:8000:
elm-live -- src/Main.elm --debug
All available packages for Elm are found at https://package.elm-lang.org or a catalogued collection through here https://korban.net/elm/catalog/
Elm plugins are available for VSCode and IntelliJ IDEA among others and elm-format
, available also via npm, will keep your code neat and uniform with the One True Style as go fmt
does with Golang sources.
Have kids? Try Elm UI.
Elm has a superb Slack community discoverable through https://elm-lang.org/community. I don't think a question on #beginners has gone unanswered without great care while I've been there. Also check out #elm-ui obviously and #misc for general software development discussions.
Links:
- Elm Radio episode on Elm UI: https://elm-radio.com/episode/elm-ui/
- a collection of patterns: https://korban.net/elm/elm-ui-patterns/