Scandinavian news in Elm

Hi folks!

A while ago we released two products: minE24 and Omni Ekonomi. minE24 is a Norwegian economy news service that provides personalised content. Omni Ekonomi is a similar, except in Sweden!

Both of these are built on a platform that we are making at Omni. The core product is provided via an app for each. But people want to share news articles, on various platforms! People can’t share apps, so we’ve made a web-share which renders just a single article. This had to look good, since it draws people into our apps and represents them. It has to load fast with minimal bandwidth usage, since our target market includes people on the go with a poor network. And, it has to always work.

So, we wrote one in Elm to do exactly that! We use elm-css in order to style things, using a type called CloneName to specific which template to apply to a rendered article. The article resources are decoded via Json, using a filterMap decoder which keeps only the resources supported. This allows us to change the API easily while still having content rendering. We render the Elm via node in order to ensure the page is minimal. Likewise, we make elm-css render a stylesheet for the same reason.

All this has led to a clean, reliable service which just does not break and can load pages extremely quickly. Adding new features takes very little time, and using Elm to verify that all the cases of CloneName-specific behaviour has been covered has led to a lot of edge cases being prevented.
Check out some articles if you like!

As of right now, Omni Ekonomi is currently doing pretty well on the Android and iOS app stores:


As is MinE24:


Let me know if you have any questions!

10 Likes

This is a super cool writeup; thank you for sharing!

I think this would make an excellent case study for Elm Town! Pinging @splodingsocks.

1 Like

Thanks! I thought I’d also share some the more interesting parts of code:

Field value

We have a lot of different parts of an article we call a resource. Each resource has a particular structure: there’s a field called type. An example for that field might be type: 'Text'. We decode things differently based on that type. Typically, you may want to use Json.Decode.string along with Json.Decode.andThen in order to cover the different paths. That approach has some great upsides: you’ll use a case of statement which lets Elm tell you when you forget to add a case. For us, decoding the resource isn’t always as simple as just relying on the type. There’s multiple different types of Text, for example. We also have some resources which don’t live in the main collection: a main image resource, and a main text resource. So, we express all our decoders independently. They still need to be decode based on the type. We came up with this function which allows us to do exactly that:

import Json.Decode


{-| Successfully decodes a value when the decoded string matches the given value

    fieldValue "yes" <| Json.Encode.string "yes"
    --> Json.Decode.succeed "yes"

    fieldValue "no" <| Json.Encode.string "yes"
    --> Json.Decode.fail "Expected 'no' but got 'yes'"

   Json.Decode.field "type" <| fieldValue "Text <| Json.Encode.object [ ("type", Json.Encode.string "Text") ]
   --> Json.Decode.succeed "Text"
-}
fieldValue : String -> Json.Decode.Decoder String
fieldValue passingFieldValue =
    Json.Decode.string
        |> Json.Decode.andThen
            (\value ->
                if value == passingFieldValue then
                    Json.Decode.succeed value
                else
                    "Expected '"
                        ++ passingFieldValue
                        ++ "' but got '"
                        ++ value
                        ++ "'"
                        |> Json.Decode.fail
            )

Filtering unsupported values

Since we need to always render things and ignore parts we don’t support yet, we ended up making out main decoder look like this:

type Resource
    = ImageResource Image
    | TextResource Text
    | Unsupported Json.Decode.Value

decodeResource : Json.Decode.Decoder Resource
decodeResource =
    Json.Decode.oneOf
        [ Json.Decode.map ImageResource decodeImage
        , Json.Decode.map TextResource decodeText
        , Json.Decode.map Unsupported Json.Decode.value
        ]

The idea here is that if the first three decoders fail, the last decoder gives us a way to ignore the failed resource, while still having it at hand for creating error reports. In development, if we need to make sure we cover all the cases, we can just comment out the Unsupported line!

Very cool! Agreed with @brian about Elm town.

How do you manage the bundling for the stores? Do you use Cordova or manually wrap the web app on a native app or something else?

Thanks for sharing!!

1 Like

How do you manage the bundling for the stores? Do you use Cordova or manually wrap the web app on a native app or something else?

The apps themselves are native Kotlin and Swift – the only place where Elm is used is for the sharing of articles. E.g for sharing on Reddit, social media, etc. So only web-based stuff!

This is really cool, @eeue56!

Want to pick a time on my schedule for after the holidays and record an episode about it?

https://calendly.com/murphyrandle/elmtown/01-05-2018

1 Like

For anyone interested, we ended up discussing it here:

https://elmtown.audio/26-noah-hall

5 Likes