Hola, Amigos! Меня зовут Саша Чаплыгин, я — Flutter-dev компании Amiga. Сегодня хочу поделиться с вами переводом полезной статьи о концепции, связанной с асинхронным программированием. Для того чтобы понять эту концепцию, давайте сначала разберёмся, что такое Future.

Future — это асинхронный объект в Dart, который представляет собой значение, доступное в будущем. Оно используется для работы с операциями, которые могут занять некоторое время, например, сетевыми запросами, чтением файлов или любой другой асинхронной операцией. Когда вы вызываете асинхронную операцию, она возвращает Future, который начинает выполняться, а затем завершится, когда операция будет завершена.

Иногда возникает ситуация, когда вам нужно отменить выполнение асинхронной операции, например, если пользователь изменил свой запрос или прервал операцию. Для этого существует понятие «отмены Future». Дальше в статье подробно описываются три метода отмены Future во Flutter и Dart.

Буду рад, если эта статья окажется вам полезной! Больше о Flutter, обновлениях платформы, новостях из мира мобильной разработки, дайджестах мероприятий и реальном опыте мы с коллегами пишем в телеграм-канал Flutter.Много. Присоединяйтесь!

Применение пакета async (рекомендуется)

Пакет async был разработан и выпущен создателями языка программирования Dart. Этот пакет предлагает вспомогательные классы в стиле dart:async для оптимизации асинхронных вычислений. Отменить Future можно с помощью класса CancelableOperation:

var myCancelableFuture = CancelableOperation.fromFuture(
  Future<T> inner,
  { FutureOr onCancel()? }
)
 
// вызовите метод cancel() для отмены future
myCancelableFuture.cancel();

Для большей ясности приведен практический пример ниже.

Полный пример

Предпросмотр приложения

В приложении есть плавающая кнопка, при нажатии на которую запускается асинхронная операция (занимает 5 секунд). Фон кнопки меняется с индиго на красный, а текст — со Start (Начать) на Cancel (Отменить); теперь ее можно использовать для отмены Future.

  • Если нажать на кнопку Cancel за 5 секунд до завершения Future, на экране появится сообщение «Future has been canceled» (Future отменено).

  • В случае бездействия через 5 секунд на экране появится сообщение «Future completed» (Future завершено).

Лучше один раз увидеть, чем сто раз услышать:

Код

1. Установите пакет async, выполнив следующие действия:

flutter pub add async

Затем:

flutter pub get

2. Полный исходный код в main.dart (с пояснениями):

// main.dart
import 'package:flutter/material.dart';
import 'package:async/async.dart';
 
void main() {
  runApp(const MyApp());
}
 
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
	return MaterialApp(
    	// Уберите баннер debug
    	debugShowCheckedModeBanner: false,
    	title: 'KindaCode.com',
    	theme: ThemeData(
      	primarySwatch: Colors.indigo,
    	),
    	home: const HomePage());
  }
}
 
class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);
 
  @override
  State<HomePage> createState() => _HomePageState();
}
 
class _HomePageState extends State<HomePage> {
  // по завершении future вернет часть текста
  Future<String?> _myFuture() async {
	await Future.delayed(const Duration(seconds: 5));
	return 'Future completed';
  }
 
  // сохраните ссылку на CancelableOperation
  CancelableOperation? _myCancelableFuture;
 
  // Future возвращает следующий результат
  String? _text;
 
  // Так вы узнаете, загружается приложение или нет
  bool _isLoading = false;
 
  // Эта функция вызывается при нажатии кнопки Start (Начать)
  void _getData() async {
	setState(() {
  	_isLoading = true;
	});
 
	_myCancelableFuture = CancelableOperation.fromFuture(
  	_myFuture(),
  	onCancel: () => 'Future has been canceld',
	);
	final value = await _myCancelableFuture?.value;
 
	// Обновите UI
	setState(() {
  	_text = value;
  	_isLoading = false;
	});
  }
 
  // эта функция вызывается при нажатии кнопки Cancel (Отменить)
  void _cancelFuture() async {
	final result = await _myCancelableFuture?.cancel();
	setState(() {
  	_text = result;
  	_isLoading = false;
	});
  }
 
  @override
  Widget build(BuildContext context) {
	return Scaffold(
  	appBar: AppBar(title: const Text('KindaCode.com')),
  	body: Center(
    	child: _isLoading
        	? const CircularProgressIndicator()
        	: Text(
            	_text ?? 'Press Start Button',
            	style: const TextStyle(fontSize: 28),
          	),
  	),
  	// Эта кнопка используется для запуска функций _getDate() и _cancelFuture()
  	// вызов функции зависит от переменной _isLoading
  	floatingActionButton: ElevatedButton(
    	onPressed: () => _isLoading ? _cancelFuture() : _getData(),
    	style: ElevatedButton.styleFrom(
        	padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 30),
        	backgroundColor: _isLoading ? Colors.red : Colors.indigo),
    	child: Text(_isLoading ? 'Cancel' : 'Start'),
  	),
	);
  }
}

Применение функции timeout()

Это простой и быстрый метод, но он недостаточно универсален.

Функция timeout() позволяет ограничить время (например, 3 секунды) для Future. Если Future завершится в установленное время, его значение будет возвращено. Если же Future не завершится в установленное время, будет выполнена функция onTimeout:

Future<T> timeout( Duration timeLimit, {FutureOr<T> onTimeout()?} )

Краткий пример

Создайте фиктивный future:

Future<String?> _myFuture() async {
	await Future.delayed(const Duration(seconds: 10));
	return 'Future completed';
}

Установите время ожидания в 3 секунды:

_myFuture().timeout(
  	const Duration(seconds: 3),
  	onTimeout: () =>
      	'The process took too much time to finish. Please try again later',
);

Преобразование Future в поток

Можно использовать метод asStream() класса Future для создания потока, который будет содержать результат исходного Future. Теперь можно отменить подписку на этот поток.

Краткий пример

// не забудьте импортировать следующее
import 'dart:async';
 
// Создайте демо-версию future
Future<dynamic> _loadData() async {
	await Future.delayed(const Duration(seconds: 10));
	return 'Some Data';
}
 
// ссылка на подписку на поток
// чтобы в дальнейшем можно было вызвать call _sub.cancel()
StreamSubscription<dynamic>? _sub;
 
// преобразуйте future в поток
_sub = _loadData().asStream().listen((data) {
	// совершите какое-либо действие с data
	print(data);
 });
 
// отмените подписку на поток
_sub.cancel();

Учтите, что этот пример лишь поверхностно описывает механизм работы. Чтобы это сработало и в вашем проекте, нужно скорректировать этот процесс.

В статье описано несколько способов отмены Future во Flutter. Вы можете выбрать один из них и внедрить в ваше приложение, чтобы сделать его более стабильным и привлекательным при выполнении асинхронных задач.

Присоединяйтесь в телеграм-канал Flutter.Много, мы ведем его совместно с моими коллегами. Если вам интересно больше узнавать про обновления платформы, новости из мира Flutter, дайджесты мероприятий и реальный опыт, то wellcome! Будем рады новичкам в сообществе, нас там уже больше 1400.

Комментарии (0)