How to implement pull_to_refresh to streambuilder

2019-08-12 13:54发布

问题:

i'm trying to implement pull_to_refresh . I have two stateless widget that i will use . First is _ProductCategoryDetailPageState here is the code

  @override
  Widget build(BuildContext context) {
    return Material(
        child: Scaffold(
        appBar: AppBar(
          title: Text(widget.categoryName),
        ),
        body: Container(
          height: MediaQuery.of(context).size.height,
          child: Column(
            children: <Widget>[
              Divider(
                height: 20.0,
              ),
              Flexible(
                  child:StreamBuilder<List<Products>>(
                stream: _productController.stream,
                builder: (context, snapshot) {
                    if (snapshot.hasError) {
                      return errMess(context, "Failed to fetch data");
                    } else {
                      if (snapshot.hasData) {
                        if (snapshot.data.length > 0) {
                          return ProductList(category: snapshot.data);
                        } else {
                          return errMess(context,
                              "There is no available product in this category");
                        }
                      } else {
                        return errMess(context,
                            "There is no available product in this category");
                      }
                    }
                },
              )),
              Divider(
                height: 25.0,
              ),
            ],
          ),
        )));
  }

  loadProduct(String categoryId, int limit, int offset) async {
    List<Products> products = await fetchProducts(http.Client(), categoryId, limit, offset);
    _productController.sink.add(products);
  }
  static List<Products> parseProducts(String responseBody) {
    final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();
    return parsed.map<Products>((json) => Products.fromJson(json)).toList();
  }

  Future<List<Products>> fetchProducts(http.Client client, String categoryId, int limit, int offset) async {
    final response = await http.post(Configuration.url +
        "api/getProducts/" +
        categoryId +
        "/" +
        limit.toString() +
        "/" +
        offset.toString());
    if (response.statusCode < 200 || response.statusCode > 300) {
      throw new Exception('Failed to fetch data');
    } else {
      return compute(parseProducts, response.body);
    }
  }

and here is my second stateless widget

class _ProductListState extends State<ProductList> {
  int limit = 0;
  int offset = 4;

  RefreshController _refreshController2 =
      RefreshController(initialRefresh: false);

  @override
  Widget build(BuildContext context) {
    return SmartRefresher(
      child: new GridView.builder(
        itemCount: widget.category.length,
        gridDelegate:
            new SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
        itemBuilder: (BuildContext context, int index) {
          return new GestureDetector(
            onTap: () {
              print("Product detail");
            },
            child: Card(
              semanticContainer: true,
              clipBehavior: Clip.antiAliasWithSaveLayer,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Expanded(
                    child: Image.network(
                      Configuration.url +
                          "assets/app_assets/" +
                          widget.category[index].productImage,
                      width: 250,
                      height: 250,
                      filterQuality: FilterQuality.low,
                    ),
                  ),
                  SizedBox(
                    height: 25,
                  ),
                  Text(
                    widget.category[index].productName,
                    style: TextStyle(fontSize: 15.0),
                  ),
                  SizedBox(
                    height: 25,
                  ),
                ],
              ),
            ),
          );
        },
      ),
      controller: _refreshController2,
      enablePullUp: true,
      header: MaterialClassicHeader(),
      onRefresh: () {
        _onRefresh(_refreshController2, widget.category,
            widget.category[0].categoryId);
      },
      onLoading: () {
        _onLoading(_refreshController2, widget.category,
            widget.category[0].categoryId);
      },
    );
  }

  void _onLoading(RefreshController controller, List<Products> data,String categoryId) async {
    await Future.delayed(Duration(milliseconds: 2000));
    setState(() {
      limit = limit + offset;
      offset = 6;
    });

   _ProductCategoryDetailPageState().loadProduct(categoryId, limit, offset);

    controller.loadComplete();
  }

  void _onRefresh(RefreshController controller, List<Products> data,
      String categoryId) async {
    await Future.delayed(Duration(milliseconds: 1000));
    controller.refreshCompleted();
  }
}

when i pull the grid there is no error , but the data is not change. After i check on this part

    Flexible(
                      child:StreamBuilder<List<Products>>(
                    stream: _productController.stream,
                    builder: (context, snapshot) {
print("run")
                        if (snapshot.hasError) {
                          return errMess(context, "Failed to fetch data");
                        } else {
                          if (snapshot.hasData) {
                            if (snapshot.data.length > 0) {
                              return ProductList(category: snapshot.data);
                            } else {
                              return errMess(context,
                                  "There is no available product in this category");
                            }
                          } else {
                            return errMess(context,
                                "There is no available product in this category");
                          }
                        }
                    },
                  )),

As you can see i adding print("run") , it just only showing once.

my full script https://gist.github.com/bobykurniawan11/04f2584c6de97f1d9324bfe3b24f669f

回答1:

This won't work as you are creating a new State instance of the State object. You should connect both widgets with a callback for example.

_ProductCategoryDetailPageState().loadProduct(categoryId, limit, offset);

Like this:

// Custom callback function
typedef void OnLoadProductsFunction(String categoryId, int limit, int offset); 

class ProductList extends StatefulWidget {
    OnLoadProductsFunction onLoad;

    ProductList({
        this.category,
        this.onLoad,
    })
}

...

 void _onLoading(RefreshController controller, List<Products> data,String categoryId) async {
    await Future.delayed(Duration(milliseconds: 2000));
    setState(() {
      limit = limit + offset;
      offset = 6;
    });

    widget.onLoad(categoryId, limit, offset);

    controller.loadComplete();
}

...


// In the parent widget
return ProductList(
    category: snapshot.data
    onLoad: (categoryId, limit, offset) {
        loadProduct(categoryId, limit, offset);
    }
);

Doing it this way the streamcontroller will be updated from the callback function. Other option you have is to pass the StreamController instance to the ProductList widget and is the child who adds the list of products to the sink.



标签: flutter dart