Как отменить текстовое поле onChange в Dart?
Я пытаюсь разработать TextField, который обновляет данные в базе данных Firestore при их изменении. Кажется, что это работает, но мне нужно предотвратить многократное срабатывание события onChange.
В JS я бы использовал lodash _debounce(), но в Dart я не знаю, как это сделать. Я читал некоторые библиотеки debounce, но я не могу понять, как они работают.
Что мой код, это только тест, так что что-то может быть странным:
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class ClientePage extends StatefulWidget {
String idCliente;
ClientePage(this.idCliente);
@override
_ClientePageState createState() => new _ClientePageState();
}
class _ClientePageState extends State<ClientePage> {
TextEditingController nomeTextController = new TextEditingController();
void initState() {
super.initState();
// Start listening to changes
nomeTextController.addListener(((){
_updateNomeCliente(); // <- Prevent this function from run multiple times
}));
}
_updateNomeCliente = (){
print("Aggiorno nome cliente");
Firestore.instance.collection('clienti').document(widget.idCliente).setData( {
"nome" : nomeTextController.text
}, merge: true);
}
@override
Widget build(BuildContext context) {
return new StreamBuilder<DocumentSnapshot>(
stream: Firestore.instance.collection('clienti').document(widget.idCliente).snapshots(),
builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (!snapshot.hasData) return new Text('Loading...');
nomeTextController.text = snapshot.data['nome'];
return new DefaultTabController(
length: 3,
child: new Scaffold(
body: new TabBarView(
children: <Widget>[
new Column(
children: <Widget>[
new Padding(
padding: new EdgeInsets.symmetric(
vertical : 20.00
),
child: new Container(
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new Text(snapshot.data['cognome']),
new Text(snapshot.data['ragionesociale']),
],
),
),
),
new Expanded(
child: new Container(
decoration: new BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20.00),
topRight: Radius.circular(20.00)
),
color: Colors.brown,
),
child: new ListView(
children: <Widget>[
new ListTile(
title: new TextField(
style: new TextStyle(
color: Colors.white70
),
controller: nomeTextController,
decoration: new InputDecoration(labelText: "Nome")
),
)
]
)
),
)
],
),
new Text("La seconda pagina"),
new Text("La terza pagina"),
]
),
appBar: new AppBar(
title: Text(snapshot.data['nome'] + ' oh ' + snapshot.data['cognome']),
bottom: new TabBar(
tabs: <Widget>[
new Tab(text: "Informazioni"), // 1st Tab
new Tab(text: "Schede cliente"), // 2nd Tab
new Tab(text: "Altro"), // 3rd Tab
],
),
),
)
);
},
);
print("Il widget id è");
print(widget.idCliente);
}
}
Ответы
Ответ 1
В статусе виджета объявите контроллер и таймер:
final _searchQuery = new TextEditingController();
Timer _debounce;
Добавить метод прослушивания:
_onSearchChanged() {
if (_debounce?.isActive ?? false) _debounce.cancel();
_debounce = Timer(const Duration(milliseconds: 500), () {
// do something with _searchQuery.text
});
}
Подцепите и снимите трубку с контроллера:
@override
void initState() {
super.initState();
_searchQuery.addListener(_onSearchChanged);
}
@override
void dispose() {
_searchQuery.removeListener(_onSearchChanged);
_searchQuery.dispose();
super.dispose();
}
В дереве сборки свяжите контроллер с TextField:
child: TextField(
controller: _searchQuery,
// ...
)
Ответ 2
Вы можете создать класс Debouncer
, используя Timer
import 'package:flutter/foundation.dart';
import 'dart:async';
class Debouncer {
final int milliseconds;
VoidCallback action;
Timer _timer;
Debouncer({ this.milliseconds });
run(VoidCallback action) {
if (_timer != null) {
_timer.cancel();
}
_timer = Timer(Duration(milliseconds: milliseconds), action);
}
}
Объявите это
final _debouncer = Debouncer(milliseconds: 500);
и вызвать его
onTextChange(String text) {
_debouncer.run(() => print(text));
}
Ответ 3
Использование BehaviorSubject из rxdart lib является хорошим решением.
Он игнорирует изменения, которые произошли в течение X секунд после предыдущего.
final searchOnChange = new BehaviorSubject<String>();
...
TextField(onChanged: _search)
...
void _search(String queryString) {
searchOnChange.add(queryString);
}
void initState() {
searchOnChange.debounceTime(Duration(seconds: 1)).listen((queryString) {
>> request data from your API
});
}
Ответ 4
Вы можете использовать пакет rxdart для создания Observable с использованием потока, а затем отменить его в соответствии с вашими требованиями. Я думаю, эта ссылка поможет вам начать работу.
Ответ 5
простой дебад может быть реализован с использованием метода Future.delayed
bool debounceActive = false;
...
//listener method
onTextChange(String text) async {
if(debounceActive) return null;
debounceActive = true;
await Future.delayed(Duration(milliSeconds:700));
debounceActive = false;
// hit your api here
}
Ответ 6
Как и предполагали другие, реализация пользовательского класса debouncer не так сложна. Вы также можете использовать плагин Flutter, например EasyDebounce.
В вашем случае вы бы использовали это так:
import 'package:easy_debounce/easy_debounce.dart';
...
// Start listening to changes
nomeTextController.addListener(((){
EasyDebounce.debounce(
'_updatenomecliente', // <-- An ID for this debounce operation
Duration(milliseconds: 500), // <-- Adjust Duration to fit your needs
() => _updateNomeCliente()
);
}));
Полное раскрытие: я являюсь автором EasyDebounce.