问:
Video doesnt play on desktop browsers all the time
Video which for some reason doesn't play most of the time on Desktop.

```<code><video autoplay loop id="video-background" class="video-bg" poster="assets/images/home-vid.jpg">
  <source  src="/assets/videos/home.mp4" type="video/mp4">
</video>
</code>```

Video shoudl autoplay in loop when page is opened..

I tried on FF, Chrome it play once as Private browsing then it stopped playing again..

```<code> $(document).ready(function () {
        $('#fullpage').fullpage({
            verticalCentered: true,
            //sectionsColor: ['#1bbc9b', '#4BBFC3', '#7BAABE'],
            afterRender: function () {
                //playing the video
                $('video').get(0).play();
            }
        });

   });
</code>```

答:
You have to add playsinline autoplay muted loop, Chrome dont allow a video to autostart if it'ss not muted
You can try this:</p>

```<code><video src="{{ asset('/assets/videos/home.mp4' )}}" muted autoplay loop playsinline></video>
And put this js after that:

window.addEventListener('load', async () => {
  let video = document.querySelector('video[muted][autoplay]');
  try {
    await video.play();
  } catch (err) {
    video.controls = true;
  }
});
</code>```

Body:
Video which for some reason doesn't play most of the time on Desktop.

<video autoplay loop id="video-background" class="video-bg" poster="assets/images/home-vid.jpg">
  <source  src="/assets/videos/home.mp4" type="video/mp4">
</video>

Video shoudl autoplay in loop when page is opened..

I tried on FF, Chrome it play once as Private browsing then it stopped playing again..

 $(document).ready(function () {
        $('#fullpage').fullpage({
            verticalCentered: true,
            //sectionsColor: ['#1bbc9b', '#4BBFC3', '#7BAABE'],
            afterRender: function () {
                //playing the video
                $('video').get(0).play();
            }
        });

   });

Body:
You have to add playsinline autoplay muted loop, Chrome dont allow a video to autostart if it'ss not muted
You can try this:

<video src="{{ asset('/assets/videos/home.mp4' )}}" muted autoplay loop playsinline></video>
And put this js after that:

window.addEventListener('load', async () => {
  let video = document.querySelector('video[muted][autoplay]');
  try {
    await video.play();
  } catch (err) {
    video.controls = true;
  }
});


来源: stackexchange

问:
Finding the "fairest" meeting point in an undirected weighted graph
Given an undirected weighted graph, a start node and an end node, is there a way to (without using brute force) find a meeting point in between the start and end nodes such that:

the difference between 1) the distance from start to meeting point and 2) the distance from end to meeting point

is minimized?

I cant seem to think of any way to do this without using brute force.

Thank you

答: You can use Dijkstra's algorithm, starting from both end and start nodes at the same time -- so using one priority queue. Just add as extra information to each node in the queue, whether you reached it from the start or from the end node. When you reach a node that was already visited, check whether it was visited from the "other" side (using that extra information): if so, stop the algorithm, and add up the two distances; the one you had registered on the first visit, and the one of your current, second visit.

ContentLicense: CC BY-SA 4.0


问:
Convert .pem to .crt and .key
Can anyone tell me the correct way/command to extract/convert the certificate .crt and private key .key files from a .pem file? I just read they are interchangable, but not how.

答:
Pre-requisite

openssl  should be installed.
On Windows, if Git Bash is installed, try that! Alternate binaries can be found here.

Step 1: Extract .key from .pem

```<code>openssl pkey -in cert.pem -out cert.key
</code>```

Step 2: Extract .crt from .pem

```<code>openssl crl2pkcs7 -nocrl -certfile cert.pem | openssl pkcs7 -print_certs -out cert.crt
</code>```

ContentLicense: CC BY-SA 3.0

Title: Convert .pem to .crt and .key

Body:
Pre-requisite

openssl  should be installed.
On Windows, if Git Bash is installed, try that! Alternate binaries can be found here.

Step 1: Extract .key from .pem

openssl pkey -in cert.pem -out cert.key

Step 2: Extract .crt from .pem

openssl crl2pkcs7 -nocrl -certfile cert.pem | openssl pkcs7 -print_certs -out cert.crt


问:
PGP file decryption\
I am looking for suggestions on how to use openPGP to decrypt a PGP encrypted file we receive externally. The file is placed on a Google Cloud bucket and I am planning to use a Cloud function to run the decryption.

There are several examples (this and this) on the web with Go and OpenPGP but they are limited to decrypting strings/texts and not files. The file is encrypted using a traditional PGP encrypt command.

I am trying to avoid creating a VM or asking the external agency to change the encryption process.

答:
In your example linked you can see that it decrypts a byte slice []byte. It can be the content from an encrypted file.

You can replace the

```<code>    decrypted, err := pgp.Decrypt(privEntity, encrypted)
    if err != nil {
        t.Error(err)
    }
</code>```

by

```<code>    encrypted, err := ioutil.ReadFile(filename)
    if err != nil {
        t.Error(err)
    }

    decrypted, err := pgp.Decrypt(privEntity, encrypted)
    if err != nil {
        t.Error(err)
    }
</code>```

Title: PGP file decryption\

Body:
In your example linked you can see that it decrypts a byte slice []byte. It can be the content from an encrypted file.

You can replace the

    decrypted, err := pgp.Decrypt(privEntity, encrypted)
    if err != nil {
        t.Error(err)
    }

by

    encrypted, err := ioutil.ReadFile(filename)
    if err != nil {
        t.Error(err)
    }

    decrypted, err := pgp.Decrypt(privEntity, encrypted)
    if err != nil {
        t.Error(err)
    }


问:
How to fetch subset of attachments
I have a CouchDB with documents, which look like this:

```<code>{
  "_id": "000040cc-e3b4-47cc-b051-a5508efb8996",
  "_rev": "1-882d7f88cc2e1e767b55d0c82fb638d2",
  "state": "uploaded",
  "state_since": "2020-02-17T11:20:55.1450252Z"
  // more metadata ...

  "_attachments": {
    "large.jpg": {
      "content_type": "image/jpeg",
      "revpos": 1,
      "digest": "md5-NK7ejYjrErhMAs7tZ4+R8w==",
      "length": 87846,
      "stub": true
    },
     "medium.jpg": {
      ...
    },
    "small.jpg": {
      ...
    }
  }
}
</code>```

Let's assume, I want to query a set of images like this:

```<code>{
   "selector": {
      "state": "uploaded"
   },
   "sort": ["state_since"],
   "limit": 100
}
</code>```

If I want to display the thumbnails of those 100 images, I'd have to iterate through the result list and download the corresponding attachments. This would be 101 requests in total.

I could also do it in one request by specifying, that I want to fetch the documents with attachments. But this would return all (potentially very large) attachments.

I know that I can set the fields property in my query to only return the fields I need. But can I apply this to attachments, too? And if yes: how?

答:
No, there's no way to do what you're requesting. The only ways to fetch a subset of attachments are by fetching them one at a time, or by using the atts_since attribute when fetching a single document, which is intended for use in replication.

Perhaps consider re-designing your documents. Perhaps you can store your thumbnails on a separate document, that only contains thumbnails.

Title: How to fetch subset of attachments

Body:
I have a CouchDB with documents, which look like this:

{
  "_id": "000040cc-e3b4-47cc-b051-a5508efb8996",
  "_rev": "1-882d7f88cc2e1e767b55d0c82fb638d2",
  "state": "uploaded",
  "state_since": "2020-02-17T11:20:55.1450252Z"
  // more metadata ...

  "_attachments": {
    "large.jpg": {
      "content_type": "image/jpeg",
      "revpos": 1,
      "digest": "md5-NK7ejYjrErhMAs7tZ4+R8w==",
      "length": 87846,
      "stub": true
    },
     "medium.jpg": {
      ...
    },
    "small.jpg": {
      ...
    }
  }
}

Let's assume, I want to query a set of images like this:

{
   "selector": {
      "state": "uploaded"
   },
   "sort": ["state_since"],
   "limit": 100
}

If I want to display the thumbnails of those 100 images, I'd have to iterate through the result list and download the corresponding attachments. This would be 101 requests in total.

I could also do it in one request by specifying, that I want to fetch the documents with attachments. But this would return all (potentially very large) attachments.

I know that I can set the fields property in my query to only return the fields I need. But can I apply this to attachments, too? And if yes: how?


问:
Clojure: Convert hash-maps key strings to keywords?
I'm pulling data from Redis using Aleph:

```<code>(apply hash-map @(@r [:hgetall (key-medication id)]))
</code>```

The problem is this data comes back with strings for keys, for ex:

```<code>({"name" "Tylenol", "how" "instructions"})
</code>```

When I need it to be:

