Flutter Tutorial

Flutter Intermediate Lesson 59 of 135 2 min read

Triple in Flutter: Simple State with Loading, Error and Data

Learn the Triple pattern (flutter_triple) for Flutter state: a Store with state, loading and error, ScopedBuilder, and an easy async example for beginners.

On this page

Triple (package flutter_triple) is built on one idea: almost every screen is in one of three situations, so a store should always have three parts:

  1. State: the data.
  2. Loading: is something happening?
  3. Error: did something go wrong?

That is the “triple”. You extend a Store, change it with update, setLoading and setError, and show it with ScopedBuilder. It comes from the Brazilian Flutter community (the Flutterando team, who also make Modular).

The package is not available in the online editor, so run the examples in your own project.

Install

flutter pub add flutter_triple

The counter

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

class CounterStore extends Store<int> {
  CounterStore() : super(0); // initial state

  void increment() => update(state + 1);
}

final counter = CounterStore();

void main() => runApp(const MaterialApp(home: CounterPage()));

class CounterPage extends StatelessWidget {
  const CounterPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Triple')),
      body: Center(
        child: ScopedBuilder<CounterStore, int>(
          store: counter,
          onState: (context, count) => Text('$count', style: const TextStyle(fontSize: 48)),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: counter.increment,
        child: const Icon(Icons.add),
      ),
    );
  }
}

Loading and errors: the real reason to use it

class UserStore extends Store<String> {
  UserStore() : super('');

  Future<void> load() async {
    setLoading(true);
    try {
      await Future.delayed(const Duration(seconds: 1)); // API call
      update('Asha Gurung');
    } catch (e) {
      setError(e);
    } finally {
      setLoading(false);
    }
  }
}

// In the widget:
ScopedBuilder<UserStore, String>(
  store: userStore,
  onLoading: (context) => const CircularProgressIndicator(),
  onError: (context, error) => Text('Failed: $error'),
  onState: (context, name) => Text('Hello, $name'),
);

The builder makes the three cases clear, so you never forget the loading spinner or the error message.

Pros and cons

  • Pros: simple mental model; loading and error built in; easy to test.
  • Cons: small community outside Brazil; fewer tutorials in English; the API has changed between major versions, so check the package page for your version.

Try it yourself

Create a ProductsStore extends Store<List<String>> with a load() method that sometimes throws, and show all three states with ScopedBuilder.

Practise in the playground Updated