如何在Dart中防抖Textfield onChange事件?

120

我正在尝试开发一个文本字段,当它们更改时更新 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);
    
  }
}
14个回答

1
这是关于预防抖动的我的两分意见,保留HTML标记不解释。
import 'dart:async';

/// Used to debounce function call.
/// That means [runnable] function will be called at most once per [delay].
class Debouncer {
  int _lastTime;
  Timer _timer;
  Duration delay;

  Debouncer(this.delay)
      : _lastTime = DateTime.now().millisecondsSinceEpoch;
  
  run(Function runnable) {
    _timer?.cancel();

    final current = DateTime.now().millisecondsSinceEpoch;
    final delta = current - _lastTime;

    // If elapsed time is bigger than [delayInMs] threshold -
    // call function immediately.
    if (delta > delay.inMilliseconds) {
      _lastTime = current;
      runnable();
    } else {
      // Elapsed time is less then [delayInMs] threshold -
      // setup the timer
      _timer = Timer(delay, runnable);
    }
  }
}

0
我写了一个易于使用的Debouncer类,可以与async/await一起使用。 这对于自动完成小部件非常适用,并且非常灵活 - 在防抖后,您可以运行任何您想要的内容。
import 'dart:async';

class Debouncer<Return> {

  Duration debounceDuration;
  
  Map<DateTime, Completer> _completers = {};
  Timer? _timer;

  Debouncer(this.debounceDuration);

  Future<Return?> run(Future<Return> Function() toRun) async {
    _timer?.cancel();
    _completers.entries
        .map((e) => e.value)
        .forEach((completer) {
          completer.completeError(_CancelException());
        });
    print('❕ Cancelling previous completer and timer…');

    final now = DateTime.now();
    _completers[now] = Completer();
    _timer = Timer(debounceDuration, () {
      _completers[now]?.complete();
    });

    try {
      await _completers[now]?.future;
      _completers.remove(now);
      print('✅ Will execute toRun…');
      return await toRun();
    } catch (exception) {
      print('❌ Cancelled!');
      _completers.remove(now);
      return null;
    }
  }

}

class _CancelException implements Exception {
  const _CancelException() : super();
}

使用方法

final _debouncer = Debouncer<GeocodingResponse>(Duration(milliseconds: 330));

// This can get called many times in quick succession,
// but it will only actually execute the query after 330 milliseconds have elapsed since the last call.
final response = await _debouncer.run(() {
  return network.geocode(query);
});

我在我的博客文章中详细解释了解决方案,并附上了演示。

0
Timer? debouncer;

startDebouncer() {
    debouncer = Timer(const Duration(milliseconds: 600), () {//set your desired duration
      //perform your logic here
    });
  }

resetDebouncer() {
    debouncer?.cancel();
    startTimer();
  }

现在在您的文本字段的onChanged回调中调用resetDebouncer方法。别忘了导入dart:async

0

https://dev59.com/L1QK5IYBdhLWcg3wefz_#62246032 是一个不错的解决方案,但如果你正在使用flutter-hooks,以下方法具有一个优点,即如果小部件被销毁,效果将自动取消。

useDebouncedEffect(Dispose? Function() cb, List<dynamic> deps, int time) {
  final timer = useState<Timer?>(null);
  final dispose = useState<Dispose?>(null);
  final isFirstRender = useState(true);

  useEffect(() {
    if (!isFirstRender.value) {
      timer.value?.cancel();
      timer.value = Timer(Duration(milliseconds: time), () {
        dispose.value = cb();
      });
      return dispose.value;
    } else {
      isFirstRender.value = false;
      return null;
    }
  }, deps);

  useEffect(() {
    return () {
      timer.value?.cancel();
    };
  }, []);
}

然后使用
class DebouncedTextFieldExample extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final query = useState("");

    useDebouncedEffect(() {
      print('Text after debounce: ${textController.text}');
      return null;
    }, [query], 500); // 500 milliseconds debounce time

    return Scaffold(
      appBar: AppBar(title: Text('Debounced Text Field')),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: TextField(
          decoration: InputDecoration(labelText: 'Type something...'),
          onChanged: (value) {
            query.value = value;
          },
        ),
      ),
    );
  }
}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接