({:name "Tylenol", :how "instructions})

I was previously creating a new map via:

{ :name (m "name"), :how (m "how")}

But this is inefficient for a large amount of keys.

If there a function that does this? Or do I have to loop through each?

答:
Using the keyword function and reduce-kv.

If I have a map e.g.

```<code> (def test-map {"key1" "val1" "key2" "val2"}
</code>```

I can do

```<code>(reduce-kv
     (fn [m k v]
        (assoc m (keyword k) v))
      {} test-map)

 => {:key1 "val1" :key2 "val2"}
</code>```

Body:
I'm pulling data from Redis using Aleph:

(apply hash-map @(@r [:hgetall (key-medication id)]))

The problem is this data comes back with strings for keys, for ex:

({"name" "Tylenol", "how" "instructions"})

When I need it to be:

({:name "Tylenol", :how "instructions})

I was previously creating a new map via:

{ :name (m "name"), :how (m "how")}

But this is inefficient for a large amount of keys.

If there a function that does this? Or do I have to loop through each?

Body:
Using the keyword function and reduce-kv.

If I have a map e.g.

 (def test-map {"key1" "val1" "key2" "val2"}

I can do

(reduce-kv
     (fn [m k v]
        (assoc m (keyword k) v))
      {} test-map)

 => {:key1 "val1" :key2 "val2"}


问:
How to change only error message color of input form-control
I am trying to style my first website.
I am using Bootstrap 4 and the jquery validation plugin and I am doing a form right now. I have two problems:
First, I only want to change the color of the error message below my input, but my styling changes the input text size/color and the error message text size/color, how can I only change error text below an input?

Second question is, where in my code do I change the Bootstrap form control state, depending on valid or not?

This is my code so far:

$('document').ready(function(){

    $('#registration_form').validate({
        rules: {
            username: {
                required: true,
                maxlength: 10,
                minlength: 3
            },
            email: {
                required: true,
                email: true
            },
            email_con: {
                required: true,
                equalTo: "#email"
            },
            password: {
                required: true,
                minlength: 7
            },
            password_con: {
                required: true,
                equalTo: "#password"
            },
            formCheck: {
                required: true
            },
        }
    });
});```
<pre class="snippet-code-css lang-css prettyprint-override"><code>        .error{
          color: red;
          font-Size: 13px;
        }</code>```

Email* :

<input class="form-control" type="email" name="email" id="email" placeholder="name@mail.com">

```

答:
You can run a snippet in full page and check this answer

<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet"/>

Register

  <form class="needs-validation" novalidate>

      E-mail
      <input type="email" class="form-control" id="email"
             placeholder="E-mail" required>

        Looks good!

        The e-mail is required!

      Password
      <input type="password" class="form-control" id="password"
             placeholder="Password" required minlength="6">

        Great!

        The password must contain at least 6 characters!

      <button class="btn btn-primary" type="submit">Register!</button>

  </form>

```

Body:
I am trying to style my first website.
I am using Bootstrap 4 and the jquery validation plugin and I am doing a form right now. I have two problems:
First, I only want to change the color of the error message below my input, but my styling changes the input text size/color and the error message text size/color, how can I only change error text below an input?

Second question is, where in my code do I change the Bootstrap form control state, depending on valid or not?

This is my code so far:

$('document').ready(function(){

    $('#registration_form').validate({
        rules: {
            username: {
                required: true,
                maxlength: 10,
                minlength: 3
            },
            email: {
                required: true,
                email: true
            },
            email_con: {
                required: true,
                equalTo: "#email"
            },
            password: {
                required: true,
                minlength: 7
            },
            password_con: {
                required: true,
                equalTo: "#password"
            },
            formCheck: {
                required: true
            },
        }
    });
});

        .error{
          color: red;
          font-Size: 13px;
        }

Email* :

<input class="form-control" type="email" name="email" id="email" placeholder="name@mail.com">

Body:
You can run a snippet in full page and check this answer

<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet"/>
<div class="container">
  <p class="h1">Register</p>

  <form class="needs-validation" novalidate>
    <div class="form-group">
      <label for="email">E-mail</label>
      <input type="email" class="form-control" id="email"
             placeholder="E-mail" required>
      <div class="valid-feedback">
        Looks good!
      </div>
      <div class="invalid-feedback">
        The e-mail is required!
      </div>
    </div>

    <div class="form-group">
      <label for="password">Password</label>
      <input type="password" class="form-control" id="password"
             placeholder="Password" required minlength="6">
      <div class="valid-feedback">
        Great!
      </div>
      <div class="invalid-feedback">
        The password must contain at least 6 characters!
      </div>
    </div>

    <div class="form-group mt-3">
      <button class="btn btn-primary" type="submit">Register!</button>
    </div>
  </form>
</div>

<script>
  (function() {
    window.addEventListener('load', function() {
      var forms = document.getElementsByClassName('needs-validation');

      var validation = Array.prototype.filter.call(forms, function(form) {
        form.addEventListener('submit', function(event) {
          if (form.checkValidity() === false) {
            event.preventDefault();
            event.stopPropagation();
          }
          form.classList.add('was-validated');
        }, false);
      });
    }, false);
  })();
</script>


问:
Implementing Ecwid on a Angular 8 Application with script tags
I'm working on a website based on the latest Angular 8 version and I need to implement a Ecwid e-shop page on one of my components with the following code generated by Ecwid:

```<code><div id="my-store-24629028"></div>
<div>
  <script
    data-cfasync="false"
    type="text/javascript"
    src="https://app.ecwid.com/script.js?24629028&data_platform=code&data_date=2020-02-17"
    charset="utf-8"
  ></script>
  <script type="text/javascript">
    xProductBrowser(
      'categoriesPerRow=3',
      'views=grid(20,3) list(60) table(60)',
      'categoryView=grid',
      'searchView=list',
      'id=my-store-24629028',
    );
  </script>
</div>
</code>```

Angular generates the component and the

 section but none of the scripts is rendered.. and so my e-shop page isn't rendered. From what I could find online it's a design choice from the Angular Team but isn't there any option or solution to disable this ?

The closest solution for the first script I've found is this stackoverflow thread but I don't see how to handle the second script tag Ecwid generates..

UPDATE

TS File:

```<code>import { Component, Renderer2, OnInit, Inject } from '@angular/core';
import { DOCUMENT } from '@angular/common';

@Component({
  selector: 'app-shop',
  templateUrl: './shop.component.html',
  styleUrls: ['./shop.component.scss'],
})
export class ShopComponent implements OnInit {
  constructor(private renderer2: Renderer2, @Inject(DOCUMENT) private document: Document) {}

  public ngOnInit() {
    const script = this.renderer2.createElement('script');
    script.type = `text/javascript`;
    script.src = 'https://app.ecwid.com/script.js?24629028&data_platform=code&data_date=2020-02-17';

    const script2 = this.renderer2.createElement('script');
    script2.type = `text/javascript`;
    script2.text = `
      xProductBrowser(
        'categoriesPerRow=3',
        'views=grid(20,3) list(60) table(60)',
        'categoryView=grid',
        'searchView=list',
        'id=my-store-24629028',
      );
        `;

    this.renderer2.appendChild(this.document.getElementById('toto'), script);
    this.renderer2.appendChild(this.document.getElementById('toto'), script2);
  }
}
</code>```

After following @Supporterino advices I managed to inject both scripts on the HTML sadly I'm now facing a variable not defined error:

```<code>Uncaught ReferenceError: xProductBrowser is not defined
    at <anonymous>:2:7
    at EmulatedEncapsulationDomRenderer2.push../node_modules/@angular/platform-browser/fesm5/platform-browser.js.DefaultDomRenderer2.appendChild (platform-browser.js:1100)
    at DebugRenderer2.push../node_modules/@angular/core/fesm5/core.js.DebugRenderer2.appendChild (core.js:30407)
    at ShopComponent.push../src/app/pages/shop/shop.component.ts.ShopComponent.ngOnInit (shop.component.ts:30)
    at checkAndUpdateDirectiveInline (core.js:21096)
    at checkAndUpdateNodeInline (core.js:29494)
    at checkAndUpdateNode (core.js:29456)
    at debugCheckAndUpdateNode (core.js:30090)
    at debugCheckDirectivesFn (core.js:30050)
    at Object.eval [as updateDirectives] (ShopComponent_Host.ngfactory.js? [sm]:1)
</code>```

SOLUTION

So after a few hours trying to handle the script loading @juvs anwser helped getting this implementation to finally work !

Final TS File

```<code>import { Component, Renderer2, OnInit, Inject } from '@angular/core';
import { DOCUMENT } from '@angular/common';

@Component({
  selector: 'app-shop',
  templateUrl: './shop.component.html',
  styleUrls: ['./shop.component.scss'],
})
export class ShopComponent implements OnInit {
  constructor(private renderer2: Renderer2, @Inject(DOCUMENT) private document: Document) {}

  public ngOnInit() {
    const storeId = 24629028;
    const script = this.renderer2.createElement('script');
    script.setAttribute('type', 'text/javascript');
    script.setAttribute('charset', 'utf-8');
    script.setAttribute('data-cfasync', 'false');
    script.setAttribute('src', `https://app.ecwid.com/script.js?${storeId}&data_platform=code&data_date=2020-02-17`);
    script.onload = this.injectEcwidProductBrowser(storeId);

    this.renderer2.appendChild(this.document.getElementById('ecwidScriptsSection'), script);
  }

  private injectEcwidProductBrowser(storeId) {
    return () => {
      const ecwidBrowserScript = document.createElement('script');
      ecwidBrowserScript.setAttribute('type', 'text/javascript');
      ecwidBrowserScript.setAttribute('charset', 'utf-8');
      ecwidBrowserScript.text = `xProductBrowser("categoriesPerRow=3","views=grid(20,3) list(60) table(60)","categoryView=grid","searchView=list","id=my-store-${storeId}");`;
      document.head.appendChild(ecwidBrowserScript);
    };
  }
}
</code>```

Final HTML File

```<code><div id="my-store-24629028"></div>
<div id="ecwidScriptsSection"></div>
</code>```

Hope this helps someone in the future cause as of right now there's actually not that much information around, even Ecwid support say they don't support Angular2 & AngularJS ..

答:
You should init the Ecwid widget after script.js is loaded.

Example:

```<code>export class AppComponent implements OnInit {
  constructor(
    private _renderer2: Renderer2,
    @Inject(DOCUMENT) private _document: Document
  ) {}

  public ngOnInit() {
    let storeId = 24629028;
    let script = this._renderer2.createElement("script");
    script.setAttribute("type", "text/javascript");
    script.setAttribute("charset", "utf-8");
    script.setAttribute("data-cfasync", "false");
    script.setAttribute(
      "src",
      `https://app.ecwid.com/script.js?${storeId}&data_platform=code&data_date=2020-02-17`
    );
    script.onload = this.injectEcwidProductBrowser(storeId);

    this._renderer2.appendChild(this._document.body, script);
  }

  private injectEcwidProductBrowser(storeId) {
    return function() {
      const ecwidBrowserScript = document.createElement("script");
      ecwidBrowserScript.setAttribute("type", "text/javascript");
      ecwidBrowserScript.setAttribute("charset", "utf-8");
      ecwidBrowserScript.text = `xProductBrowser("categoriesPerRow=3","views=grid(20,3) list(60) table(60)","categoryView=grid","searchView=list","id=my-store-${storeId}");`;
      document.head.appendChild(ecwidBrowserScript);
    };
  }
}
</code>```

Body:
I'm working on a website based on the latest Angular 8 version and I need to implement a Ecwid e-shop page on one of my components with the following code generated by Ecwid:

Angular generates the component and the

 section but none of the scripts is rendered.. and so my e-shop page isn't rendered. From what I could find online it's a design choice from the Angular Team but isn't there any option or solution to disable this ?

The closest solution for the first script I've found is this stackoverflow thread but I don't see how to handle the second script tag Ecwid generates..

UPDATE

TS File:

import { Component, Renderer2, OnInit, Inject } from '@angular/core';
import { DOCUMENT } from '@angular/common';

@Component({
  selector: 'app-shop',
  templateUrl: './shop.component.html',
  styleUrls: ['./shop.component.scss'],
})
export class ShopComponent implements OnInit {
  constructor(private renderer2: Renderer2, @Inject(DOCUMENT) private document: Document) {}

  public ngOnInit() {
    const script = this.renderer2.createElement('script');
    script.type = `text/javascript`;
    script.src = 'https://app.ecwid.com/script.js?24629028&data_platform=code&data_date=2020-02-17';

    const script2 = this.renderer2.createElement('script');
    script2.type = `text/javascript`;
    script2.text = `
      xProductBrowser(
        'categoriesPerRow=3',
        'views=grid(20,3) list(60) table(60)',
        'categoryView=grid',
        'searchView=list',
        'id=my-store-24629028',
      );
        `;

    this.renderer2.appendChild(this.document.getElementById('toto'), script);
    this.renderer2.appendChild(this.document.getElementById('toto'), script2);
  }
}

After following @Supporterino advices I managed to inject both scripts on the HTML sadly I'm now facing a variable not defined error:

Uncaught ReferenceError: xProductBrowser is not defined
    at <anonymous>:2:7
    at EmulatedEncapsulationDomRenderer2.push../node_modules/@angular/platform-browser/fesm5/platform-browser.js.DefaultDomRenderer2.appendChild (platform-browser.js:1100)
    at DebugRenderer2.push../node_modules/@angular/core/fesm5/core.js.DebugRenderer2.appendChild (core.js:30407)
    at ShopComponent.push../src/app/pages/shop/shop.component.ts.ShopComponent.ngOnInit (shop.component.ts:30)
    at checkAndUpdateDirectiveInline (core.js:21096)
    at checkAndUpdateNodeInline (core.js:29494)
    at checkAndUpdateNode (core.js:29456)
    at debugCheckAndUpdateNode (core.js:30090)
    at debugCheckDirectivesFn (core.js:30050)
    at Object.eval [as updateDirectives] (ShopComponent_Host.ngfactory.js? [sm]:1)

SOLUTION

So after a few hours trying to handle the script loading @juvs anwser helped getting this implementation to finally work !

Final TS File

import { Component, Renderer2, OnInit, Inject } from '@angular/core';
import { DOCUMENT } from '@angular/common';

@Component({
  selector: 'app-shop',
  templateUrl: './shop.component.html',
  styleUrls: ['./shop.component.scss'],
})
export class ShopComponent implements OnInit {
  constructor(private renderer2: Renderer2, @Inject(DOCUMENT) private document: Document) {}

  public ngOnInit() {
    const storeId = 24629028;
    const script = this.renderer2.createElement('script');
    script.setAttribute('type', 'text/javascript');
    script.setAttribute('charset', 'utf-8');
    script.setAttribute('data-cfasync', 'false');
    script.setAttribute('src', `https://app.ecwid.com/script.js?${storeId}&data_platform=code&data_date=2020-02-17`);
    script.onload = this.injectEcwidProductBrowser(storeId);

    this.renderer2.appendChild(this.document.getElementById('ecwidScriptsSection'), script);
  }

  private injectEcwidProductBrowser(storeId) {
    return () => {
      const ecwidBrowserScript = document.createElement('script');
      ecwidBrowserScript.setAttribute('type', 'text/javascript');
      ecwidBrowserScript.setAttribute('charset', 'utf-8');
      ecwidBrowserScript.text = `xProductBrowser("categoriesPerRow=3","views=grid(20,3) list(60) table(60)","categoryView=grid","searchView=list","id=my-store-${storeId}");`;
      document.head.appendChild(ecwidBrowserScript);
    };
  }
}

Final HTML File

Hope this helps someone in the future cause as of right now there's actually not that much information around, even Ecwid support say they don't support Angular2 & AngularJS ..

Body:
You should init the Ecwid widget after script.js is loaded.

Example:

export class AppComponent implements OnInit {
  constructor(
    private _renderer2: Renderer2,
    @Inject(DOCUMENT) private _document: Document
  ) {}

  public ngOnInit() {
    let storeId = 24629028;
    let script = this._renderer2.createElement("script");
    script.setAttribute("type", "text/javascript");
    script.setAttribute("charset", "utf-8");
    script.setAttribute("data-cfasync", "false");
    script.setAttribute(
      "src",
      `https://app.ecwid.com/script.js?${storeId}&data_platform=code&data_date=2020-02-17`
    );
    script.onload = this.injectEcwidProductBrowser(storeId);

    this._renderer2.appendChild(this._document.body, script);
  }

  private injectEcwidProductBrowser(storeId) {
    return function() {
      const ecwidBrowserScript = document.createElement("script");
      ecwidBrowserScript.setAttribute("type", "text/javascript");
      ecwidBrowserScript.setAttribute("charset", "utf-8");
      ecwidBrowserScript.text = `xProductBrowser("categoriesPerRow=3","views=grid(20,3) list(60) table(60)","categoryView=grid","searchView=list","id=my-store-${storeId}");`;
      document.head.appendChild(ecwidBrowserScript);
    };
  }
}


答:
If you want to change color of error text message only then write like label.error{color: red;} and follow below html structure.

$(document).ready(function(){
  $('#registration_form').validate({
    rules: {
      email: {
        required: true,
        email: true
      }
    }
  });
});```
<pre class="snippet-code-css lang-css prettyprint-override"><code>label.error{
  color: red;
  font-size: 13px;
}</code>```

<link rel="stylesheet" type="text/css" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">

    <form class="col-sm-12" action="#" id="registration_form">

          Email* :

          <input class="form-control" type="email" name="email" id="email" placeholder="name@mail.com">

          <button type="submit" class="btn btn-success">Submit</button>

    </form>

```

Title: How to change only error message color of input form-control

Body:
If you want to change color of error text message only then write like label.error{color: red;} and follow below html structure.

$(document).ready(function(){
  $('#registration_form').validate({
    rules: {
      email: {
        required: true,
        email: true
      }
    }
  });
});

label.error{
  color: red;
  font-size: 13px;
}

<link rel="stylesheet" type="text/css" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">

<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.1/jquery.validate.min.js"></script>

<div class="container my-2">
  <div class="row">
    <form class="col-sm-12" action="#" id="registration_form">
      <div class="form-row form-group">
        <div class="col-sm-4 text-sm-right">
          <label class="col-form-label" for="email">Email* :</label>
        </div>
        <div class="col-sm-6">
          <input class="form-control" type="email" name="email" id="email" placeholder="name@mail.com">
        </div>
      </div>
      <div class="form-row form-group">
        <div class="col-sm-6 offset-sm-4">
          <button type="submit" class="btn btn-success">Submit</button>
        </div>
      </div>
    </form>
  </div>
</div>


问:
Why is python giving me a fatal error upon downloading a package?
When trying to install Marine-traffic-api im getting an error:</p>

```<code>Installing collected packages: ujson, Marine-Traffic-API
  Running setup.py install for ujson: started
    Running setup.py install for ujson: finished with status 'error'
    Complete output from command /home/benjamin/PycharmProjects/ShipTracking/venv/bin/python -u -c "import setuptools, tokenize;__file__='/tmp/pycharm-packaging/ujson/setup.py';f=getattr(tokenize, 'open', open)(__file__);code=f.read().replace('\r\n', '\n');f.close();exec(compile(code, __file__, 'exec'))" install --record /tmp/pip-record-ys30b_z8/install-record.txt --single-version-externally-managed --compile --install-headers /home/benjamin/PycharmProjects/ShipTracking/venv/include/site/python3.7/ujson:
    Warning: 'classifiers' should be a list, got type 'filter'
    running install
    running build
    running build_ext
    building 'ujson' extension
    creating build
    creating build/temp.linux-x86_64-3.7
    creating build/temp.linux-x86_64-3.7/python
    creating build/temp.linux-x86_64-3.7/lib
    x86_64-linux-gnu-gcc -pthread -DNDEBUG -g -fwrapv -O2 -Wall -g -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -fPIC -I./python -I./lib -I/home/benjamin/PycharmProjects/ShipTracking/venv/include -I/usr/include/python3.7m -c ./python/ujson.c -o build/temp.linux-x86_64-3.7/./python/ujson.o -D_GNU_SOURCE
    In file included from ./python/ujson.c:39:0:
    ./python/py_defines.h:39:10: fatal error: Python.h: No such file or directory
     #include <Python.h>
              ^~~~~~~~~~
    compilation terminated.
    error: command 'x86_64-linux-gnu-gcc' failed with exit status 1
</code>```

答:
Install the needed packages:

```<code>sudo apt-get install build-essential python3 python-dev python3-dev
</code>```

After that, run:

```<code>pip install --no-cache-dir ujson
</code>```

Body:
When trying to install Marine-traffic-api im getting an error:

Installing collected packages: ujson, Marine-Traffic-API
  Running setup.py install for ujson: started
    Running setup.py install for ujson: finished with status 'error'
    Complete output from command /home/benjamin/PycharmProjects/ShipTracking/venv/bin/python -u -c "import setuptools, tokenize;__file__='/tmp/pycharm-packaging/ujson/setup.py';f=getattr(tokenize, 'open', open)(__file__);code=f.read().replace('\r\n', '\n');f.close();exec(compile(code, __file__, 'exec'))" install --record /tmp/pip-record-ys30b_z8/install-record.txt --single-version-externally-managed --compile --install-headers /home/benjamin/PycharmProjects/ShipTracking/venv/include/site/python3.7/ujson:
    Warning: 'classifiers' should be a list, got type 'filter'
    running install
    running build
    running build_ext
    building 'ujson' extension
    creating build
    creating build/temp.linux-x86_64-3.7
    creating build/temp.linux-x86_64-3.7/python
    creating build/temp.linux-x86_64-3.7/lib
    x86_64-linux-gnu-gcc -pthread -DNDEBUG -g -fwrapv -O2 -Wall -g -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -fPIC -I./python -I./lib -I/home/benjamin/PycharmProjects/ShipTracking/venv/include -I/usr/include/python3.7m -c ./python/ujson.c -o build/temp.linux-x86_64-3.7/./python/ujson.o -D_GNU_SOURCE
    In file included from ./python/ujson.c:39:0:
    ./python/py_defines.h:39:10: fatal error: Python.h: No such file or directory
     #include <Python.h>
              ^~~~~~~~~~
    compilation terminated.
    error: command 'x86_64-linux-gnu-gcc' failed with exit status 1

Body:
Install the needed packages:

sudo apt-get install build-essential python3 python-dev python3-dev

After that, run:

pip install --no-cache-dir ujson


问:
Unable to turn factor into numeric
I took in an open access data, but I'm unable to convert column 5 to 50 to numeric.

```<code>file_url <- "https://genelab-data.ndc.nasa.gov/genelab/static/media/dataset/GLDS-138_metabolomics_mx%20367428_NASA_bacteria%20cells_09-2017_submit%20.csv?version=1"
dst1 = 'GLDS-138_metabolomics_mx 367428_NASA_bacteria cells_09-2017_submit.csv'
download.file(file_url, dst1)
Bdata <- read.csv(dst1, stringsAsFactors = FALSE)

Bdata <- t(Bdata)
Bdata <- Bdata[-c(2:7, 114:118),]
Bdata <- Bdata[,-c(1,2,6,8)]
Bdata[1,1:4] <- Bdata[2,1:4]
Bdata <- Bdata[-c(2),]

columnName <- Bdata[1,]
rowName    <- Bdata[,1]
colnames(Bdata) <- columnName
rownames(Bdata) <- rowName
Bdata <- Bdata[-1 ,]
Bdata <- as.data.frame(Bdata)

Bdata[,5:50] <- as.numeric(as.character(Bdata[,5:50]))
</code>```

I've tried numerous method, most of which either coerces NAs or changes the information.

Anyone know how to solve this?

答:
You need this instead of your last line of code:

```<code>Bdata[,5:50] <- as.data.frame(lapply(Bdata[,5:50], function(x) as.numeric(as.character(x))))
</code>```

The reason the code above didn't work is that it tries to turn a dataframe (Bdata[,5:50]) into a numeric vector. But what you actually want is to turn each column into a numeric vector. lapply() applies the passed function to every element of a list (a dataframe actually is a special list) and returns a list. That's why afterwards it needs to be converted into a dataframe again.

Title: Unable to turn factor into numeric

Body:
I took in an open access data, but I'm unable to convert column 5 to 50 to numeric.

file_url <- "https://genelab-data.ndc.nasa.gov/genelab/static/media/dataset/GLDS-138_metabolomics_mx%20367428_NASA_bacteria%20cells_09-2017_submit%20.csv?version=1"
dst1 = 'GLDS-138_metabolomics_mx 367428_NASA_bacteria cells_09-2017_submit.csv'
download.file(file_url, dst1)
Bdata <- read.csv(dst1, stringsAsFactors = FALSE)

Bdata <- t(Bdata)
Bdata <- Bdata[-c(2:7, 114:118),]
Bdata <- Bdata[,-c(1,2,6,8)]
Bdata[1,1:4] <- Bdata[2,1:4]
Bdata <- Bdata[-c(2),]

columnName <- Bdata[1,]
rowName    <- Bdata[,1]
colnames(Bdata) <- columnName
rownames(Bdata) <- rowName
Bdata <- Bdata[-1 ,]
Bdata <- as.data.frame(Bdata)

Bdata[,5:50] <- as.numeric(as.character(Bdata[,5:50]))

I've tried numerous method, most of which either coerces NAs or changes the information.

Anyone know how to solve this?

Body:
You need this instead of your last line of code:

Bdata[,5:50] <- as.data.frame(lapply(Bdata[,5:50], function(x) as.numeric(as.character(x))))

The reason the code above didn't work is that it tries to turn a dataframe (Bdata[,5:50]) into a numeric vector. But what you actually want is to turn each column into a numeric vector. lapply() applies the passed function to every element of a list (a dataframe actually is a special list) and returns a list. That's why afterwards it needs to be converted into a dataframe again.


问:
Pagination / Infinite scrolling in Flutter with caching and realtime invalidation
It's been a long time since I started to search for a Flutter ListView library that will allow me to use pagination in a smart way. Sadly I haven't found anything that meets my criteria:

Smart pagination: the library should't simply increase a list page-by-page but must have a fixed size cache which load and keep in memory only the needed pages in the moment.

Async loading: the library should basically accept a function which returns a future of a list representing a page.

Real-time invalidation: Dart has streams, so somehow the library should use their power to handle invalidation and reload everything needed when data changes in a reactive way.

Basically I wanted something that acted like PagedListAdapter + DataSource.Factory + LiveData in the standard Android library.
I came up with the widget PagedListView:

import 'dart:math';

import 'package:fimber/fimber.dart';
import 'package:flutter/material.dart';

typedef Future<List<T>> PageFuture<T>(int pageIndex);

typedef Widget ItemBuilder<T>(BuildContext context, int index, T entry);
typedef Widget WaitBuilder(BuildContext context);
typedef Widget PlaceholderBuilder(BuildContext context);
typedef Widget EmptyResultBuilder(BuildContext context);
typedef Widget ErrorBuilder(BuildContext context);

class PagedListView<T> extends StatefulWidget {
  final int pageSize;
  final PageFuture<T> pageFuture;
  final Stream<int> countStream;

  final ItemBuilder<T> itemBuilder;
  final WaitBuilder waitBuilder;
  final PlaceholderBuilder placeholderBuilder;
  final EmptyResultBuilder emptyResultBuilder;
  final ErrorBuilder errorBuilder;

  PagedListView(
      {@required this.pageSize,
      @required this.pageFuture,
      @required this.countStream,
      @required this.itemBuilder,
      @required this.waitBuilder,
      @required this.placeholderBuilder,
      @required this.emptyResultBuilder,
      @required this.errorBuilder});

  @override
  _PagedListView<T> createState() => _PagedListView<T>();
}

class _PagedListView<T> extends State<PagedListView<T>> {
  /// Represent the number of cached pages before and after the current page.
  /// If edgeCachePageCount = 1 the total number of cached pages are 3 (one before + current + one after).
  /// TODO calculate from pageSize
  final int edgeCachePageCount = 2;

  int get maxCachedPageCount => (edgeCachePageCount * 2) + 1;

  int currentPage = 0;

  List<T> items;
  Object error;

  int totalCount = -1;

  /// Contains the page indexes which the fetching is started but not completed.
  final progressPages = Set<int>();

  /// Contains the page indexes already retrieved.
  final cachedPages = Set<int>();

  int limitStartIndex = -1;
  int limitEndIndex = -1;

  @override
  void initState() {
    super.initState();
    items = List.filled(widget.pageSize * maxCachedPageCount, null);

    widget.countStream.listen((int count) {
      Fimber.i("Total count changed: $count");
      totalCount = count;

      // Invalidate.
      cachedPages.clear();

      if (count > 0) {
        _fetchPages(PageRequest.SAME);
      }

      setState(() {});
    });
  }

  void _fetchPages(PageRequest pageRequest) {
    Set<int> refreshIndexes = _getRefreshIndexes();
    //Fimber.i("Refresh indexes are $refreshIndexes");
    refreshIndexes.forEach((pageIndex) => _fetchPage(pageIndex, pageRequest));
  }

  Set<int> _getRefreshIndexes() {
    return getRefreshIndexes(maxCachedPageCount, edgeCachePageCount, currentPage, widget.pageSize, totalCount);
  }

  _fetchPage(int index, PageRequest request) {
    if (cachedPages.contains(index)) {
      // We already have this page.
      return;
    }
    if (!progressPages.contains(index)) {
      //Fimber.i("Fetch page $index start");
      progressPages.add(index);
      widget.pageFuture(index).asStream().map((list) => PageResult<T>(index, request, list)).listen(_onData, onError: _onError);
    }
  }

  void _onData(PageResult<T> data) {
    if (data.items != null) {
      if (!_getRefreshIndexes().contains(data.index)) {
        progressPages.remove(data.index);
        //Fimber.i("Skipping invalid page ${data.index}, currentPage = $currentPage, refreshIndexes = ${_getRefreshIndexes()}");
        return;
      }
      //Fimber.i("Fetch page ${data.index} end");

      if (cachedPages.length == maxCachedPageCount) {
        // The cached page count is exceeded, remove the smallest / greatest page.
        if (data.request == PageRequest.NEXT) {
          int smallestPage = cachedPages.reduce(min);
          cachedPages.remove(smallestPage);
          //Fimber.i("Smallest page $smallestPage removed");
        } else if (data.request == PageRequest.PREVIOUS) {
          int greatestPage = cachedPages.reduce(max);
          cachedPages.remove(greatestPage);
          //Fimber.i("Greatest page $greatestPage removed");
        } else {
          int smallestPage = cachedPages.reduce(min);
          int greatestPage = cachedPages.reduce(max);
          int smallestPageDistance = currentPage - smallestPage;
          int greatestPageDistance = greatestPage - currentPage;
          if (smallestPageDistance >= greatestPageDistance) {
            //Fimber.i("Smallest page $smallestPage removed, smallestPageDistance = $smallestPageDistance, greatestPageDistance = $greatestPageDistance");
            cachedPages.remove(smallestPage);
          } else {
            //Fimber.i("Greatest page $greatestPage removed, smallestPageDistance = $smallestPageDistance, greatestPageDistance = $greatestPageDistance");
            cachedPages.remove(greatestPage);
          }
        }
      }
      Set<int> tempCachedPages = cachedPages.toSet()..add(data.index);

      // Put the result in the correct position.
      int startIndex = widget.pageSize * (data.index % maxCachedPageCount);
      items.setAll(startIndex, data.items);
      //Fimber.i("Fetch page ${data.index} end, startIndex = $startIndex");

      limitStartIndex = cachedPages.isEmpty ? 0 : tempCachedPages.reduce(min) * widget.pageSize;
      //Fimber.i("limitStartIndex set to $limitStartIndex");

      limitEndIndex = cachedPages.isEmpty ? -1 : (widget.pageSize * tempCachedPages.reduce(max)) + data.items.length - 1;
      //Fimber.i("limitEndIndex set to $limitEndIndex");

      cachedPages.add(data.index);
      progressPages.remove(data.index);
      //Fimber.i("Fetch page ${data.index} end, startIndex = $startIndex, cached pages ${cachedPages.toList()..sort()}, currentPage = $currentPage");

      setState(() {});
    }
  }

  void _onError(error) {
    this.error = error;
    setState(() {});
  }

  _fetchNewPage(int index) {
    int newPage = index ~/ widget.pageSize;
    PageRequest pageRequest = newPage > currentPage ? PageRequest.NEXT : (newPage < currentPage ? PageRequest.PREVIOUS : PageRequest.SAME);
    /*pageRequest == PageRequest.NEXT
        ? Fimber.i("Fetch next page $newPage")
        : (pageRequest == PageRequest.PREVIOUS ? Fimber.i("Fetch previous page $newPage") : null);*/
    currentPage = newPage;
    _fetchPages(pageRequest);
  }

  @override
  void dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    if (error != null) {
      return widget.errorBuilder(context);
    }
    if (totalCount == -1) {
      return widget.waitBuilder(context);
    }
    if (totalCount == 0) {
      return widget.emptyResultBuilder(context);
    }
    return ListView.builder(
      key: Key("listView"),
      itemCount: totalCount,
      itemBuilder: (context, index) {
        if (index < limitStartIndex || index > limitEndIndex) {
          _fetchNewPage(index);
        }
        return _getListItem(context, index);
      },
    );
  }

  Widget _getListItem(BuildContext context, int realIndex) {
    int pageIndex = realIndex ~/ widget.pageSize;
    if (!cachedPages.contains(pageIndex)) {
      return widget.placeholderBuilder(context);
    }
    int cachePageIndex = pageIndex % maxCachedPageCount;
    int cacheIndex = (cachePageIndex * widget.pageSize) + (realIndex % widget.pageSize);
    return widget.itemBuilder(context, realIndex, items[cacheIndex]);
  }
}

enum PageRequest { NEXT, PREVIOUS, SAME }

class PageResult<T> {
  /// Page index of this data.
  final int index;

  /// Represent the direction from the current page when the request was made.
  final PageRequest request;
  final List<T> items;

  PageResult(this.index, this.request, this.items);
}

Set<int> getRefreshIndexes(int maxCachedPageCount, int edgeCachePageCount, int currentPage, int pageSize, int totalCount) {
  List<int> temp = List.generate(min(maxCachedPageCount, (totalCount ~/ pageSize) + 1), (index) => index + (currentPage - edgeCachePageCount));
  int minIndex = temp.reduce(min);
  if (minIndex < 0) {
    return temp.map((index) => index + minIndex.abs()).toSet();
  }
  int maxIndex = temp.reduce(max);
  int maxPage = totalCount ~/ pageSize;
  if (maxIndex > maxPage) {
    return temp.map((index) => index - (maxIndex - maxPage)).toSet();
  }
  return temp.toSet();
}
```

<p>Since I need to know the total amount of items and handle invalidation I figured to accept a <code>Stream<int></code> which returns the real list size every time data is modified.</p>

<p>This is an example of how it's used:</p>

<pre class="lang-dart prettyprint-override"><code>class MyHomePage extends StatelessWidget {
  final MyDatabase database = MyDatabase();

  MyHomePage({Key key}) : super(key: key);

  Random random = Random.secure();

  @override
  Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
    title: Text("Test"),
    ),
    body: PagedListView(
    pageSize: 10,
    pageFuture: (pageIndex) =>
      Future.delayed(Duration(milliseconds: (random.nextDouble() * 5000).toInt()), () => database.getCategories(10, 10 * pageIndex)),
    countStream: database.countCategories().watchSingle(),
    itemBuilder: _itemBuilder,
    waitBuilder: _waitBuilder,
    placeholderBuilder: _placeholderBuilder,
    emptyResultBuilder: _emptyResultBuilder,
    errorBuilder: _errorBuilder,
    ),
  );
  }

  Widget _itemBuilder(BuildContext context, int index, Category item) => Container(
    height: 60,
    child: Center(
      child: ListTile(
      key: Key(item.id.toString()),
      title: Text(item.description),
      subtitle: Text("id = ${item.id}, index = $index")
      ),
    ),
    );

  Widget _waitBuilder(BuildContext context) => Center(child: CircularProgressIndicator());

  Widget _placeholderBuilder(BuildContext context) => Container(
    height: 60,
    margin: EdgeInsets.all(8),
    child: Center(
    child: CircularProgressIndicator(),
    ));

  Widget _emptyResultBuilder(BuildContext context) => Container(
    margin: EdgeInsets.all(8),
    child: Center(
    child: Text("Empty"),
    ));

  Widget _errorBuilder(BuildContext context) => Container(
    color: Colors.red,
    margin: EdgeInsets.all(8),
    child: Center(
    child: Text("Error"),
    ));
}
</code>```

I'm using SQLite with Moor to retrieve data (https://moor.simonbinder.eu/docs/).

database.getCategories(10, 10 * pageIndex)) is a method returning the Future<List<Category>> representing a page

database.countCategories().watchSingle() is the Stream emitting the list size at every add/update/delete

What do you think?
Am I missing some bugs? Would you done things differently? Maybe in a more simple / elegant / performant way?

Thanks

UPDATE #1

I made a new version based on pskink suggestion using LruMap.

import 'package:fimber/fimber.dart';
import 'package:flutter/material.dart';
import 'package:quiver/cache.dart';
import 'package:quiver/collection.dart';

typedef Future<List<T>> PageFuture<T>(int pageIndex);

typedef Widget ItemBuilder<T>(BuildContext context, int index, T entry);
typedef Widget WaitBuilder(BuildContext context);
typedef Widget PlaceholderBuilder(BuildContext context);
typedef Widget EmptyResultBuilder(BuildContext context);
typedef Widget ErrorBuilder(BuildContext context);

class LazyListView<T> extends StatefulWidget {
  final int pageSize;
  final PageFuture<T> pageFuture;
  final Stream<int> countStream;

  final ItemBuilder<T> itemBuilder;
  final WaitBuilder waitBuilder;
  final PlaceholderBuilder placeholderBuilder;
  final EmptyResultBuilder emptyResultBuilder;
  final ErrorBuilder errorBuilder;

  LazyListView(
      {@required this.pageSize,
      @required this.pageFuture,
      @required this.countStream,
      @required this.itemBuilder,
      @required this.waitBuilder,
      @required this.placeholderBuilder,
      @required this.emptyResultBuilder,
      @required this.errorBuilder});

  @override
  _LazyListView<T> createState() => _LazyListView<T>();
}

class _LazyListView<T> extends State<LazyListView<T>> {
  Map<int, PageResult<T>> map;
  MapCache<int, PageResult<T>> cache;

  Object error;

  int totalCount = -1;

  int currentPage = 0;

  @override
  void initState() {
    super.initState();
    map = LruMap<int, PageResult<T>>(maximumSize: 500 ~/ widget.pageSize);
    cache = MapCache<int, PageResult<T>>(map: map);

    widget.countStream.listen((int count) {
      Fimber.i("Total count changed: $count");
      totalCount = count;

      map.clear();

      setState(() {});
    });
  }

  @override
  Widget build(BuildContext context) {
    if (error != null) {
      return widget.errorBuilder(context);
    }
    if (totalCount == -1) {
      return widget.waitBuilder(context);
    }
    if (totalCount == 0) {
      return widget.emptyResultBuilder(context);
    }
    return ListView.builder(
      key: Key("listView"),
      itemCount: totalCount,
      itemBuilder: (context, index) {
        currentPage = index ~/ widget.pageSize;
        final pageResult = map[currentPage];
        final value = pageResult == null ? null : pageResult.items[index % widget.pageSize];
        final loading = (value == null);
        if (loading) {
          cache.get(currentPage, ifAbsent: _loadPage).then(reload);
          return widget.placeholderBuilder(context);
        }
        return widget.itemBuilder(context, index, value);
      },
    );
  }

  Future<PageResult<T>> _loadPage(int index) {
    Fimber.i("Start fetch page $index");
    return widget.pageFuture(index).then((list) => PageResult(index, list));
  }

  reload(PageResult<T> value) {
    // Avoid calling setState if it's not needed.
    if ((value.index - currentPage).abs() > 2) {
      // ATTENTION: 2 is an arbitrary value, the distance between the current page and the page in the future result should ensure correct refreshing.
      // It should be greater if item widgets have a smaller height, can be smaller if item widgets have a greater height.
      // TODO: make it configurable?
      Fimber.i("Skipping refreshing for result of page ${value.index}, currentPage = $currentPage");
      return;
    }
    setState(() {});
  }
}

class PageResult<T> {
  /// Page index of this data.
  final int index;

  final List<T> items;

  PageResult(this.index, this.items);
}
```

