J'essaie de développer un TextField qui met à jour les données d'une base de données Firestore lorsqu'elles changent. Cela semble fonctionner, mais je dois empêcher l'événement onChange de se déclencher plusieurs fois.
En JS j'utiliserais lodash _debounce () mais en Dart je ne sais pas comment faire. J'ai lu quelques bibliothèques de debounce mais je ne peux pas comprendre comment elles fonctionnent.
C'est mon code, c'est seulement un test donc quelque chose peut être étrange:
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);
}
}
Dans votre état de widget, déclarez un contrôleur et une minuterie:
final _searchQuery = new TextEditingController();
Timer _debounce;
Ajoutez une méthode d'écoute:
_onSearchChanged() {
if (_debounce?.isActive ?? false) _debounce.cancel();
_debounce = Timer(const Duration(milliseconds: 500), () {
// do something with _searchQuery.text
});
}
Accrochez et décrochez la méthode au contrôleur:
@override
void initState() {
super.initState();
_searchQuery.addListener(_onSearchChanged);
}
@override
void dispose() {
_searchQuery.removeListener(_onSearchChanged);
_searchQuery.dispose();
super.dispose();
}
Dans votre arborescence de construction, liez le contrôleur au TextField:
child: TextField(
controller: _searchQuery,
// ...
)
Vous pouvez créer une classe Debouncer
en utilisant 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);
}
}
Déclarez-le
final _debouncer = Debouncer(milliseconds: 500);
et le déclencher
onTextChange(String text) {
_debouncer.run(() => print(text));
}
L'utilisation de BehaviorSubject à partir de rxdart lib est une bonne solution. Il ignore les changements qui se produisent dans les X secondes de la précédente.
final searchOnChange = new BehaviorSubject<String>();
...
TextField(onChanged: _search)
...
void _search(String queryString) {
searchOnChange.add(queryString);
}
void initState() {
searchOnChange.debounce(Duration(seconds: 1)).listen((queryString) {
>> request data from your API
});
}
Vous pouvez utiliser le package rxdart pour créer un observable à l'aide d'un flux, puis le rebounce selon vos besoins. Je pense que cela link vous aiderait à démarrer.
un simple anti-rebond peut être implémenté en utilisant Future.delayed
méthode
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
}
Comme d'autres l'ont suggéré, l'implémentation d'une classe anti-rebond personnalisée n'est pas si difficile. Vous pouvez également utiliser un plugin Flutter, tel que EasyDebounce .
Dans votre cas, vous l'utiliseriez comme ceci:
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()
);
}));
Divulgation complète: je suis l'auteur de EasyDebounce .