ios

Why developers should consider Flutter for their next project

Introduction

If you’re a lead engineer starting a new mobile project you will surely have considered the use of cross platform technologies reduce your development time and staffing requirements. The go-to solution for this recently has been either React Native or Xamarin, but now that Flutter is stable and mature it’s fast becoming one of the main contenders. 

So what is Flutter exactly?

Flutter is Google’s platform for building cross-platform mobile applications from a single, compiled codebase. It uses the Dart language for development and can also target the desktop and web from a single codebase. It uses a reactive, component-based development architecture that scales well, especially when used with the Bloc pattern for state management.

So we’re gonna have to learn a new language?

Our experience so far has been that Dart is actually one of the main advantages of using Flutter. It has most of the niceties you’ve come to expect from modern languages such as strong typing, type inference, async/await and the IDE support through VSCode or Android Studio is excellent. You’ll pick it up in no time, especially if you already have JS / Java experience, and you’ll be supported by the excellent first party documentation and tutorials. 

So why use Dart in the first place?

One of the main benefits of Dart is that runs with both JIT (Just In Time) and AOT (Ahead Of Time) compilation. AOT compiled languages like Java and Swift typically require a compilation phase for the whole program before the app can be deployed, which slows down the develop and test cycle. JIT languages like Javascript are compiled just before they’re used, so deployment is rapid but there will be a slower startup speed as the compiler does its work for each file before it’s executed. Because Dart can use both, Flutter benefits from the best of both worlds - rapid ‘hot reload’ deployment during development, and runtime optimisation with AOT compilation for release builds.

Another consideration is that languages like JavaScript need to use the JavaScriptCore engine provided by the platform provider (Google or Apple). For security reasons the JavaScriptCore on iOS does not have access to writable executable memory, which makes perfect sense from a security perspective but severely impacts performance by precluding even JIT compilation.

So why would I use Flutter over React Native?

Flutter follows the same reactive development architecture as React Native and also supports hot reload for rapid development. Flutter developers tend to use the Bloc architecture rather than Redux, which can be the source of a lot of complication in React Native apps. 

React Native renders all of its UI using platform-native widgets, and the only way for it to do this is to use the performance-crippled JavaScriptCore and pass all instructions through the process boundary using a native bridge. Flutter compiles to native code and accesses platform APIs more directly, avoiding the need for the native bridge performance bottleneck. It also uses its own rendering engine, so engaging animations and custom designs are straightforward to implement and move naturally at 60fps.

So will my app feel native to the user after implementing with Flutter?

Flutter has historically shipped with the Material Design widget set as default, which is well thought-out and complete. It makes a great starting point for custom app designs that work well across iOS and Android with minimal platform-specific changes.

More recently the Flutter team have release the Cupertino widget set, which more closely mirrors the UI and interaction paradigms of iOS. The Cupertino widget set is surprisingly complete, and a good option if you plan to spend the extra effort to put together a truly native-feeling app. In practice we find that a sprinkling of Cupertino widgets over a customised Material UI more than enough to please a typical iOS user.

Sounds good, where can I start?

Google has done an excellent job with the developer on-boarding experience. The Flutter environment is straightforward to set up at the command line, and the ‘flutter doctor’ command will check over everything including IDE plugins. The codelabs are a great way to get acquainted with the framework and concepts interactively, and the reference documentation is complete, if a little concise.

Porting an iPad app to macOS Catalina

Introduction

At the recent WWDC 2019, Apple announced that in the current Xcode 11 betas they are bringing a new way to port a native iOS iPad app to macOS. We’ve been keeping an eye on the progress of Apple’s Project Catalyst (previously Project Marzipan) for a while, but now that the functionality is available to developers we decided to give it a try on one of our previous iPad-optimised projects. This article is intended to show you how to port a typical existing iPad app over to macOS Catalina using UIKitForMac.

In theory, all we need to do is select the “Mac” checkbox in the project settings of our existing iPad app project.

Screenshot 2019-07-20 at 10.45.17.png

For a simple app this may well be the case, but for a typical app with lots of Cocoapods dependencies it’s unfortunately not that simple. This article will describe the process I went through to convert an iPad Pro app into a native macOS app that uses Apple’s new UIKitForMac.

CocoaPods Dependency Migration