<p><strong>UPDATE #2</strong> based on pskink new comment</p>

<pre class="lang-dart prettyprint-override"><code>import 'package:fimber/fimber.dart';
import 'package:flutter/material.dart';
import 'package:quiver/cache.dart';
import 'package:quiver/collection.dart';

typedef Future<List<T>> PageFuture<T>(int pageIndex);

typedef Widget ItemBuilder<T>(BuildContext context, int index, T entry);
typedef Widget WaitBuilder(BuildContext context);
typedef Widget PlaceholderBuilder(BuildContext context);
typedef Widget EmptyResultBuilder(BuildContext context);
typedef Widget ErrorBuilder(BuildContext context);

class LazyListView<T> extends StatefulWidget {
  final int pageSize;
  final PageFuture<T> pageFuture;
  final Stream<int> countStream;

  final ItemBuilder<T> itemBuilder;
  final WaitBuilder waitBuilder;
  final PlaceholderBuilder placeholderBuilder;
  final EmptyResultBuilder emptyResultBuilder;
  final ErrorBuilder errorBuilder;

  LazyListView(
      {@required this.pageSize,
      @required this.pageFuture,
      @required this.countStream,
      @required this.itemBuilder,
      @required this.waitBuilder,
      @required this.placeholderBuilder,
      @required this.emptyResultBuilder,
      @required this.errorBuilder});

