Flutter
Используйте SoloListenable<S> из flutter_solo как базовый класс
контроллера. Он наследует Solo<S> и реализует ValueListenable<S>.
Контроллер профиля сохраняет те же состояния и метод load:
import 'dart:async';
import 'package:flutter/material.dart';import 'package:flutter_solo/flutter_solo.dart';
final class ProfileController extends SoloListenable<ProfileState> { final ProfileApi api;
ProfileController(this.api) : super(const Initial());
// ...задачи из быстрого старта...}Экран может владеть контроллером: создайте его в initState и закройте
в dispose. Общий контроллер может находиться в существующем контейнере
зависимостей: provider, get_it или InheritedWidget. За закрытие
отвечает владеющий им код; flutter_solo не предоставляет SoloProvider
и не закрывает контроллеры автоматически.
ValueListenableBuilder перестраивается при изменении состояния.
Чтобы перейти на другой экран или показать сообщение после конкретной
операции, дождитесь исхода её Job в месте вызова. После ожидания
проверьте mounted, прежде чем обращаться к контексту виджета.
Здесь ProfilePage является экраном назначения в приложении:
class ProfileScreen extends StatefulWidget { const ProfileScreen({super.key});
@override State<ProfileScreen> createState() => _ProfileScreenState();}
class _ProfileScreenState extends State<ProfileScreen> { late final ProfileController profile;
@override void initState() { super.initState(); profile = ProfileController(ProfileApi()); }
@override void dispose() { unawaited(profile.close()); super.dispose(); }
Future<void> _open() async { final outcome = await profile.load().done; if (!mounted) { return; } switch (outcome) { case Done(): await Navigator.of(context).push( MaterialPageRoute<void>(builder: (_) => const ProfilePage()), ); case Failed(:final error): ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text('$error'))); case Cancelled(): break; } }
@override Widget build(BuildContext context) => Scaffold( body: Center( child: ValueListenableBuilder<ProfileState>( valueListenable: profile, builder: (context, state, _) => state is Loading ? const CircularProgressIndicator() : ElevatedButton( onPressed: _open, child: const Text('Open profile'), ), ), ), );}value и state ссылаются на один объект. Сеттера значения нет;
Job контроллера выполняют обновления через контекст.
ListenableBuilder и AnimatedBuilder также принимают контроллер,
когда самому билдеру значение состояния не нужно.