Friday, December 15, 2023

Using debounce in Flutter apps for rate-limiting for security & performance

Debouncing refers to limiting how often a function can fire. It's useful to avoid performance issues when a function gets called rapidly, such as in response to user input events like scrolling or typing.

In Flutter, you can debounce a function using Dart streams and the debounce method. This transforms an input stream so each original event is delayed in time, with only the last event propagated downstream within a given duration.

What is Debouncing

Debouncing is a programming practice used to improve performance and stability by limiting how often a function can fire. Without debouncing, functions that execute frequently like event handlers can overload the main thread and cause jank or crashes.

As explained by developer Nidhi Sorathiya, "Debouncing enforces that a function not be called again until a certain amount of time has passed without it being called. As in "execute this function only if 100 milliseconds have passed without it being called."

Why Use Debounce In Your Flutter App?

There are some key reasons debouncing is useful:

  • Avoid constant stream of function calls hogging resources
  • Prevent jank from overworked main thread
  • Wait for user to finish input before executing logic
  • Allow time for processing without new triggers queued

For example, rapidly typing text in a search box can trigger constant suggestions before user is done typing. Debouncing ensures suggestions only trigger after typing stops for a set duration.

Implementing Debounce in Flutter

This is how you can structure your Flutter code to take advantage of the debounce mechanism:

  
    String text ="";

    final _debouncer = Debouncer(milliseconds: 1000);

    void getSuggestions() {
      // Do heavy task 
    }

    void textChanged(String input) {

      text = input;
      _debouncer.run(() {
        getSuggestions();
      });
    }
  

The Debouncer class manages the timing and execution. Each text change resets the timer, and only after 1 second (1000ms) of no new triggers will getSuggestions() run.

This avoids overloading from rapid inputs.

Other Examples Of Debounce Use In Flutter Apps

The concepts applies similarly for other streams like scroll events:

  
    final _debouncer = Debouncer(milliseconds: 100);

    void onScroll() {
      // Heavy scroll handler tasks
    }

    NotificationListener<ScrollNotification>(
      onNotification: (notification) {
        _debouncer.run(() {
             onScroll(); 
        });
      } 
    )
  

Debouncing is useful in many places that functions can be called repetitively more than needed. Applying it appropriately can optimize Flutter app performance.

Rate-limiting in Flutter using debounce

The debounce method also provides a way to rate limit how often a function call happens over time. This establishes a set frequency that functions trigger rather than allowing them to run constantly.

Rate-limiting is important for several key reasons:

  1. Avoid overloading APIs or servers: If you allow unlimited API calls, this risks flooding a server with requests and overloading resources, which is as much a security concern as it is a performance and therefore service quality issue. Rate limiting ensures a set maximum frequency of calls that stays within server limits and restricts attackers ability to abuse your resources to enjoy fast and unlimited attack vectors.
  2. Prevent crashing or instability from spikes: A sudden spike in function calls or data processing could briefly overwhelm an app and cause crashes, lag, or failed requests. Rate limiting smooths out spikes for more stable performance.
  3. Improve user experience with more consistent feedback cycles: With unlimited rapid calls, the feedback loop to the user interface could be erratic, with freezing, delays, stacked up responses, etc. Rate limiting streams events for smoother UI updates.
  4. Balance accuracy of data with performance: Some data like user input or scroll positions gets sampled many times per second accurately but doesn't need that frequency of processing. Rate limiting reduces accuracy slightly but majorly cuts back on unnecessary function triggers.
  5. Conserve battery/CPU resources on mobile devices: Frequent wireless data exchange, network requests, sensor sampling, and computation/processing drains mobile resources quickly. Enforcing rate limits allows mobiles to conserve resources.

For example, you may want to limit an analytics tracker to only send events every 2 seconds at most:

  
    Stream<double> positionStream; // User scroll position  

    final _debouncer = Debouncer(milliseconds: 2000); 

    void logAnalyticsEvent(double position) {
      // Send scroll event to analytics
    }

    void onScrollMove(double offset) {

      positionStream = offset;

      _debouncer.run(() {
        logAnalyticsEvent(positionStream); 
      });

    }
  

Now analytics events are queued on scroll changes, but logAnalyticsEvent is rate limited to trigger max once per 2 seconds. This batches events over time rather than spamming with constant triggers.

Another common use case is limiting network calls that retrieve data. Here is an example fetching suggestions from a server no more than once per second when text changes:

  
    final _debouncer = Debouncer(milliseconds: 1000);

    void fetchServerSuggestions(String query) {
      // Call API for suggestions  
    }

    void onQueryChange(String newQuery) {

      queryText = newQuery;

      _debouncer.run(() {
        fetchServerSuggestions(queryText);
      }); 

    }
  

So the debounce method provides an easy way in Flutter to implement both debouncing and rate limiting patterns useful for optimizing performance.

Other Flutter packages that can help you implement rate-limiting

If you don't want to implement your own debounce methods to implement rate limiting then you can consider these Dart packages (remember, I've not used or vetted these packages, so test them thoroughly before using them in prod):

  • flutter-debouncer is a "Flutter plugin for debouncing can be used to simplify the implementation of debouncing logic in Flutter applications" with a GPL-3.0 license.
  • debounce_throttle 2.0.0 is a "debouncer and throttle that works with Futures, Streams, and callbacks" with an MIT license.

These packages while having decent "popularity" stats haven't been updated in almost 2 years so inspect them thoroughly in a sandbox environment before using them.

How to test security of APIs in your Flutter app?

There are very few tools that allow software developers to protect their APIs in Flutter apps against hackers - without the help of specialist application security experts.

However, Cyber Chief is one such tool that allows you to run regular vulnerability scans with a automated API security tool.

Start your free trial of Cyber Chief now to see not only how it can help to keep attackers out, but also to see how you can ensure that you ship every release with zero known vulnerabilities. 

Or, if you prefer to have an expert-vetted vulnerability assessment performed on your Node.js application you can order the vulnerability assessment from here. Each vulnerability assessment report comes with:

  • Results from scanning your application for the presence of OWASP Top 10 + SANS CWE 25 + thousands of other vulnerabilities.
  • A detailed description of the vulnerabilities found.
  • A risk level for each vulnerability, so you know which GraphQL endpoints to fix first.
  • Best-practice fixes for each vulnerability, including code snippets where relevant.
  • One-month free access to our Cyber Chief API & web application security testing tool.
  • Email support from our application security experts.

Which option do you prefer?