как использовать useStreamController в HookWidget? - PullRequest
0 голосов
/ 07 августа 2020

Я новичок в флаттер-хуках и 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 в качестве средства управления состоянием для приведенного выше примера. Я кое-что знаю о крючках и речных стручках, но все же не понимаю, что такое крючки и государственное управление (речные стручки). Может ли кто-нибудь помочь понять их и предоставить несколько примеров или, по крайней мере, преобразовать приведенный выше код в виджет ловушки и использовать hookbuilder

Заранее спасибо

1 Ответ

0 голосов
/ 07 августа 2020

Во-первых, код:

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. Мы создаем Computed, который будет вызывать Future при каждом изменении значения поставщика текста. Это возвращает AsyncValue, который является прекрасной заменой StreamBuilder, который вы использовали (объясним больше).
  4. Немного отредактировал ваш поиск с отклонениями, чтобы использовать ловушку useEffect. Это обработает удаление ресурсов для вашего таймера и обновит textProvider по мере необходимости. (Я узнал об этом из примера Marvel Реми )
  5. Нам больше не нужно нажимать кнопку onChanged или вручную для поиска, поскольку состояние текстового провайдера обновляется при каждом изменении контроллера.
  6. Тело вашей страницы перемещено в отдельный класс, чтобы отделить то, что нужно загрузить, от того, что является stati c.
  7. Теперь вместо StreamBuilder мы можем использовать AsyncValue для обработки загрузки, ошибка , и успешные состояния вашей сборки.

Я знаю, что это было много, поэтому я бы рекомендовал покопаться в документации, чтобы узнать больше обо всем, что в этом примере.

...