我正在使用flutter_riverpod
包。我将它用于搜索字段,在搜索某个内容时显示错误。我问了一个关于错误的问题,但无法修复。有其他方法可以使用flutter_riverpod
包在Flutter中创建搜索字段吗?
这里有一些代码:
用户界面代码:
TextFormField(
…
onChanged: (search) => controller.updateSearch(search),
onSaved: (search) {
search == null ? null : controller.updateSearch(search);
},
),
itemBuilder: (context, index) {
if (mounted) {
return name.contains(state.search) ? ListTile(title: Text(name)) : Container();
}
return Container();
},
控制器代码:
class Controller extends StateNotifier<State> {
Controller() : super(State());
void updateSearch(String search) => state = state.copyWith(search: search);
}
final controllerProvider = StateNotifierProvider.autoDispose< Controller, State>((ref) {
return Controller();
});
状态代码:
class State {
State({this.search = "", this.value = const AsyncValue.data(null)});
final String search;
final AsyncValue<void> value;
bool get isLoading => value.isLoading;
State copyWith({String? search, AsyncValue<void>? value}) {
return State(search: search ?? this.search, value: value ?? this.value);
}
}
上面的代码有问题吗?如果是,使用flutter_riverpod
包创建seach字段的方法是什么?如果没有,为什么我会收到错误消息(转到此问题查看错误消息(?
如果我不能在Flutter中使用flutter_riverpod
包创建搜索字段,我该如何创建搜索字段(我希望可能不使用任何包,也不使用setState
函数(?
如果您需要更多信息,请随时发表评论!
如何修复此错误?如果有任何帮助,我将不胜感激。提前谢谢!
更新:
我们可以在这个房间里讨论。
这可能不能直接回答这个问题,但它可能会为如何重构代码提供另一种变体。从控制器跟踪装载状态:
class Controller extends StateNotifier<AsyncValue<void>> {
Controller({required this.repository}) : super(AsyncData(null));
//this depends on where your search api is located at.
//for this example i use repository because it is mostly where its is.
final SeachRepository repository
Future<void> submitSearch(String search) async {
state = const AsyncLoading();
//`.guard` is used to handle possible errors from your API so no need
// to use `try/catch`
state = await AsyncValue.guard(()=> repository.submitSearch(search));
}
}
final controllerProvider = StateNotifierProvider.autoDispose< Controller, AsyncValue<void>>((ref) {
return Controller();
});
你可以从UI中使用你的控制器,如下所示:
final controller = ref.watch(controllerProvider.notifier);
TextFormField(
…
onChanged: (search) => ref.read(searchValueProvider.notifier).state = search,
onSaved: (search) {
search == null ? null : controller.submitSearch(search);
},
),
对于加载状态:
//your loading state
final state = ref.watch(controllerProvider);
//to handle passible error:
ref.listen<AsyncValue>(controllerProvider, (_, state)=> showAlertDialog(context));
itemBuilder: (context, index) {
if (mounted) {
return name.contains(state.search) ? ListTile(title: Text(name)) : Container();
}
return state.isLoading ? CircularProgressIndicator() : Container();
},
对于搜索变量,可以将其存储在StateProvider中以便于访问:final searchValueProvider=StateProvider.autodispose((ref(=>''(;
尝试以下代码:
class FooSearchRiverpod extends ConsumerWidget {
final controller = StreamController<String>();
late final provider = obtainProvider(controller);
@override
Widget build(BuildContext context, WidgetRef ref) {
AsyncValue<List<String>> list = ref.watch(provider);
return Column(
children: [
TextField(
onChanged: controller.add,
decoration: const InputDecoration(
prefixIcon: Icon(Icons.search), hintText: 'search...'),
),
Expanded(
child: list.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (err, stack) => Center(
child: Padding(
padding: const EdgeInsets.all(32.0),
child: Card(elevation: 4, child: Text(err.toString())),
),
),
data: (list) => AnimatedSwitcher(
duration: const Duration(milliseconds: 400),
child: ListView.builder(
itemCount: list.length,
itemBuilder: (ctx, i) => ListTile(
key: UniqueKey(),
title: Text(list[i]),
onTap: () => debugPrint('onTtap: ${list[i]}'),
),
),
),
),
),
],
);
}
obtainProvider(StreamController<String> controller) {
return StreamProvider.autoDispose<List<String>>((ref) => controller.stream
.debounce(const Duration(milliseconds: 1000))
.asyncMap(_getAPI));
}
final list = ['foo', 'foo 2', 'bar', 'bar 2', 'spam'];
Future<List<String>> _getAPI(String query) async {
debugPrint('_getAPI: |$query|');
if (query.isEmpty) return [];
return list.where((s) => s.contains(query)).toList();
}
}