These days, with multiple mobile platforms on the market and a wide range of different devices, it can be a huge effort to develop an app that supports everything. So instead of going the native way and developing separate apps for every needed platform there's also the possibility to develop apps cross-platform. This means that you only have a single codebase that then runs on multiple platforms and devices. Today you can choose between mutliple cross-platform SDK's. Below you can find three of them:
"Controlling complexity is the essence of computer programming."
Flutter is Google's open source SDK for developing mobile Apps for iOS and Android. It was released in may 2017 and makes use of Google's Dart programming language. Dart gets compiled to native code and therefore you can not sideload new source-code as you can do with React-Native. Dart is very similar to C++, C# and Java. When you have expereience with one or more of these programming languages you will get into Dart programming quickly. Dart also incorporates a generational garbage collector which means that long living objects will be checked less often than newer objects.
If you do not have any knowledge about Flutter you should first read the Flutter 'Get Started' docs. With that background it will be easier to follow this blog post.
In this blog post I will descibe a little app (Github Repository) I created with Flutter. This app lets your search iTunes for keywords and lists all found tracks. You can then select any track and see more details of the tracks's album. Further you can listen to the track preview and open the tracks website. In the video below you can see how the app looks like.
For the data model I chose Redux. Redux comes as a separate package for Flutter and can be integrated by adding it to the project's pubspec.yaml file.
Data in a Redux based application is stored in a single store which describes the state of the application. The state can be described as the single source of truth and is read-only. You can only change the state by sending an action that describes what data needs to be changed. Once the action gets handled, a new state is being created which represents the changes made by the action. This prevents several hard to debug issues, like race conditions. Further by using actions you can easily enable undo/redo or view a history of changes made to the state. In case of a crash you can even send this history together with the crash in order to reproduce the issue. The functions that actually make the change to the state are called reducers. Reducers take the current state and the action and return the new state which is from then on the new single source of truth.
You create the Redux store simply by passing your State object the initial state instance and a reference to the reducer method.
The reducer method itself just handles all existing actions and creates a new state object based on the action type and data. You can trigger actions by calling Store's dispatch method and passing a corresponding action object.
For easy access of the Redux state in your UI hierarchy, Redux helps with an object of type StoreConnector. StoreConnector being a widget, can be embedded into your UI hierarchy. You can use its converter property to transform the store into a view-model that provides the data needed for your UI. This view-model is then passed to the builder property which then constructs the UI widgets. In the gist below you can see how the StoreConnector is configured to provide a List<TrackItem> parameter to the builder property.
Our app also makes use of iTunes' public REST search API. In order to not block the UI while the REST call is in progress and waits for a response we want to perform this REST call asynchronous. Dart offers asynchronous support via the async/await keywords. These keywords allow you to write code that looks like synchronous code but actually works asynchronously. A good explanation can be found here.
But we also need to update our state with the response data we receive asynchronously. In order to make this work we integrate redux_thunk which enables us to create actions from asynchronous methods.
The code above triggers an asynchronous REST call and waits until it finishes via the await keyword. The result is then passed via UpdateTrackItemsAction to the reducer in order to update the app state. getSearchResult is called via the Store's dispatch function just like any other action.
Navigating to a new view, called route in Flutter, you make use of the Navigator. By calling Navigator.push and providing the target route (view), Flutter will automatically switch to that route using a platform-specific animation. In our case we transition between found tracks and album details back and forth.
iTunes offers an URL where you can download a sound preview clip (30s in length). We make use of this URL by downloading the data from there and then passing it to the Flutter audio player package. We first need to download the clip and write it to a temporary file so that the audio player can read it.
Once the data got written to the file we can start audio playback by calling AudioPlayer.play.
The audio player provides progress updates so that we can display the time the track is already playing. We therefore need to register a listener which receives audio updates regularly and then transforms these updates into actions so that the UI can be updated.
Once you get used to creating UI in a declarative way with Flutter you can make progress very fast. By using the hot reload functionality making changes and verifyng them is just a matter of seconds. I usually have Android and iOS simulators running in parallel so that I can see how my app looks like on both platforms at the same time. I am really impressed how easy it is to get into Flutter development and I will definitely continue using it.