Thursday, February 04, 2021

Flutter vs SwiftUI

It's a new year and time to make a new app.

So what do I build my new app in? SwiftUI or Flutter?

TL;DR - I'm making my new app in Flutter.

I arrived at this decision by making a vertical slice of the app using SwiftUI,  Flutter and standard Swift with SpriteKit.  

The app I'm building is an update of my Brainiversity brain training game. Brainiversity has a number of small games that test your math, memory and word skills. I've been making versions of this app since 2007, so I have code bases written in C++, ActionScript and Lua. I used the existing "Add It Up" mini-game for my vertical slice.

Swift and SpriteKit

Line count: 647
SpriteKit version

This version is very similar to the original C++, ActionScript and Lua versions. It uses sprite animations to give the game zing, and everything is imperative - so the text in the answer counter label in the top left of the screen is manually updated when a new question is generated. 

I found SKAction  was useful in handling animations. This made it really easy to chain together a sequence of events like:
  1. a cross appears when the user presses the wrong answer button,
  2. the correct answer scrolls in from the right of the screen,
  3. after a pause, answer counter pulses as it counts up one,
  4. the answer scrolls off screen to the left as a new question scrolls in from the right
  5. the buttons flop over to reveal new answer values.
Compared to the SwiftUI and Flutter versions, there was a lot of code to make stuff happen, and to be honest, after making apps with reactive UI, programming this way felt old fashioned.

SwiftUI

Line count: 210
SwiftUI version

I've already made a few apps in SwiftUI so I was used to the reactive paradigm. Reactive programming results in reduced code which makes reading the code so much easier. I love how SwiftUI handle state with the @State property wrapper - it seems a lot cleaner and less code than Flutter's SetState or Provider solutions.

The use of VStack, HStack and Spacer makes it really easy to lay out the screen. Here is the code to layout the question and the answer buttons.

   VStack {

      VStack {

        HStack {

          Text("Correct: \(correct)")

            .bold()

            .font(.system(size: width * 0.20))

          Spacer()

          Text("Wrong: \(mistakes)")

            .bold()

            .font(.system(size: width * 0.20))

        }.padding()

        Text("Select the correct answer for the math equation below from the multiple choice buttons.")

          .font(.system(size: width * 0.20))

          .padding()

        Text(question)

          .font(.system(size: width * 0.35))

        VStack {

          HStack {

            Spacer()

            RoundButton(title: "\(answer1)", color: Color.red, size: width, function: {tryAnswer(button: 1)})

            

            Spacer()

            RoundButton(title: "\(answer2)", color: Color.orange, size: width, function: {tryAnswer(button: 2)})

            

            Spacer()

          }.padding()

          

          HStack {

            Spacer()

            RoundButton(title: "\(answer3)", color: Color.green, size: width, function: {tryAnswer(button: 3)})

            

            Spacer()

            RoundButton(title: "\(answer4)", color: Color.blue, size: width, function: {tryAnswer(button: 4)})

            

            Spacer()

          }.padding()

        }

      }

      Spacer()

    }

    .navigationTitle("Add It Up")

    .onAppear {

      generateResults()

      width = UIScreen.main.bounds.width * 0.3

    }


This is fairly similar to Flutter, which uses Columns, Rows and Spacer - here is the button section code in Flutter:

return Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
answerButtonRound(
answer: answerList[0],
color: Colors.green,
size: buttonSize,
func: () => {_answerButtonPressed(buttonNr: 1)}),
answerButtonRound(
answer: answerList[1],
color: Colors.blue,
size: buttonSize,
func: () => {_answerButtonPressed(buttonNr: 2)}),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
answerButtonRound(
answer: answerList[2],
color: Colors.red,
size: buttonSize,
func: () => {_answerButtonPressed(buttonNr: 2)}),
answerButtonRound(
answer: answerList[3],
color: Colors.orange,
size: buttonSize,
func: () => {_answerButtonPressed(buttonNr: 4)}),
],
),
],
);

Swift code is definitely a lot cleaner to read with less nesting and less boiler plate. The child and children parameters of Flutter take up extra space and cause more visual clutter.

I hit a brick wall with SwiftUI when I wanted to chain animations together with some state updates in the midst of all the action. I could not find a way to make the question label animate away, then update the state for new questions, then slide an updated question text back on screen. The best answer I could find was to start the slide out animation with a slide in animaion, then set a timer to call my generateNewQuestions() function so it occurred halfway between the slide in and out.