  @override
  _LazyListView<T> createState() => _LazyListView<T>();
}

class _LazyListView<T> extends State<LazyListView<T>> {
  Map<int, PageResult<T>> map;
  MapCache<int, PageResult<T>> cache;

  Object error;

  int totalCount = -1;

  @override
  void initState() {
    super.initState();
    map = LruMap<int, PageResult<T>>(maximumSize: 50 ~/ widget.pageSize);
    cache = MapCache<int, PageResult<T>>(map: map);

    widget.countStream.listen((int count) {
      Fimber.i("Total count changed: $count");
      totalCount = count;

      map.clear();

      setState(() {});
    });
  }

  @override
  Widget build(BuildContext context) {
    if (error != null) {
      return widget.errorBuilder(context);
    }
    if (totalCount == -1) {
      return widget.waitBuilder(context);
    }
    if (totalCount == 0) {
      return widget.emptyResultBuilder(context);
    }
    return ListView.builder(
      key: Key("listView"),
      itemCount: totalCount,
      itemBuilder: (context, index) {
        int currentPage = index ~/ widget.pageSize;
        final pageResult = map[currentPage];
        final value = pageResult == null ? null : pageResult.items[index % widget.pageSize];
        final loading = (value == null);
        if (loading) {
          cache.get(currentPage, ifAbsent: _loadPage).then(_reload);
          return widget.placeholderBuilder(context);
        }
        return widget.itemBuilder(context, index, value);
      },
    );
  }

