State Mutation, Pure Functions, Scoping And Functional Reactive Programming

I recently blogged about No Architecture, where I stated my experience with many architectures, and how eventually all of them twisted or bent under the weight of real life development. In the end, using a particular architecture didn’t result in desired maintainability, a decrease in complexity or an increase in productivity. Choosing a specific architecture does help you towards those goals and keeps developers coding in a specific pattern, however things could be simpler.

State Mutation

State mutation is the main cause of most bugs in mobile apps, and it was because something wasn’t in the correct state when you went to action on it, or changed while you were performing an action on it. Hence most architectures split up the app into various sections, and keep control of certain parts of your state. The view, the model, business logic, services, all separated in different ways to try and keep state well managed.

While these work great in small sample apps, in developing most apps we eventually come across an issue where we need a value passed to another layer or section of the app. Each architecture offers a way to pass values, Redux even does immutability with a large setup cost, but eventually something goes wrong. This simple example shows the immediate dangers of a class keeping its own state.

class MyCustomClass {

  Data _data;

  void init() {
    _data = new Data();
  }

  void reset() {
    _data = null;
  }

  void doSomething() {
    _data.doingSomething();
  }

}

You can add checks, but we all know where that path eventually leads. We can add immutability to objects that keep state, where you have to recreate them each time, though this can cause considerable developer cost in an OOP environment.

So why are we hiding state? I know it doesn’t concern the whole app, but there should be no reason why a caller shouldn’t know the internal state of the class. We’re all the same team here, and not trying to harm to interfere with others, there is no security concern. We might want to keep other developers from changing the state because they don’t realize everything that depends upon it, and that is a valid reason to hide state, but we can overcome this with pure functions.

Pure Functions

Functional programmers will be familiar with this, but its not too common with Object Oriented programmers. A pure function is where there are no side effects. No state is modified outside the scope of the function. If you input certain variables, you will always get the same result back every time.

int add(int a, int b) {
  return a + b;
}

However functions get more complicated than that, and you don’t want to repeat yourself every time, you might want to combine functions. Hence you can create higher order functions. Here is a simple example.

int add(int a, int b) {
  return a + b;
}
typedef int Add(int a, int b);

int addManyNumbers(Add add, int a, int b, int c) {
  return add(add(a, b), c);
}

Instead of making classes, that hide state, make every service, or piece of business logic, a pure function. That effectively makes everything outside of your Flutter pages, state free. Then where do you keep your app state? Just keep it in a file accessible as needed, in the appropriate scope.

Scoping

Having one file with all your variables in, will get messy and hard to maintain. Create files for your state variables, scoped to where they are needed. If they are only needed inside a particular Flutter page, keep them there. If they need to traverse multiple pages, place them in a file accessible to those pages.

You may ask, what happens if a page modifies it or makes it null before another page is about to use it? Well, all I would say, is at least now you know about it, before it was probably hidden in a class and you couldn’t check.

If you need to derive more complicated state’s based on numerous variables, you could can easily create computed states.

String api_accessToken;
String api_refreshToken;
bool get api_isAuthenticated => api_accessToken.isNotEmpty && api_refreshToken.isNotEmpty;

Scoping variables into the correct location helps keep maintainability, and also ensures you are well aware that they values could be anything and change at any time. If you needed explicit checks before assigning values, you would add a setter, to perform those checks before assigning.

This leads another thing, that if state isn’t hidden inside a class, you need to be more descriptive in the variable name, as to what your state actually holds. While you might need to type a few more characters, it helps when other developers read your code in the future.

Functional Reactive Programming

Flutter is reactive, and functional programming doesn’t stop reactive programming. Functional Reactive Programming is a common way to keep things functional and reactive. Streams and StreamBuilder are powerful and easy way to implement this, and RxDart (a functional reactive programming library) can make it even more powerful.

// This is just an example model
enum Pattern { loading, finished }
class PatternData {
  PatternData(this.pattern, this.result);
  final Pattern pattern;
  final int result;
}

// Create your stream
final StreamController _streamController =
      new StreamController();

// Add StreamBuilder Widget
body: new StreamBuilder<PatternData>(
                    stream: _streamController.stream,
                    initialData: PatternData(Pattern.loading, 0),
                    builder: (BuildContext context, AsyncSnapshot<PatternData> snapshot) {
                      if (snapshot.data.pattern == Pattern.loading)
                        return Text('Loading');
                      else
                        return Text('${snapshot.data.result}');
                    }),

// Add things to your stream as needed.
_streamController.add(PatternData(Pattern.loading, 0));
 
_streamController.add(PatternData(Pattern.finished, 1));

Each time the stream is updated, the builder is run and a new widget is created. The stream builder update process calls a pure function, in that you can handle the data given, without any need for referencing state outside the function. Running a pure function off an event (aka Stream) is easy. RxDart can take this even further.

Share on
© 2019 Adam Pedley