Functional Programming with Flutter

Flutter uses Dart, which is an Object-Orientated language. Due to this, we can not write an app, in a fully functional programming style. However we can take some patterns and lesson’s learnt from the functional programming space, and apply them to your Flutter applications. You will find Flutter actually fits in well with some Functional Programming concepts, due to its immutable Widget Tree.

Brief Overview of Functional Programming

Functional Programming is a big topic and I’m not going to delve into too much detail, but will give a brief outline, with some links for further research if you desire. While there is no concrete definition of what the main concepts of Functional Programming are, they are generally brought down to these 4 core concepts.

Lambda Calculus

This is what all functional programs essentially come down to. In simple terms, its a system for defining how to compute, based on 3 things. A variable (just like in regular OOP languages), an abstraction (this means a function definition, like a delegate e.g. typedef in Dart, that can be assigned to a variable), then application, passing functions as arguments. As an example:

// Typedef's allow you to create a `Type` for a method signature
typedef bool FunctionName(String value);

// This is the implementation of the method
bool functionName(String value) => true;

// Main app that will run
void mainApp()
{
  // We can assign a function to a variable
  var x = functionName;

  // Pass the variable to a function and get the result
  var result = getValue(x, 'Test');
}

// Here the typedef is used to make it easier to pass functions
bool getValue(FunctionName function, String value)
{
  // We call the function
  return function(value);
}

Lambda Calculus involves more than just this. If you want to learn more you can find more information in Lambda Calculus.

Higher Order Functions

A higher order function is something we just saw in the sample above. It takes a function as an argument and/or returns a function as a result. If it meets either of those criteria, it is a higher order function. This leads to a saying you may be familiar with in the OOP realm, composition over inheritance. Functional programming has no inheritance, and any larger computation is composed of multiple functions and variables.

Immutability

It’s common for developers to be confused in functional programming regarding immutability, thinking it means you can not keep state. If I can’t keep state, how does the app work? The common misconception here, is that functional programming does keep state, its just immutable, meaning it can not be changed. Any time you want to change the state, you need to recreate the state from scratch with new values.

Coming from OOP, at first glance you may be concerned at such wasteful processing, but each language or framework, has its own way of ensuring things run efficiently under the hood. You may notice that this is the exact approach Flutter’s Widget Tree takes. You rebuild the Widget Tree every time the state changes.

No side effects

While no side effects is a broad stroke, as all programs produce side effects, usually side effects are avoided in most of the code. A side effect is when your function mutates a variable passed to it or a variable outside of it, or uses a variable outside. As demonstrated here, any other process can modify the variable value and cause the function to return different values each time it is run.

String value = 'Test';

bool isTest()
{
  return value == 'Test';
}

We do this all the time in OOP, and it leads to a lot of bug fixing. If you have a function, that accepts immutable variables and returns a function or a computed result, and returns the same value every time, with the same inputs, it is said to have no side effects, and is a pure function.

bool isTest(String value)
{
  return value == 'Test';
}

Functional Dart

Lets get into some functional styled programming with Dart.

State Management

A widget tree is immutable and rebuilt every time, but lets look at some things to manage your application state. First, try to make every function static. This will help you avoid keeping state in class instances.

static bool isTest(String value)

Every app has state, and you still need a way to store it. One of the best ways to ensure your state isn’t mutable is to use the immutable attribute and mark your fields as final.

@immutable
abstract class AppState {
    const AppState(this.value);
    final String value;
}

Now you can be assured that this state object doesn’t change in the app. But the next problem you may be asking, is where does this state get stored? You will do something similar to setState. A static method can be created to apply the new state.

class StateGraph {

  // This is private, keeping each state change (optional - you could just overwrite).
   static List _state = new List();

  static apply(AppState state) {
  _state.add(state);
  }

  static IState current() {
    // You can either check for no first entry
    // Or return null.
    return _state[_state.length - 1];
  }
}

Then a simple StateGraph.apply(new AppState('Test')); can be used to apply a new state. While you may be concerned of having a static variable holding all your state, this is exactly what OOP languages do as well, you could say we are just being more honest about it here.

Alternatively, you can do away with classes altogether. Dart allows functions and fields with no class, and this may be a great approach for most of your functions. You can then import each file and name as appropriate.

import 'stateGraph.dart' as StateGraph;

Higher Order Functions and Partial Applications

This is the part where I see immense test-ability and removal of many bugs common in our code. There are no class instances with their own state, doing things in secret, if we want to make a call to an API for example, we need to call a function and pass everything through, including the function that makes the HTTP call. This is where higher order functions and partial applications come into play. Lets follow through on a very simple example.

To call an API we will want to send a Request and receive a Response.

@immutable
class Request {
  final String url;
  final String token;
  const Request(this.url, this.token);
}

@immutable
class Response {
  final String data;  
  final int code;
  const Response(this.data, this.code);
}

// This will contain our state and function through to the http package.
// In this example we are using a JWT token.
@immutable
class ApiState {
  final String token;
  final HttpSend send;
  const ApiState(this.send, this.token);
}

Create a function that sends an HTTP request and a typedef for easier referencing.

typedef Response HttpSend(Request request);

Response send(Request request) {
  // Send request over http

  // Return response - Example
  return Response("error", 404);
}

Now create a place to store the ApiState. How you update this will depend on your app. This will just be hard coded as an example.

// send is the function to send an Http Request as shown above.
static ApiState apiState() => ApiState(send, "token");

A partial application is now what we need to help make this easily usable. A Partial Application is where you create a function that fills in some of the parameters of another function, then returns a function with just what parameters are left to fill in. This login function, returns a LoginRequest, which is a function that accepts username and password.

typedef bool LoginRequest(String username, String password);

class Account {
  static LoginRequest login(ApiState apiState) =>
      (username, password) => _login(apiState.send, apiState.token, username, password);

  static bool _login(
      HttpSend send, String token, String username, String password) {
    var request = Request("/v1/login", token);
    // Add in username and password to request here
    var response = send(request);
    if (response.code == 200) {
      return true;
    } else {
      return false;
    }
  }
}

The final function we will create is accepting the loginRequest function and the username and password.

static bool login(LoginRequest loginRequest, String username, String password) {
    return loginRequest(username, password);
}

Finally, we call the function to get a response.

var result = login(Account.login(StateGraph.apiState()),
                   "username",
                   "password"));

This is certainly a different way of thinking. Every function needs to have values or functions passed into it, to remain pure. The benefit of this approach is its high test-ability, and in my opinion, easier to maintain code. I don’t need to think about other variables and states, everything in my function I need to deal with, is inside this function.

Summary

Functional programming languages enforce many of these principles and contain many other language features to make using these concepts easier. While writing a mostly functional program in Flutter is possible, you need to stick to certain patterns yourself, as the Dart language will let you ignore many of these. Some concepts may also be cumbersome.

Based on similar principles of Functional Programming, the State Graph Architecture for Flutter is a possible solution to helping you develop your Flutter apps, with more Functional Programming concepts.

© 2019 Adam Pedley