  Future<PageResult<T>> _loadPage(int index) {
    Fimber.i("Start fetch page $index");
    return widget.pageFuture(index).then((list) => PageResult(index, list));
  }

  _reload(PageResult<T> value) {
    if (value.refreshed) {
      // Avoid calling setState if already called.
      Fimber.i("Skipping refreshing for result of page ${value.index}");
      return;
    }
    setState(() {
      value.refreshed = true;
    });
  }
}

class PageResult<T> {
  /// Page index of this data.
  final int index;

  final List<T> items;
  bool refreshed = false;

  PageResult(this.index, this.items);
}
</code>```

What dou you think?

答:
This is the last version thanks to some very useful suggestions

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:quiver/cache.dart';
import 'package:quiver/collection.dart';

typedef Future<List<T>> PageFuture<T>(int pageIndex);

typedef Widget ItemBuilder<T>(BuildContext context, int index, T entry);
typedef Widget ErrorBuilder(BuildContext context, dynamic error);

class LazyListView<T> extends StatefulWidget {
  final int pageSize;
  final PageFuture<T> pageFuture;
  final Stream<int> countStream;

  final ItemBuilder<T> itemBuilder;
  final IndexedWidgetBuilder placeholderBuilder;
  final WidgetBuilder waitBuilder;
  final WidgetBuilder emptyResultBuilder;
  final ErrorBuilder errorBuilder;
  final double velocityThreshold;

  LazyListView({
    @required this.pageSize,
    @required this.pageFuture,
    @required this.countStream,
    @required this.itemBuilder,
    @required this.placeholderBuilder,
    this.waitBuilder,
    this.emptyResultBuilder,
    this.errorBuilder,
    this.velocityThreshold = 128,
  })  : assert(pageSize > 0),
        assert(pageFuture != null),
        assert(countStream != null),
        assert(itemBuilder != null),
        assert(placeholderBuilder != null),
        assert(velocityThreshold >= 0);

  @override
  _LazyListViewState<T> createState() => _LazyListViewState<T>();
}

class _LazyListViewState<T> extends State<LazyListView<T>> {
  Map<int, PageResult<T>> map;
  MapCache<int, PageResult<T>> cache;
  dynamic error;
  int totalCount = -1;
  bool _frameCallbackInProgress = false;

  @override
  void initState() {
    super.initState();
    _initCache();

    widget.countStream.listen((int count) {
      totalCount = count;
      _initCache();
      setState(() {});
    });
  }

  @override
  Widget build(BuildContext context) {
    //debugPrintBeginFrameBanner = true;
    //debugPrintEndFrameBanner = true;
    //print('build');
    if (error != null && widget.errorBuilder != null) return widget.errorBuilder(context, error);
    if (totalCount == -1 && widget.waitBuilder != null) return widget.waitBuilder(context);
    if (totalCount == 0 && widget.emptyResultBuilder != null) return widget.emptyResultBuilder(context);

    return ListView.builder(
      physics: _LazyListViewPhysics(velocityThreshold: widget.velocityThreshold),
      itemCount: max(totalCount, 0),
      itemBuilder: (context, index) {
        // print('builder $index');
        var page = index ~/ widget.pageSize;
        final pageResult = map[page];
        final value = pageResult?.items?.elementAt(index % widget.pageSize);
        if (value != null) {
          return widget.itemBuilder(context, index, value);
        }

        // print('$index ${Scrollable.of(context).position.activity.velocity}');
        if (!Scrollable.recommendDeferredLoadingForContext(context)) {
          cache.get(page, ifAbsent: _loadPage).then(_reload).catchError(_error);
        } else if (!_frameCallbackInProgress) {
          _frameCallbackInProgress = true;
          SchedulerBinding.instance.scheduleFrameCallback((d) => _deferredReload(context));
        }
        return widget.placeholderBuilder(context, index);
      },
    );
  }

  Future<PageResult<T>> _loadPage(int index) async {
    print('load $index');
    var list = await widget.pageFuture(index);
    return PageResult(index, list);
  }

  void _initCache() {
    map = LruMap<int, PageResult<T>>(maximumSize: 50 ~/ widget.pageSize);
    cache = MapCache<int, PageResult<T>>(map: map);
  }

  void _error(dynamic e, StackTrace stackTrace) {
    if (widget.errorBuilder == null) {
      throw e;
    }
    setState(() => error = e);
  }

  void _reload(PageResult<T> value) => _doReload(value.index);

  void _deferredReload(BuildContext context) {
    print('_deferredReload');
    if (!Scrollable.recommendDeferredLoadingForContext(context)) {
      _frameCallbackInProgress = false;
      _doReload(-1);
    } else {
      SchedulerBinding.instance.scheduleFrameCallback((d) => _deferredReload(context), rescheduling: true);
    }
  }

  void _doReload(int index) {
    // print('reload $index');
    setState(() {});
  }
}

class PageResult<T> {
  /// Page index of this data.
  final int index;
  final List<T> items;

  PageResult(this.index, this.items);
}

class _LazyListViewPhysics extends AlwaysScrollableScrollPhysics {
  final double velocityThreshold;

  _LazyListViewPhysics({
    @required this.velocityThreshold,
    ScrollPhysics parent,
  }) : super(parent: parent);

  @override
  recommendDeferredLoading(double velocity, ScrollMetrics metrics, BuildContext context) {
    // print('velocityThreshold: $velocityThreshold');
    return velocity.abs() > velocityThreshold;
  }

  @override
  _LazyListViewPhysics applyTo(ScrollPhysics ancestor) {
    // print('applyTo($ancestor)');
    return _LazyListViewPhysics(velocityThreshold: velocityThreshold, parent: buildParent(ancestor));
  }
}
```

