如何在HookWidget中使用useStreamController?

时间:2020-08-07 09:55:48

标签: flutter flutter-dependencies

不熟悉扑钩和Riverpod(状态管理)

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String _url = "https://owlbot.info/api/v4/dictionary/";
  String _token = "ae7cbdfff57e548a4360348ee519123a741d8e3d";

  TextEditingController _controller = TextEditingController();

  StreamController _streamController;
  Stream _stream;

  Timer _debounce;

  Future _search() async {
    if (_controller.text == null || _controller.text.length == 0) {
      _streamController.add(null);
      return;
    }

    _streamController.add("waiting");
    Response response = await get(_url + _controller.text.trim(),
        headers: {"Authorization": "Token " + _token});
    _streamController.add(json.decode(response.body));
  }

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

    _streamController = StreamController();
    _stream = _streamController.stream;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Flictionary"),
        bottom: PreferredSize(
          preferredSize: Size.fromHeight(48.0),
          child: Row(
            children: <Widget>[
              Expanded(
                child: Container(
                  margin: const EdgeInsets.only(left: 12.0, bottom: 8.0),
                  decoration: BoxDecoration(
                    color: Colors.white,
                    borderRadius: BorderRadius.circular(24.0),
                  ),
                  child: TextFormField(
                    onChanged: (String text) {
                      if (_debounce?.isActive ?? false) _debounce.cancel();
                      _debounce = Timer(const Duration(milliseconds: 1000), () {
                        _search();
                      });
                    },
                    controller: _controller,
                    decoration: InputDecoration(
                      hintText: "Search for a word",
                      contentPadding: const EdgeInsets.only(left: 24.0),
                      border: InputBorder.none,
                    ),
                  ),
                ),
              ),
              IconButton(
                icon: Icon(
                  Icons.search,
                  color: Colors.white,
                ),
                onPressed: () {
                  _search();
                },
              )
            ],
          ),
        ),
      ),
      body: Container(
        margin: const EdgeInsets.all(8.0),
        child: StreamBuilder(
          stream: _stream,
          builder: (BuildContext ctx, AsyncSnapshot snapshot) {
            if (snapshot.data == null) {
              return Center(
                child: Text("Enter a search word"),
              );
            }

            if (snapshot.data == "waiting") {
              return Center(
                child: CircularProgressIndicator(),
              );
            }

            return ListView.builder(
              itemCount: snapshot.data["definitions"].length,
              itemBuilder: (BuildContext context, int index) {
                return ListBody(
                  children: <Widget>[
                    Container(
                      color: Colors.grey[300],
                      child: ListTile(
                        leading: snapshot.data["definitions"][index]
                                    ["image_url"] ==
                                null
                            ? null
                            : CircleAvatar(
                                backgroundImage: NetworkImage(snapshot
                                    .data["definitions"][index]["image_url"]),
                              ),
                        title: Text(_controller.text.trim() +
                            "(" +
                            snapshot.data["definitions"][index]["type"] +
                            ")"),
                      ),
                    ),
                    Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: Text(
                          snapshot.data["definitions"][index]["definition"]),
                    )
                  ],
                );
              },
            );
          },
        ),
      ),
    );
  }
}

我只是想将上述statefulWidget转换为HookWidget,以及如何在上述示例中将riverpod用作statemanagenent。我知道钩子和Riverpod的一些基础知识,但是我仍然对钩子,statemanagement(riverpod)之间感到困惑。 请有人可以帮助他们理解并提供一些示例,或者至少将以上代码转换为hook小部件,并使用hookbuilder

预先感谢

1 个答案:

答案 0 :(得分:1)

首先,代码:

final textProvider = StateProvider<String>((_) => '');

final responseFutureProvider =
    FutureProvider.autoDispose.family<Response, String>((ref, text) async {
  if (text == null || text.length == 0) {
    throw Error();
  }

  final String _url = "https://owlbot.info/api/v4/dictionary/";
  final String _token = "ae7cbdfff57e548a4360348ee519123a741d8e3d";

  return await get(_url + text.trim(), headers: {"Authorization": "Token " + _token});
});