After selecting the Mac checkbox above I then tried to build the project but got a number of errors for different pods within my project, mainly because these pods had not yet been ported to UIKitForMac or iOS 13. There are also still outstanding issues related to Project Catalyst support within Cocoapods, which means that some of the more complicated pods aren’t easy to work with.

Out of the pods that were causing errors, I decided which pods I needed and which pods the app could run without. The pods that I decided were unnecessary for the initial port were: Crashlytics, Firebase/Analytics, Fabric and JGProgressHUD. I will now go through the pods that were necessary but had issues, and how I fixed each of these pods so that they would run in a native Mac app.

Realm

Realm wouldn’t build for UIKitForMac due to an error:

in .../Pods/Realm/core/librealmcore-ios.a(bptree.o), building for UIKitForMac, but linking in object file built for iOS Simulator, for architecture x86_64”.

This is quite a typical error when porting from iOS to macOS and refers to the lack of available binaries for the UIKitForMac platform. Surprisingly, the UIKitForMac build is actually considered ‘iOS’, but with an architecture of x86_64. Until xCode 11 an x86_64 architecture for iOS would always have been a binary built for the simulator, but this is no longer the case. This seems to have been some of the motivation behind the new .xcframework bundle type, which is explained in this WWDC 2019 talk.

On the Realm GitHub there was an open branch for Xcode-11-2, which I hoped may fix this issue but the same issue persisted. After reading through a ticket on the Realm GitHub with regards to Xcode 11 fixes, I found there was an open feature branch for Swift Package Manager support.

I commented-out the Realm pod in the podfile and opted to add the pod through the Swift Package Manager route. To do this I followed these simple instructions: 

  • Click File -> Swift Packages -> Add Swift Package Dependency

  • Paste the Realm GitHub url 

  • Click Next

  • Select the project you want to add the dependency to

  • Select the branch radio button and copy the branch from the GitHub repository that you want to add to your project.

The result of adding Realm to my project in this way was that I still had 2 pods in my Podfile that depended on Realm. If I left them in my podfile as-is they would re-download the Realm pod as their dependency, and it’s difficult to share Swift Package Manager dependencies with Cocoapods. Therefore I decided that the best option would be to add these 2 pods as Swift Packages instead, keeping the dependency tree within the same package management system.

Creating a Package.swift File for a Cocoapods Depencency

I found it relatively straightforward to create a package.swift file based on what we see in a dependency’s GitHub repository and existing .podspec. Below is an example of the Package.swift file I made for the RxRealmDataSources dependency.

// swift-tools-version:5.1
import PackageDescription

let package = Package(
	name: "RxRealmDataSources",
    // 1
    platforms: [
        .macOS(.v10_15), .iOS(.v12), .tvOS(.v9), .watchOS(.v3)
    ],
    // 2
    products: [
        .library(name: "RxRealmDataSources", targets: ["RxRealmDataSources"])
    ],
    // 3
	dependencies: [
        .package(url: "https://github.com/foresightmobile/RxSwift", .branch("removing-uiwebkit")),
		.package(url: "https://github.com/foresightmobile/RxRealm.git", .branch("removed-realm")),
        .package(url: "https://github.com/realm/realm-cocoa", .branch("tg/spm"))
	],
    // 4
    targets: [
        .target(
            // 5
            name: "RxRealmDataSources", 
            // 6
            dependencies: ["RxRealm", "RxSwift", "RealmSwift", "Realm", "RxCocoa"], 
            // 7
            path: ".", 
            // 8
            sources: ["RxRealmDataSources"]
        )
    ]
)
I184KL0LDI72.2KRankn/aAgel0whoissourceRank136KMore dataSummary reportDiagnosisDensity00n/a

The first two lines are always necessary, the first line tells the compiler which version of Swift Tools we are using and so decides the syntax of the Package.swift file. The rest of the file is the package definition itself.

  1. These are the platforms that you want the Package to be able to build for. Of note, UIKitForMac is actually the iOS platform.

  2. These are the libraries within the Package that you want to be able to use in your apps in an import statement.

  3. These are the external dependencies that are required for the Package to build successfully. Here I used a couple of projects that I had forked and edited myself to make compatible with UIKitForMac. The difference here from .podspec files is that you can specify the branch of a dependency in a Package.swift file whereas in a .podspec file you cannot.

  4. The targets or target in this case includes the details for the code we want to run.

  5. The name here must match the name in the targets array in the library in 2.

  6. These are equivalent to each target in the dependencies from 3. For example, the package with the url “https://github.com/foresightmobile/RxSwift” will have two targets in “RxSwift” and “RxCocoa” and we need both of these targets for our Package.

  7. This is where within the Package we want to start looking for the code. The path “.” means we want to start looking for the code from the root of the project.

  8. This is the folder within the project that contains all the code we want to run.