<p><strong>UPDATE #1</strong></p>

<p>This is a new version which ensures futures don't call <code>setState</code> if the widget is unmounted.</p>

<pre class="lang-dart prettyprint-override"><code>import 'dart:async';
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:quiver/cache.dart';
import 'package:quiver/collection.dart';

typedef Future<List<T>> PageFuture<T>(int pageIndex);

typedef Widget ItemBuilder<T>(BuildContext context, int index, T entry);
typedef Widget ErrorBuilder(BuildContext context, dynamic error);

class LazyListView<T> extends StatefulWidget {
  final int pageSize;
  final PageFuture<T> pageFuture;
  final Stream<int> countStream;

  final ItemBuilder<T> itemBuilder;
  final IndexedWidgetBuilder placeholderBuilder;
  final WidgetBuilder waitBuilder;
  final WidgetBuilder emptyResultBuilder;
  final ErrorBuilder errorBuilder;
  final double velocityThreshold;

  LazyListView({
    @required this.pageSize,
    @required this.pageFuture,
    @required this.countStream,
    @required this.itemBuilder,
    @required this.placeholderBuilder,
    this.waitBuilder,
    this.emptyResultBuilder,
    this.errorBuilder,
    this.velocityThreshold = 128,
  })  : assert(pageSize > 0),
        assert(pageFuture != null),
        assert(countStream != null),
        assert(itemBuilder != null),
        assert(placeholderBuilder != null),
        assert(velocityThreshold >= 0);

  @override
  _LazyListViewState<T> createState() => _LazyListViewState<T>();
}

class _LazyListViewState<T> extends State<LazyListView<T>> {
  Map<int, PageResult<T>> map;
  MapCache<int, PageResult<T>> cache;
  dynamic error;
  int totalCount = -1;
  bool _frameCallbackInProgress = false;

  StreamSubscription<int> countStreamSubscription;

  @override
  void initState() {
    super.initState();
    _initCache();

    countStreamSubscription = widget.countStream.listen((int count) {
      totalCount = count;
      print('totalCount = $totalCount');
      _initCache();
      setState(() {});
    });
  }

  @override
  void dispose() {
    countStreamSubscription.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    //debugPrintBeginFrameBanner = true;
    //debugPrintEndFrameBanner = true;
    //print('build');
    if (error != null && widget.errorBuilder != null) {
      return widget.errorBuilder(context, error);
    }
    if (totalCount == -1 && widget.waitBuilder != null) {
      return widget.waitBuilder(context);
    }
    if (totalCount == 0 && widget.emptyResultBuilder != null) {
      return widget.emptyResultBuilder(context);
    }

    return ListView.builder(
      physics: _LazyListViewPhysics(velocityThreshold: widget.velocityThreshold),
      itemCount: max(totalCount, 0),
      itemBuilder: (context, index) {
        // print('builder $index');
        final page = index ~/ widget.pageSize;
        final pageResult = map[page];
        final value = pageResult?.items?.elementAt(index % widget.pageSize);
        if (value != null) {
          return widget.itemBuilder(context, index, value);
        }

        // print('$index ${Scrollable.of(context).position.activity.velocity}');
        if (!Scrollable.recommendDeferredLoadingForContext(context)) {
          cache.get(page, ifAbsent: _loadPage).then(_reload).catchError(_error);
        } else if (!_frameCallbackInProgress) {
          _frameCallbackInProgress = true;
          SchedulerBinding.instance.scheduleFrameCallback((d) => _deferredReload(context));
        }
        return widget.placeholderBuilder(context, index);
      },
    );
  }

  Future<PageResult<T>> _loadPage(int index) async {
    print('load $index');
    var list = await widget.pageFuture(index);
    return PageResult(index, list);
  }

  void _initCache() {
    map = LruMap<int, PageResult<T>>(maximumSize: 512 ~/ widget.pageSize);
    cache = MapCache<int, PageResult<T>>(map: map);
  }

  void _error(dynamic e, StackTrace stackTrace) {
    if (widget.errorBuilder == null) {
      throw e;
    }
    if (this.mounted) {
      setState(() => error = e);
    }
  }

  void _reload(PageResult<T> value) => _doReload(value.index);

  void _deferredReload(BuildContext context) {
    print('_deferredReload');
    if (!Scrollable.recommendDeferredLoadingForContext(context)) {
      _frameCallbackInProgress = false;
      _doReload(-1);
    } else {
      SchedulerBinding.instance.scheduleFrameCallback((d) => _deferredReload(context), rescheduling: true);
    }
  }

  void _doReload(int index) {
    print('reload $index');
    if (this.mounted) {
      setState(() {});
    }
  }
}

class PageResult<T> {
  /// Page index of this data.
  final int index;
  final List<T> items;

  PageResult(this.index, this.items);
}

class _LazyListViewPhysics extends AlwaysScrollableScrollPhysics {
  final double velocityThreshold;

  _LazyListViewPhysics({
    @required this.velocityThreshold,
    ScrollPhysics parent,
  }) : super(parent: parent);

  @override
  recommendDeferredLoading(double velocity, ScrollMetrics metrics, BuildContext context) {
    // print('velocityThreshold: $velocityThreshold');
    return velocity.abs() > velocityThreshold;
  }

