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:
- State: the data.
- Loading: is something happening?
- 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.