When changing Package.swift files and pushing changes to GitHub, you may find that Xcode isn’t picking up your updates from the remote repository. if your Package.swift file doesn’t seem to be updating you will need to alternate between two urls to force updates: i.e. switch between “https://github.com/foresightmobile/RxSwift” and “https://github.com/foresightmobile/RxSwift.git” in the dependency settings. This should force Xcode to update its cached package files.

RxRealmDataSources / RxRealm

For these two pods there was no Package.swift file in their repository, which is required to add a dependency as a Swift Package. So I had to fork these two pods and create Package.swift files for each of them here and here.

RxSwift

This pod was using a class called UIWebKit that is not available for UIKitForMac, so I had to fork the project and remove the UIWebKit references from the code. Then later on in the porting process I realised that another pod was relying on RxSwift 4.5 (rather than the latest 5.x), so I had to create another branch with the UIWebKit removed from RxSwift 4.5.

RxDataSources

The most up to date version of this pod used RxSwift 5, but we had another pod that was relying on RxSwift 4.5. Therefore I had to fork this project and make a branch where the Package.swift file was pointing to the version of RxSwift detailed above.

Nuke

Nuke was using a deprecated version of the URLCache initialiser, which meant I had to switch the name of one of the arguments in the initialisation of URLCache from “diskPath” to “directory” and supply a URL instead of a string. I also made use of an #if targetEnvironment statement here to determine whether to use “diskPath” or “directory”, depending on whether the target build environment is UIKitForMac or not. This means the dependency will still run on iPad as well as Mac.

    public static let sharedUrlCache = { () -> URLCache in
        #if targetEnvironment(UIKitForMac)
            return URLCache(
                memoryCapacity: 0,
                diskCapacity: 150 * 1024 * 1024, // 150 MB
                directory: URL(fileURLWithPath: cachePath))
        #else
            return URLCache(
                memoryCapacity: 0,
                diskCapacity: 150 * 1024 * 1024, // 150 MB
                diskPath: cachePath)
        #endif
    }

More Problems to Fix

…/Pods/Fabric/run: No such file or directory
Command PhaseScriptExecution failed with a nonzero exit code

After fixing issues with the above pods, I was still having an issue with the Fabric pod, which I had commented out of my Podfile earlier. This was because I had a Fabric run script leftover from when Fabric was still in the app. I removed this and the error involving Fabric went away.

Part6-FabricRunScript.png

The final error I was getting was an error that the XCTest framework could not be found. This was because some extra packages had been imported at some point during the Swift Package Manager transition. These extra packages could be found under the “Link with Binary Libraries” section in the “Build Phases” tab of the project settings. Once I removed the packages that I had not added manually (RxBlocking, RxTest), the app ran and I could successfully use it in a window on my Mac and also in the iPad simulator.

macOS Main Window Layout

After getting the app to run in a window on my Mac, it became apparent there was an issue with the main layout and the title bar of the window. This title bar was covering the top part of the screen in the app. To fix this I simply changed the top constraint for the main view element at the top of the page to constrain to the safe area rather than the outside of the window.

Progress Reporting and macOS Interaction

One of the pods I decided to remove was the JGProgressHUD. This was a pod that I could not fix quickly, so an alternative had to be considered. We were using this pod to display the progress of loading and processing data from Contentful. To fit more in-line with macOS interaction paradigms the alternative we decided to pursue was to disable all the buttons on the screen while this data was being downloaded and processed so that a user cannot interrupt this data flow. This was a quick fix and more in-line with macOS interaction paradigms.

Conclusion and Future Work

The intention of this project was to determine the impact of an iPad to macOS port with a real-world example. The porting process was relatively simple, and given a few more weeks I’m sure that a lot of our dependencies would have added Swift Package Manager support and made the transition easier.