  @override
  _LazyListViewPhysics applyTo(ScrollPhysics ancestor) {
    // print('applyTo($ancestor)');
    return _LazyListViewPhysics(velocityThreshold: velocityThreshold, parent: buildParent(ancestor));
  }
}
</code>```

Anyone with a better idea?

Body:
It's been a long time since I started to search for a Flutter ListView library that will allow me to use pagination in a smart way. Sadly I haven't found anything that meets my criteria:

Smart pagination: the library should't simply increase a list page-by-page but must have a fixed size cache which load and keep in memory only the needed pages in the moment.

Async loading: the library should basically accept a function which returns a future of a list representing a page.

Real-time invalidation: Dart has streams, so somehow the library should use their power to handle invalidation and reload everything needed when data changes in a reactive way.

Basically I wanted something that acted like PagedListAdapter + DataSource.Factory + LiveData in the standard Android library.
I came up with the widget PagedListView:

import 'dart:math';

import 'package:fimber/fimber.dart';
import 'package:flutter/material.dart';

typedef Future<List<T>> PageFuture<T>(int pageIndex);

typedef Widget ItemBuilder<T>(BuildContext context, int index, T entry);
typedef Widget WaitBuilder(BuildContext context);
typedef Widget PlaceholderBuilder(BuildContext context);
typedef Widget EmptyResultBuilder(BuildContext context);
typedef Widget ErrorBuilder(BuildContext context);

class PagedListView<T> extends StatefulWidget {
  final int pageSize;
  final PageFuture<T> pageFuture;
  final Stream<int> countStream;

  final ItemBuilder<T> itemBuilder;
  final WaitBuilder waitBuilder;
  final PlaceholderBuilder placeholderBuilder;
  final EmptyResultBuilder emptyResultBuilder;
  final ErrorBuilder errorBuilder;

  PagedListView(
      {@required this.pageSize,
      @required this.pageFuture,
      @required this.countStream,
      @required this.itemBuilder,
      @required this.waitBuilder,
      @required this.placeholderBuilder,
      @required this.emptyResultBuilder,
      @required this.errorBuilder});

  @override
  _PagedListView<T> createState() => _PagedListView<T>();
}

class _PagedListView<T> extends State<PagedListView<T>> {
  /// Represent the number of cached pages before and after the current page.
  /// If edgeCachePageCount = 1 the total number of cached pages are 3 (one before + current + one after).
  /// TODO calculate from pageSize
  final int edgeCachePageCount = 2;

  int get maxCachedPageCount => (edgeCachePageCount * 2) + 1;

  int currentPage = 0;

  List<T> items;
  Object error;

  int totalCount = -1;

  /// Contains the page indexes which the fetching is started but not completed.
  final progressPages = Set<int>();

  /// Contains the page indexes already retrieved.
  final cachedPages = Set<int>();

  int limitStartIndex = -1;
  int limitEndIndex = -1;

  @override
  void initState() {
    super.initState();
    items = List.filled(widget.pageSize * maxCachedPageCount, null);

    widget.countStream.listen((int count) {
      Fimber.i("Total count changed: $count");
      totalCount = count;

      // Invalidate.
      cachedPages.clear();

      if (count > 0) {
        _fetchPages(PageRequest.SAME);
      }

      setState(() {});
    });
  }

  void _fetchPages(PageRequest pageRequest) {
    Set<int> refreshIndexes = _getRefreshIndexes();
    //Fimber.i("Refresh indexes are $refreshIndexes");
    refreshIndexes.forEach((pageIndex) => _fetchPage(pageIndex, pageRequest));
  }

  Set<int> _getRefreshIndexes() {
    return getRefreshIndexes(maxCachedPageCount, edgeCachePageCount, currentPage, widget.pageSize, totalCount);
  }

  _fetchPage(int index, PageRequest request) {
    if (cachedPages.contains(index)) {
      // We already have this page.
      return;
    }
    if (!progressPages.contains(index)) {
      //Fimber.i("Fetch page $index start");
      progressPages.add(index);
      widget.pageFuture(index).asStream().map((list) => PageResult<T>(index, request, list)).listen(_onData, onError: _onError);
    }
  }

  void _onData(PageResult<T> data) {
    if (data.items != null) {
      if (!_getRefreshIndexes().contains(data.index)) {
        progressPages.remove(data.index);
        //Fimber.i("Skipping invalid page ${data.index}, currentPage = $currentPage, refreshIndexes = ${_getRefreshIndexes()}");
        return;
      }
      //Fimber.i("Fetch page ${data.index} end");

      if (cachedPages.length == maxCachedPageCount) {
        // The cached page count is exceeded, remove the smallest / greatest page.
        if (data.request == PageRequest.NEXT) {
          int smallestPage = cachedPages.reduce(min);
          cachedPages.remove(smallestPage);
          //Fimber.i("Smallest page $smallestPage removed");
        } else if (data.request == PageRequest.PREVIOUS) {
          int greatestPage = cachedPages.reduce(max);
          cachedPages.remove(greatestPage);
          //Fimber.i("Greatest page $greatestPage removed");
        } else {
          int smallestPage = cachedPages.reduce(min);
          int greatestPage = cachedPages.reduce(max);
          int smallestPageDistance = currentPage - smallestPage;
          int greatestPageDistance = greatestPage - currentPage;
          if (smallestPageDistance >= greatestPageDistance) {
            //Fimber.i("Smallest page $smallestPage removed, smallestPageDistance = $smallestPageDistance, greatestPageDistance = $greatestPageDistance");
            cachedPages.remove(smallestPage);
          } else {
            //Fimber.i("Greatest page $greatestPage removed, smallestPageDistance = $smallestPageDistance, greatestPageDistance = $greatestPageDistance");
            cachedPages.remove(greatestPage);
          }
        }
      }
      Set<int> tempCachedPages = cachedPages.toSet()..add(data.index);

      // Put the result in the correct position.
      int startIndex = widget.pageSize * (data.index % maxCachedPageCount);
      items.setAll(startIndex, data.items);
      //Fimber.i("Fetch page ${data.index} end, startIndex = $startIndex");

      limitStartIndex = cachedPages.isEmpty ? 0 : tempCachedPages.reduce(min) * widget.pageSize;
      //Fimber.i("limitStartIndex set to $limitStartIndex");

      limitEndIndex = cachedPages.isEmpty ? -1 : (widget.pageSize * tempCachedPages.reduce(max)) + data.items.length - 1;
      //Fimber.i("limitEndIndex set to $limitEndIndex");

      cachedPages.add(data.index);
      progressPages.remove(data.index);
      //Fimber.i("Fetch page ${data.index} end, startIndex = $startIndex, cached pages ${cachedPages.toList()..sort()}, currentPage = $currentPage");

      setState(() {});
    }
  }

  void _onError(error) {
    this.error = error;
    setState(() {});
  }

  _fetchNewPage(int index) {
    int newPage = index ~/ widget.pageSize;
    PageRequest pageRequest = newPage > currentPage ? PageRequest.NEXT : (newPage < currentPage ? PageRequest.PREVIOUS : PageRequest.SAME);
    /*pageRequest == PageRequest.NEXT
        ? Fimber.i("Fetch next page $newPage")
        : (pageRequest == PageRequest.PREVIOUS ? Fimber.i("Fetch previous page $newPage") : null);*/
    currentPage = newPage;
    _fetchPages(pageRequest);
  }

  @override
  void dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    if (error != null) {
      return widget.errorBuilder(context);
    }
    if (totalCount == -1) {
      return widget.waitBuilder(context);
    }
    if (totalCount == 0) {
      return widget.emptyResultBuilder(context);
    }
    return ListView.builder(
      key: Key("listView"),
      itemCount: totalCount,
      itemBuilder: (context, index) {
        if (index < limitStartIndex || index > limitEndIndex) {
          _fetchNewPage(index);
        }
        return _getListItem(context, index);
      },
    );
  }

  Widget _getListItem(BuildContext context, int realIndex) {
    int pageIndex = realIndex ~/ widget.pageSize;
    if (!cachedPages.contains(pageIndex)) {
      return widget.placeholderBuilder(context);
    }
    int cachePageIndex = pageIndex % maxCachedPageCount;
    int cacheIndex = (cachePageIndex * widget.pageSize) + (realIndex % widget.pageSize);
    return widget.itemBuilder(context, realIndex, items[cacheIndex]);
  }
}

enum PageRequest { NEXT, PREVIOUS, SAME }

class PageResult<T> {
  /// Page index of this data.
  final int index;

  /// Represent the direction from the current page when the request was made.
  final PageRequest request;
  final List<T> items;

  PageResult(this.index, this.request, this.items);
}

Set<int> getRefreshIndexes(int maxCachedPageCount, int edgeCachePageCount, int currentPage, int pageSize, int totalCount) {
  List<int> temp = List.generate(min(maxCachedPageCount, (totalCount ~/ pageSize) + 1), (index) => index + (currentPage - edgeCachePageCount));
  int minIndex = temp.reduce(min);
  if (minIndex < 0) {
    return temp.map((index) => index + minIndex.abs()).toSet();
  }
  int maxIndex = temp.reduce(max);
  int maxPage = totalCount ~/ pageSize;
  if (maxIndex > maxPage) {
    return temp.map((index) => index - (maxIndex - maxPage)).toSet();
  }
  return temp.toSet();
}

Since I need to know the total amount of items and handle invalidation I figured to accept a Stream<int> which returns the real list size every time data is modified.

This is an example of how it's used:

class MyHomePage extends StatelessWidget {
  final MyDatabase database = MyDatabase();

  MyHomePage({Key key}) : super(key: key);

  Random random = Random.secure();

  @override
  Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
    title: Text("Test"),
    ),
    body: PagedListView(
    pageSize: 10,
    pageFuture: (pageIndex) =>
      Future.delayed(Duration(milliseconds: (random.nextDouble() * 5000).toInt()), () => database.getCategories(10, 10 * pageIndex)),
    countStream: database.countCategories().watchSingle(),
    itemBuilder: _itemBuilder,
    waitBuilder: _waitBuilder,
    placeholderBuilder: _placeholderBuilder,
    emptyResultBuilder: _emptyResultBuilder,
    errorBuilder: _errorBuilder,
    ),
  );
  }

  Widget _itemBuilder(BuildContext context, int index, Category item) => Container(
    height: 60,
    child: Center(
      child: ListTile(
      key: Key(item.id.toString()),
      title: Text(item.description),
      subtitle: Text("id = ${item.id}, index = $index")
      ),
    ),
    );

  Widget _waitBuilder(BuildContext context) => Center(child: CircularProgressIndicator());

  Widget _placeholderBuilder(BuildContext context) => Container(
    height: 60,
    margin: EdgeInsets.all(8),
    child: Center(
    child: CircularProgressIndicator(),
    ));

  Widget _emptyResultBuilder(BuildContext context) => Container(
    margin: EdgeInsets.all(8),
    child: Center(
    child: Text("Empty"),
    ));

  Widget _errorBuilder(BuildContext context) => Container(
    color: Colors.red,
    margin: EdgeInsets.all(8),
    child: Center(
    child: Text("Error"),
    ));
}

I'm using SQLite with Moor to retrieve data (https://moor.simonbinder.eu/docs/).

database.getCategories(10, 10 * pageIndex)) is a method returning the Future<List<Category>> representing a page

database.countCategories().watchSingle() is the Stream emitting the list size at every add/update/delete

What do you think?
Am I missing some bugs? Would you done things differently? Maybe in a more simple / elegant / performant way?

Thanks

UPDATE #1

I made a new version based on pskink suggestion using LruMap.

import 'package:fimber/fimber.dart';
import 'package:flutter/material.dart';
import 'package:quiver/cache.dart';
import 'package:quiver/collection.dart';

typedef Future<List<T>> PageFuture<T>(int pageIndex);

typedef Widget ItemBuilder<T>(BuildContext context, int index, T entry);
typedef Widget WaitBuilder(BuildContext context);
typedef Widget PlaceholderBuilder(BuildContext context);
typedef Widget EmptyResultBuilder(BuildContext context);
typedef Widget ErrorBuilder(BuildContext context);

class LazyListView<T> extends StatefulWidget {
  final int pageSize;
  final PageFuture<T> pageFuture;
  final Stream<int> countStream;

  final ItemBuilder<T> itemBuilder;
  final WaitBuilder waitBuilder;
  final PlaceholderBuilder placeholderBuilder;
  final EmptyResultBuilder emptyResultBuilder;
  final ErrorBuilder errorBuilder;

  LazyListView(
      {@required this.pageSize,
      @required this.pageFuture,
      @required this.countStream,
      @required this.itemBuilder,
      @required this.waitBuilder,
      @required this.placeholderBuilder,
      @required this.emptyResultBuilder,
      @required this.errorBuilder});

  @override
  _LazyListView<T> createState() => _LazyListView<T>();
}

class _LazyListView<T> extends State<LazyListView<T>> {
  Map<int, PageResult<T>> map;
  MapCache<int, PageResult<T>> cache;

  Object error;

  int totalCount = -1;

  int currentPage = 0;

  @override
  void initState() {
    super.initState();
    map = LruMap<int, PageResult<T>>(maximumSize: 500 ~/ widget.pageSize);
    cache = MapCache<int, PageResult<T>>(map: map);

    widget.countStream.listen((int count) {
      Fimber.i("Total count changed: $count");
      totalCount = count;

      map.clear();

      setState(() {});
    });
  }

  @override
  Widget build(BuildContext context) {
    if (error != null) {
      return widget.errorBuilder(context);
    }
    if (totalCount == -1) {
      return widget.waitBuilder(context);
    }
    if (totalCount == 0) {
      return widget.emptyResultBuilder(context);
    }
    return ListView.builder(
      key: Key("listView"),
      itemCount: totalCount,
      itemBuilder: (context, index) {
        currentPage = index ~/ widget.pageSize;
        final pageResult = map[currentPage];
        final value = pageResult == null ? null : pageResult.items[index % widget.pageSize];
        final loading = (value == null);
        if (loading) {
          cache.get(currentPage, ifAbsent: _loadPage).then(reload);
          return widget.placeholderBuilder(context);
        }
        return widget.itemBuilder(context, index, value);
      },
    );
  }

  Future<PageResult<T>> _loadPage(int index) {
    Fimber.i("Start fetch page $index");
    return widget.pageFuture(index).then((list) => PageResult(index, list));
  }

  reload(PageResult<T> value) {
    // Avoid calling setState if it's not needed.
    if ((value.index - currentPage).abs() > 2) {
      // ATTENTION: 2 is an arbitrary value, the distance between the current page and the page in the future result should ensure correct refreshing.
      // It should be greater if item widgets have a smaller height, can be smaller if item widgets have a greater height.
      // TODO: make it configurable?
      Fimber.i("Skipping refreshing for result of page ${value.index}, currentPage = $currentPage");
      return;
    }
    setState(() {});
  }
}

class PageResult<T> {
  /// Page index of this data.
  final int index;

  final List<T> items;

  PageResult(this.index, this.items);
}

UPDATE #2 based on pskink new comment

import 'package:fimber/fimber.dart';
import 'package:flutter/material.dart';
import 'package:quiver/cache.dart';
import 'package:quiver/collection.dart';

typedef Future<List<T>> PageFuture<T>(int pageIndex);

typedef Widget ItemBuilder<T>(BuildContext context, int index, T entry);
typedef Widget WaitBuilder(BuildContext context);
typedef Widget PlaceholderBuilder(BuildContext context);
typedef Widget EmptyResultBuilder(BuildContext context);
typedef Widget ErrorBuilder(BuildContext context);

class LazyListView<T> extends StatefulWidget {
  final int pageSize;
  final PageFuture<T> pageFuture;
  final Stream<int> countStream;

  final ItemBuilder<T> itemBuilder;
  final WaitBuilder waitBuilder;
  final PlaceholderBuilder placeholderBuilder;
  final EmptyResultBuilder emptyResultBuilder;
  final ErrorBuilder errorBuilder;

  LazyListView(
      {@required this.pageSize,
      @required this.pageFuture,
      @required this.countStream,
      @required this.itemBuilder,
      @required this.waitBuilder,
      @required this.placeholderBuilder,
      @required this.emptyResultBuilder,
      @required this.errorBuilder});

  @override
  _LazyListView<T> createState() => _LazyListView<T>();
}

class _LazyListView<T> extends State<LazyListView<T>> {
  Map<int, PageResult<T>> map;
  MapCache<int, PageResult<T>> cache;

  Object error;

  int totalCount = -1;

  @override
  void initState() {
    super.initState();
    map = LruMap<int, PageResult<T>>(maximumSize: 50 ~/ widget.pageSize);
    cache = MapCache<int, PageResult<T>>(map: map);

    widget.countStream.listen((int count) {
      Fimber.i("Total count changed: $count");
      totalCount = count;

      map.clear();

      setState(() {});
    });
  }

  @override
  Widget build(BuildContext context) {
    if (error != null) {
      return widget.errorBuilder(context);
    }
    if (totalCount == -1) {
      return widget.waitBuilder(context);
    }
    if (totalCount == 0) {
      return widget.emptyResultBuilder(context);
    }
    return ListView.builder(
      key: Key("listView"),
      itemCount: totalCount,
      itemBuilder: (context, index) {
        int currentPage = index ~/ widget.pageSize;
        final pageResult = map[currentPage];
        final value = pageResult == null ? null : pageResult.items[index % widget.pageSize];
        final loading = (value == null);
        if (loading) {
          cache.get(currentPage, ifAbsent: _loadPage).then(_reload);
          return widget.placeholderBuilder(context);
        }
        return widget.itemBuilder(context, index, value);
      },
    );
  }

  Future<PageResult<T>> _loadPage(int index) {
    Fimber.i("Start fetch page $index");
    return widget.pageFuture(index).then((list) => PageResult(index, list));
  }

  _reload(PageResult<T> value) {
    if (value.refreshed) {
      // Avoid calling setState if already called.
      Fimber.i("Skipping refreshing for result of page ${value.index}");
      return;
    }
    setState(() {
      value.refreshed = true;
    });
  }
}

class PageResult<T> {
  /// Page index of this data.
  final int index;

  final List<T> items;
  bool refreshed = false;

  PageResult(this.index, this.items);
}

What dou you think?

Body:
This is the last version thanks to some very useful suggestions

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:quiver/cache.dart';
import 'package:quiver/collection.dart';

typedef Future<List<T>> PageFuture<T>(int pageIndex);

typedef Widget ItemBuilder<T>(BuildContext context, int index, T entry);
typedef Widget ErrorBuilder(BuildContext context, dynamic error);

class LazyListView<T> extends StatefulWidget {
  final int pageSize;
  final PageFuture<T> pageFuture;
  final Stream<int> countStream;

  final ItemBuilder<T> itemBuilder;
  final IndexedWidgetBuilder placeholderBuilder;
  final WidgetBuilder waitBuilder;
  final WidgetBuilder emptyResultBuilder;
  final ErrorBuilder errorBuilder;
  final double velocityThreshold;

  LazyListView({
    @required this.pageSize,
    @required this.pageFuture,
    @required this.countStream,
    @required this.itemBuilder,
    @required this.placeholderBuilder,
    this.waitBuilder,
    this.emptyResultBuilder,
    this.errorBuilder,
    this.velocityThreshold = 128,
  })  : assert(pageSize > 0),
        assert(pageFuture != null),
        assert(countStream != null),
        assert(itemBuilder != null),
        assert(placeholderBuilder != null),
        assert(velocityThreshold >= 0);

  @override
  _LazyListViewState<T> createState() => _LazyListViewState<T>();
}

class _LazyListViewState<T> extends State<LazyListView<T>> {
  Map<int, PageResult<T>> map;
  MapCache<int, PageResult<T>> cache;
  dynamic error;
  int totalCount = -1;
  bool _frameCallbackInProgress = false;

  @override
  void initState() {
    super.initState();
    _initCache();

    widget.countStream.listen((int count) {
      totalCount = count;
      _initCache();
      setState(() {});
    });
  }

  @override
  Widget build(BuildContext context) {
    //debugPrintBeginFrameBanner = true;
    //debugPrintEndFrameBanner = true;
    //print('build');
    if (error != null && widget.errorBuilder != null) return widget.errorBuilder(context, error);
    if (totalCount == -1 && widget.waitBuilder != null) return widget.waitBuilder(context);
    if (totalCount == 0 && widget.emptyResultBuilder != null) return widget.emptyResultBuilder(context);

    return ListView.builder(
      physics: _LazyListViewPhysics(velocityThreshold: widget.velocityThreshold),
      itemCount: max(totalCount, 0),
      itemBuilder: (context, index) {
        // print('builder $index');
        var page = index ~/ widget.pageSize;
        final pageResult = map[page];
        final value = pageResult?.items?.elementAt(index % widget.pageSize);
        if (value != null) {
          return widget.itemBuilder(context, index, value);
        }

        // print('$index ${Scrollable.of(context).position.activity.velocity}');
        if (!Scrollable.recommendDeferredLoadingForContext(context)) {
          cache.get(page, ifAbsent: _loadPage).then(_reload).catchError(_error);
        } else if (!_frameCallbackInProgress) {
          _frameCallbackInProgress = true;
          SchedulerBinding.instance.scheduleFrameCallback((d) => _deferredReload(context));
        }
        return widget.placeholderBuilder(context, index);
      },
    );
  }

  Future<PageResult<T>> _loadPage(int index) async {
    print('load $index');
    var list = await widget.pageFuture(index);
    return PageResult(index, list);
  }

  void _initCache() {
    map = LruMap<int, PageResult<T>>(maximumSize: 50 ~/ widget.pageSize);
    cache = MapCache<int, PageResult<T>>(map: map);
  }

  void _error(dynamic e, StackTrace stackTrace) {
    if (widget.errorBuilder == null) {
      throw e;
    }
    setState(() => error = e);
  }

  void _reload(PageResult<T> value) => _doReload(value.index);

  void _deferredReload(BuildContext context) {
    print('_deferredReload');
    if (!Scrollable.recommendDeferredLoadingForContext(context)) {
      _frameCallbackInProgress = false;
      _doReload(-1);
    } else {
      SchedulerBinding.instance.scheduleFrameCallback((d) => _deferredReload(context), rescheduling: true);
    }
  }

  void _doReload(int index) {
    // print('reload $index');
    setState(() {});
  }
}

class PageResult<T> {
  /// Page index of this data.
  final int index;
  final List<T> items;

  PageResult(this.index, this.items);
}

class _LazyListViewPhysics extends AlwaysScrollableScrollPhysics {
  final double velocityThreshold;

  _LazyListViewPhysics({
    @required this.velocityThreshold,
    ScrollPhysics parent,
  }) : super(parent: parent);

  @override
  recommendDeferredLoading(double velocity, ScrollMetrics metrics, BuildContext context) {
    // print('velocityThreshold: $velocityThreshold');
    return velocity.abs() > velocityThreshold;
  }

  @override
  _LazyListViewPhysics applyTo(ScrollPhysics ancestor) {
    // print('applyTo($ancestor)');
    return _LazyListViewPhysics(velocityThreshold: velocityThreshold, parent: buildParent(ancestor));
  }
}

UPDATE #1

This is a new version which ensures futures don't call setState if the widget is unmounted.

import 'dart:async';
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:quiver/cache.dart';
import 'package:quiver/collection.dart';

typedef Future<List<T>> PageFuture<T>(int pageIndex);

typedef Widget ItemBuilder<T>(BuildContext context, int index, T entry);
typedef Widget ErrorBuilder(BuildContext context, dynamic error);

class LazyListView<T> extends StatefulWidget {
  final int pageSize;
  final PageFuture<T> pageFuture;
  final Stream<int> countStream;

  final ItemBuilder<T> itemBuilder;
  final IndexedWidgetBuilder placeholderBuilder;
  final WidgetBuilder waitBuilder;
  final WidgetBuilder emptyResultBuilder;
  final ErrorBuilder errorBuilder;
  final double velocityThreshold;

  LazyListView({
    @required this.pageSize,
    @required this.pageFuture,
    @required this.countStream,
    @required this.itemBuilder,
    @required this.placeholderBuilder,
    this.waitBuilder,
    this.emptyResultBuilder,
    this.errorBuilder,
    this.velocityThreshold = 128,
  })  : assert(pageSize > 0),
        assert(pageFuture != null),
        assert(countStream != null),
        assert(itemBuilder != null),
        assert(placeholderBuilder != null),
        assert(velocityThreshold >= 0);

  @override
  _LazyListViewState<T> createState() => _LazyListViewState<T>();
}

class _LazyListViewState<T> extends State<LazyListView<T>> {
  Map<int, PageResult<T>> map;
  MapCache<int, PageResult<T>> cache;
  dynamic error;
  int totalCount = -1;
  bool _frameCallbackInProgress = false;

  StreamSubscription<int> countStreamSubscription;

  @override
  void initState() {
    super.initState();
    _initCache();

    countStreamSubscription = widget.countStream.listen((int count) {
      totalCount = count;
      print('totalCount = $totalCount');
      _initCache();
      setState(() {});
    });
  }

  @override
  void dispose() {
    countStreamSubscription.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    //debugPrintBeginFrameBanner = true;
    //debugPrintEndFrameBanner = true;
    //print('build');
    if (error != null && widget.errorBuilder != null) {
      return widget.errorBuilder(context, error);
    }
    if (totalCount == -1 && widget.waitBuilder != null) {
      return widget.waitBuilder(context);
    }
    if (totalCount == 0 && widget.emptyResultBuilder != null) {
      return widget.emptyResultBuilder(context);
    }

    return ListView.builder(
      physics: _LazyListViewPhysics(velocityThreshold: widget.velocityThreshold),
      itemCount: max(totalCount, 0),
      itemBuilder: (context, index) {
        // print('builder $index');
        final page = index ~/ widget.pageSize;
        final pageResult = map[page];
        final value = pageResult?.items?.elementAt(index % widget.pageSize);
        if (value != null) {
          return widget.itemBuilder(context, index, value);
        }

        // print('$index ${Scrollable.of(context).position.activity.velocity}');
        if (!Scrollable.recommendDeferredLoadingForContext(context)) {
          cache.get(page, ifAbsent: _loadPage).then(_reload).catchError(_error);
        } else if (!_frameCallbackInProgress) {
          _frameCallbackInProgress = true;
          SchedulerBinding.instance.scheduleFrameCallback((d) => _deferredReload(context));
        }
        return widget.placeholderBuilder(context, index);
      },
    );
  }

  Future<PageResult<T>> _loadPage(int index) async {
    print('load $index');
    var list = await widget.pageFuture(index);
    return PageResult(index, list);
  }

  void _initCache() {
    map = LruMap<int, PageResult<T>>(maximumSize: 512 ~/ widget.pageSize);
    cache = MapCache<int, PageResult<T>>(map: map);
  }

  void _error(dynamic e, StackTrace stackTrace) {
    if (widget.errorBuilder == null) {
      throw e;
    }
    if (this.mounted) {
      setState(() => error = e);
    }
  }

  void _reload(PageResult<T> value) => _doReload(value.index);

  void _deferredReload(BuildContext context) {
    print('_deferredReload');
    if (!Scrollable.recommendDeferredLoadingForContext(context)) {
      _frameCallbackInProgress = false;
      _doReload(-1);
    } else {
      SchedulerBinding.instance.scheduleFrameCallback((d) => _deferredReload(context), rescheduling: true);
    }
  }

  void _doReload(int index) {
    print('reload $index');
    if (this.mounted) {
      setState(() {});
    }
  }
}

class PageResult<T> {
  /// Page index of this data.
  final int index;
  final List<T> items;

  PageResult(this.index, this.items);
}

class _LazyListViewPhysics extends AlwaysScrollableScrollPhysics {
  final double velocityThreshold;

  _LazyListViewPhysics({
    @required this.velocityThreshold,
    ScrollPhysics parent,
  }) : super(parent: parent);

  @override
  recommendDeferredLoading(double velocity, ScrollMetrics metrics, BuildContext context) {
    // print('velocityThreshold: $velocityThreshold');
    return velocity.abs() > velocityThreshold;
  }

  @override
  _LazyListViewPhysics applyTo(ScrollPhysics ancestor) {
    // print('applyTo($ancestor)');
    return _LazyListViewPhysics(velocityThreshold: velocityThreshold, parent: buildParent(ancestor));
  }
}

Anyone with a better idea?