Maybe I didn't look hard enough for the right way to do this, but that's currently an issue with SwiftUI documentation. There's not much of it out there. Whereas I found multiple examples and tutorials on how to do what I wanted in Flutter on flutter.dev and other web sites.

Swift Pros

  • Light weight code that is easy to read. You can do a lot with so little.
  • Dark theme just works out of the box.
  • I can target Apple Watch.
  • CoreData is nice and works out of the box with Apple devices.
  • The state system is really easy to use.
  • No semi-colons!

Swift Cons

  • App Preview is slow to update, even on my M1 MacBook Air - and I constantly had to keep hitting Resume to rebuild the preview.
  • Documentation is lacking, especially compared to Flutter. Thank goodness for Hacking With Swift - the best resource for Swift developers.
  • Can only make apps for the Apple ecosystem.
  • Refactoring is less advanced than Flutter.

Flutter

Line count: 304
Flutter version


Even though Flutter has sightly more boilerplate code to set things up, I like how everything is a widget and it's pretty consistent. You generally nest widgets within widgets - and when you go too deep you refactor out into new widgets which makes for easier to read code. SwiftUI has nesting as well, but also has modifiers which I'm not 100% sold on.

In SwiftUI to make the text bold you add a .bold() modifier.

          Text("Wrong: \(mistakes)")

            .bold()


In Flutter you add the bold as part of a parameter of Text.

                        Text('Wrong $mistakes', style: TextStyle(fontWeight: FontWeight.bold)),

I guess what feels weird with SwiftUI modifiers is that to give a screen a title, you add the modifier at the bottom of the view - so when you read the code you aren't seeing the layout as it occurs. Whereas with Flutter, you add the screen title at the top so you read the screen in the order it's drawn. Maybe I'm being picky, but I found that a little weird.

Even though Flutter is a bit more wordy, I found it a lot easier to find out what modifiers are available by simply pressing CTRL-SPACE in VSCode. Maybe there is an easy way to find out what  modifiers are available for Text() in Xcode apart from going into the Library Panel and searching (which is a pain) - but I have yet to find it.

Another plus with Flutter is that it's so easy to refactor code. Are your widgets nested too deep? Turn them into a new reusable widget by hitting Refactor and choosing Extract to widget. Want to wrap everything in a new Row? Hit Refactor and choose Wrap with row. Want to get rid of a top level widget? Again, hit Refactor and choose Remove this widget.

Xcode has a refactor option, but doesn't offer as many features. 

In terms of Dart vs. Swift - I have to admit I found them fairly interchangeable and could happily switch between the two. Most of the stuff I am using to build my apps is common across both. All the modern language features like helper functions for lists such as sort, insert, and index functions were common to both and in many cases had the same name.

Flutter Pros

  • Hot reload is amazing. Instant updates on my actual target device makes development so much faster.
  • Great documentation. Not only is the documentation at Fluter.dev awesome, but there is a dedicated YouTube channel with lots of content including the useful Widget of the Week.
  • Lots of great packages at Pub.dev. The odds are, if you need something, there is a package already available. Want to add charting to your app? There's are many packages for that.
  • There is a Widget for literally everything. 
  • Refactoring with VSCode is super easy.
  • Supports a lot more legacy devices and older OS versions.
  • Can make apps for iOS, Android, Mac, Windows and web. My website, www.redspritestudios.com is written in Flutter :-)
  • Flutter is open source. So you can drill down into any piece of code to see how it works. There is no blackbox like Swift.

Flutter Cons

  • Can't build for Apple Watch :-(
  • 2D only. So no chance of doing any activities in 3D.
  • Slightly more verbose compared to SwiftUI.
  • State management is not as clear cut as SwiftUI.
  • I have to type a semi-colon at the end of every line.

Conclusion

I love SwiftUI and have already published apps built with SwiftUI. 

But, for my new app, Flutter has the edge. It has lots of widgets, a large amount of good quality documentation, cross-platform support, and an animation system that does what I need.

More importantly I actually found it a tad faster to build my vertical slice in Flutter. This was probably due to the abundance of quality documentation helping me solve roadblocks quickly, and having a faster feedback loop with hot-reloading.

I plan to get an MVP of the new Brainiversity up as soon as I can, and I will build out the rest of the activities with feedback from users. I'll post again when it's ready to play.

Let me know if I got anything wrong, or if there is a better way of doing things.

Cheers,
John