final responseProvider = Computed<AsyncValue<Response>>((read) {
  final text = read(textProvider).state;
  return read(responseFutureProvider(text));
});

String _useDebouncedSearch(TextEditingController controller) {
  final search = useState(controller.text);

  useEffect(() {
    Timer timer;
    void listener() {
      timer?.cancel();
      timer = Timer(
        const Duration(milliseconds: 1000),
        () => search.value = controller.text,
      );
    }

    controller.addListener(listener);
    return () {
      timer?.cancel();
      controller.removeListener(listener);
    };
  }, [controller]);

  return search.value;
}

class MyHomePage extends HookWidget {
  const MyHomePage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final controller = useTextEditingController();
    final text = useProvider(textProvider);
    text.state = _useDebouncedSearch(controller);

    return Scaffold(
      appBar: AppBar(
        title: Text("Flictionary"),
        bottom: PreferredSize(
          preferredSize: Size.fromHeight(48.0),
          child: Row(
            children: <Widget>[
              Expanded(
                child: Container(
                  margin: const EdgeInsets.only(left: 12.0, bottom: 8.0),
                  decoration: BoxDecoration(
                    color: Colors.white,
                    borderRadius: BorderRadius.circular(24.0),
                  ),
                  child: TextFormField(
                    controller: controller,
                    decoration: InputDecoration(
                      hintText: "Search for a word",
                      contentPadding: const EdgeInsets.only(left: 24.0),
                      border: InputBorder.none,
                    ),
                  ),
                ),
              ),
              Icon(
                Icons.search,
                color: Colors.white,
              ),
            ],
          ),
        ),
      ),
      body: MyHomePageBody(),
    );
  }
}

class MyHomePageBody extends HookWidget {
  const MyHomePageBody({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final text = useProvider(textProvider).state;
    final response = useProvider(responseProvider);

    response.when(
      error: (err, stack) => Center(child: Text('Error: $err')),
      loading: () => Center(child: CircularProgressIndicator()),
      data: (response) => ListView.builder(
        itemCount: Response["definitions"].length,
        itemBuilder: (BuildContext context, int index) {
          return ListBody(
            children: <Widget>[
              Container(
                color: Colors.grey[300],
                child: ListTile(
                  leading: response["definitions"][index]["image_url"] == null
                      ? null
                      : CircleAvatar(
                          backgroundImage:
                              NetworkImage(response["definitions"][index]["image_url"]),
                        ),
                  title: Text(text.trim() + "(" + response["definitions"][index]["type"] + ")"),
                ),
              ),
              Padding(
                padding: const EdgeInsets.all(8.0),
                child: Text(response["definitions"][index]["definition"]),
              )
            ],
          );
        },
      ),
    );
  }
}
  1. 我们添加了一个外部文本提供程序,以便我们可以从其他提供程序中读取文本字段。
  2. 我们创建一个FutureProviderFamily,以便我们可以使用参数(来自您的文本字段的文本)执行API调用。在Riverpod中,家庭启用了将参数传递给提供程序的功能。
  3. 我们创建了一个Compute,每次文本提供程序的值更改时,都会调用Future。这将返回一个AsyncValue,它是您正在使用的StreamBuilder的绝佳替代品(将进行解释)。
  4. 将去抖动的搜索稍作重构,以使用useEffect挂钩。这将处理为计时器分配的资源,并根据需要更新textProvider。 (我是从Remi's Marvel example中学到的)
  5. 我们不再需要按onChanged或手动按钮进行搜索,因为只要控制器更改,文本提供者的状态就会更新。
  6. 将页面正文移到其自己的类中,以将需要加载的内容与静态的内容分开。
  7. 现在,我们可以使用AsyncValue代替StreamBuilder来处理构建的加载,错误和成功状态。

我知道这涉及很多内容,所以我建议您真正深入研究文档以了解有关此示例中所有内容的更多信息。