floop 0.2.3

Floop #

An automatic Widget refresh library for Flutter. Inspired by react-recollect. Alternative approach for state management.

Floop uses an observed 'global store' state management paradigm. Widgets will always display the current value in the store. With Floop it's possible to build a whole interactive app using purely stateless widgets, reducing the complexity overhead of having to learn new concepts or widgets when dealing with data changes that need to be displayed.

Example - How to use #

-class Clicker extends StatelessWidget {
+class Clicker extends StatelessWidget with Floop {

  @override
-Widget build(BuildContext context) {
+Widget buildWithFloop(BuildContext context) {
buildWithFloop
    return Scaffold(
      body: Center(
+          child: Text(floop['clicks'].toString())
        ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
+        onPressed: () => floop['clicks']++    // change 'clicks' from anywhere in the app and the widget will get updated
      ),
    );
  }
}

The example above displays everything required to use the library, keep reading to learn more details, but it's not necessary. Full example here.

Any kind of values can be kept in the [ObservedMap] floop, it implements [Map]. Own [ObservedMap]s can be created instead: Map myStore = ObservedMap().

On Stateful widgets: ...extends State with FloopStateMixin (the Widget itself is left unchanged).

More #

Check the Github repository for up to date Readme, status of the project and for more examples.

https://github.com/icatalud/floop

Floop also exist for Flutter Web. Search for floop_web on pub.dev or check the Github repository.

Advantages of using Floop #

  • There is no learning curve. Other state management libraries (e.g. Redux) require grasping concepts like actions, reducers, observers, etc. Floop is understood immediately, the example above is all there is.

  • It's an intuitive way of building UI. Many developers expect that when they add a component Text(myText), the UI will display whatever value is set on the var myText. That's exactly what happens when doing Text(floop['myText']).

  • When data that is common to the whole app changes (for example user data), automatically all widgets that use that data get updated. One less problem to worry about.

  • Loading and displaying data asynchronously (http requests) simplified. There is no need to use more complex objects like StreamBuilders to handle these cases. Store the async data on floop and conditionally check floop['myData'] == null ? LoadingWidget() : DisplayDataWidget(floop['myData']). Example

  • It's efficient and has good performance (see performance), it only updates the widgets that need to be updated, being an advantage over having few StatefulWidgets that propagate data changes down the Widget tree, causing a whole branch of the tree to update.

  • Easily make simple animations. Animations can be completely decoupled from the component, allowing the common basic stateless components to be used by reading values that will be changing. For example create oscillating values (like colors, position, size), save them in the store and read those values in the widgets buildWithFloop. Animation example. In the play store.

Details #

floop is an instance of [ObservedMap], which implements [Map]. create alternative 'stores' doing Map myStore = ObservedMap() ([Map<K, V>] also possible).

Widgets only subscribe to the keys read during the last build. This means that keys that were read in a previous build that for example are used inside conditions that didn't trigger, will not get "subscribed" to the widget and will therefore not update the widget. This is consistent, if a key is not read during the last build, a change on it's value has no impact on the widget's buildWithFloop output.

[Map] and [List] values will not be stored as they are, but rather they'll get deep copied (automatically). Every [Map] gets copied as an [ObservedMap] instance, while lists get copied using [List.unmodifiable]. Maps and Lists can be stored as they are using the method [ObservedMap.setValueRaw], however by doing so the values inside the Map or List will not update Widgets when they change.

Performance #

As a rule of thumb, including Floop in a Widget can be considered (being pessimistic) as wrapping the Widget with a small Widget. In practice it's better than that, because there is only one widget, so there is not impact that goes beyond the Widget's build time. It also has to be considered that a Widget's build time is far from being the bottleneck of the rendering process in Flutter. Even an order of magnitude of performance hit in the Widgets build time might go unnoticed.

The following performance impact exist on the Widget build time compared to building the same Widget without Floop but reading the same data from a LinkedHashMap (imagine StatefulWidgets that would call setState manually).

In very small Widgets (less than 10 lines in the build method), including Floop implies the following performance hit in build time:

  • x1.15 when Floop is included, but no value is read from an ObservedMap. This implies that including Floop 'just in case' in every Widget is almost negligible.
  • x3 when up to 5 values are read.

In medium Widgets:

  • x1 or negligible performance hit when Floop is included, but no value is read from an ObservedMap.
  • x3 when up to 15 values are read.

If more values are read, the Map read operation starts becoming the bottleneck of Widget's build time even when reading from a regular Map. The more values are read from the Map, the more the performance hit approaches the difference between reading from a Map and an ObservedMap while listening. The performance hit when reading from an ObservedMap in comparison to a regular LinkedHashMap is the following:

  • x1.25 using the map like a regular map.
  • x2.5 while Floop is on 'listening' mode (when a Widget is building).
  • x5 - x8 considering the whole preprocessing (start listening) and post processing (stop listening), which means preparing to listen and commiting all the reads that were 'observed' during the build of a widget.

Benchmarks have quite some variability on each run, it depends if debugging or not, the type of data being written or read, the amount of data, etc. Generally the performance hit is proportional to the amount of data read, converging at around x7 (for 100000 values read).

To reduce the impact in build time, there is an alternative [FloopLight] mixin that can be used, which has the limitation of being able to read from at most one ObservedMap (any number of values can be read) during buildWithFloop. FloopLight should satisfy most use cases, as normally just a few values (one or two) are read from only one [ObservedMap]. It's not the default mixin to make Floop safe in any use case and avoid users from having unexpected errors.

Writing performance #

Writing to an [ObservedMap] has a rough performance hit of x3.2 in all circumstances, unless there are widgets subscribed to the key, in which case there is the extra time that takes Flutter to run [Element.markNeedsBuild]. This time is not counted, since that method would be called anyways to update the Widget.

Collaborate #

Feel free to collaborate, report bugs, give advice or ideas to improve the library.

[0.2.3] - 20/07/2019

Rolls back library dependency.

[0.2.2] - 20/07/2019

Updates to latest version of meta library.

[0.2.1] - 20/07/2019

Exposes the internals of the library.

[0.2.0] - 09/07/2019

Update Docs, readme, reworks file organization.

[0.1.1] - 09/07/2019

Update Readme and Docs. Adds asynchronous data fetch example.

[0.1.1] - 09/07/2019

Update Readme and Docs. Adds asynchronous data fetch example.

[0.1.0] - 09/07/2019

Update Docs, Readme and version to comply with pub mantainance suggestions.

example/example.dart

import 'package:flutter/material.dart';
import 'package:floop/floop.dart';

/// This example exists to satisfy dart publishing requirements.
/// More complete examples are available at the root of the project on Github.
/// https://github.com/icatalud/floop

void main() {
  floop['clicks'] = 0;
  runApp(MaterialApp(
      title: 'Clicker',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Clicker()));
}

class Clicker extends StatelessWidget with Floop {
  @override
  Widget buildWithFloop(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Text(
          floop['clicks'].toString(),
          style: TextStyle(
            color: Colors.red,
            fontSize: 100,
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
          child: Icon(Icons.add), onPressed: () => floop['clicks']++),
    );
  }
}

// The following are alternative implementations.

class ClickerStateful extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => ClickerState();
}

class ClickerState extends State<ClickerStateful> with FloopStateMixin {
  @override
  Widget buildWithFloop(BuildContext context) {
    return Scaffold(
      body: Center(
          child: Text(floop['clicks'].toString(),
              style: TextStyle(
                color: Colors.red,
                fontSize: 100,
              ))),
      floatingActionButton: FloatingActionButton(
          child: Icon(Icons.add), onPressed: () => floop['clicks']++),
    );
  }
}

// Simplest example.

class SimpleClicker extends StatelessWidget with Floop {
  @override
  Widget buildWithFloop(BuildContext context) {
    return Scaffold(
      body: Center(child: Text(floop['clicks'].toString())),
      floatingActionButton: FloatingActionButton(
          child: Icon(Icons.add), onPressed: () => floop['clicks']++),
    );
  }
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  floop: ^0.2.3

2. Install it

You can install packages from the command line:

with Flutter:


$ flutter pub get

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:floop/floop.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
32
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
100
Overall:
Weighted score of the above. [more]
66
Learn more about scoring.

We analyzed this package on Aug 21, 2019, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.4.0
  • pana: 0.12.19
  • Flutter: 1.7.8+hotfix.4

Platforms

Detected platforms: Flutter

References Flutter, and has no conflicting libraries.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.1.0 <3.0.0
flutter 0.0.0
Transitive dependencies
collection 1.14.11 1.14.12
meta 1.1.6 1.1.7
sky_engine 0.0.99
typed_data 1.1.6
vector_math 2.0.8
Dev dependencies
flutter_test
mockito ^4.1.0
pedantic ^1.0.0