颤振搜索栏与带有firestore的块

Posted

技术标签:

【中文标题】颤振搜索栏与带有firestore的块【英文标题】:flutter search bar with bloc with firestore 【发布时间】:2020-07-23 17:27:06 【问题描述】:

我搜索应用程序栏。我按标题(子字符串)搜索数据并返回项目。我有 Ui、bloc、服务类。我不知道如何将“$searchQuery”传递到 bloc 接收器。我返回流列表(在Ui),但首先我需要按标题(项目)过滤我的列表并放入接收器。你能给我一些建议或例子吗?我应该修复我的 bloc 类吗?

class Search extends StatefulWidget 
  @override
  _Search createState() => _Search();


class _Search extends State<Search> 

  static final GlobalKey<ScaffoldState> scaffoldKey =
      new GlobalKey<ScaffoldState>();
  TextEditingController _searchQuery;
  bool _isSearching = false;
  String searchQuery = "Search query";

  @override
  void initState() 
    super.initState();
    _searchQuery = new TextEditingController();
  

  void _startSearch() 
    print("open search box");
    ModalRoute.of(context)
        .addLocalHistoryEntry(new LocalHistoryEntry(onRemove: _stopSearching));

    setState(() 
      _isSearching = true;
    );
  

  void _stopSearching() 
    _clearSearchQuery();

    setState(() 
      _isSearching = false;
    );
  

  void _clearSearchQuery() 
    print("close search box");
    setState(() 
      _searchQuery.clear();
      updateSearchQuery("Search query");
    );
  

  Widget _buildTitle(BuildContext context) 
    var horizontalTitleAlignment =
        Platform.isios ? CrossAxisAlignment.center : CrossAxisAlignment.start;

    return new InkWell(
      onTap: () => scaffoldKey.currentState.openDrawer(),
      child: new Padding(
        padding: const EdgeInsets.symmetric(horizontal: 12.0),
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: horizontalTitleAlignment,
          children: <Widget>[
            const Text('Seach box'),
          ],
        ),
      ),
    );
  

  Widget _buildSearchField() 
    return new TextField(
      controller: _searchQuery,
      autofocus: true,
      decoration: const InputDecoration(
        hintText: 'Search...',
        border: InputBorder.none,
        hintStyle: const TextStyle(color: Colors.white30),
      ),
      style: const TextStyle(color: Colors.white, fontSize: 16.0),
      onChanged: updateSearchQuery,
    );
  

  void updateSearchQuery(String newQuery) 
    setState(() 
      searchQuery = newQuery;
    );
    print("search query " + newQuery);
  

  List<Widget> _buildActions() 
    if (_isSearching) 
      return <Widget>[
        new IconButton(
          icon: const Icon(Icons.clear),
          onPressed: () 
            if (_searchQuery == null || _searchQuery.text.isEmpty) 
              Navigator.pop(context);
              return;
            
            _clearSearchQuery();
          ,
        ),
      ];
    

    return <Widget>[
      new IconButton(
        icon: const Icon(Icons.search),
        onPressed: _startSearch,
      ),
    ];
  

  @override
  Widget build(BuildContext context) 
    return SafeArea(
        child: Scaffold(
      key: scaffoldKey,
      appBar: new AppBar(
        leading: _isSearching ? const BackButton() : null,
        title: _isSearching ? _buildSearchField() : _buildTitle(context),
        actions: _buildActions(),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text(
              '$searchQuery',
              style: Theme.of(context).textTheme.display1,
            )
          ],
        ),
      ),
    ));
  


class MovieService 
  static final String _baseUrl = 'xxxx';

  final CollectionReference _db;

  MovieService() : _db = Firestore.instance.collection(_baseUrl);

  Future<List<MovieEntity>> searchByString(String stringSearch) async 
    final CollectionReference _dbs = Firestore.instance.collection(_baseUrl);
    QuerySnapshot query =
    await _dbs.where("Title", isEqualTo: stringSearch.substring(0, 1).toUpperCase()).getDocuments();
    List<MovieEntity> products = query.documents
        .map((doc) => MovieEntity.fromSnapshotJson(doc))
        .toList();
    return products;
  



class MovieBloc extends BlocBase 

  String nameFilmSearchQuery;
  final MovieService _productService;

  MovieBloc(this._productService) 
    _loadMovies();
  

  final BehaviorSubject<List<MovieEntity>> _controllerSearch =
      new BehaviorSubject<List<MovieEntity>>.seeded(List<MovieEntity>());

  Observable<List<MovieEntity>> get listMoviesFluxSearch =>
      _controllerSearch.stream;

  Sink<List<MovieEntity>> get listMoviesEventSearch => _controllerSearch.sink;

  _loadMovies() async 
    listMoviesEventSearch.add(await _productService.searchByString(nameFilmSearchQuery));// i should pass '$searchQuery' here from Ui
  

  @override
  void dispose() 
    _controllerSearch.close();
    super.dispose();
  

【问题讨论】:

【参考方案1】:

此功能的一种可能实现,包括相对于更改状态和事件的主 BLoC 使用的 SearchDelegate。 在这种情况下,所需的资源是一个 City,因此它实现了一个扩展 SearchDelegate 的“SearchCity”类。 重写方法 buildResults 和 BlocBuilder,它还添加了 SearchCity 事件和一些 SearchCity 状态,以便轻松管理与搜索操作本身相关的所有可能操作。

class CitySearchEvent 
  final String query;

  const CitySearchEvent(this.query);

  @override
  String toString() => 'CitySearchEvent  query: $query ';


class CitySearchState 
  final bool isLoading;
  final List<City> cities;
  final bool hasError;

  const CitySearchState(this.isLoading, this.cities, this.hasError);

  factory CitySearchState.initial() 
    return CitySearchState(
      cities: [],
      isLoading: false,
      hasError: false,
    );
  

  factory CitySearchState.loading() 
    return CitySearchState(
      cities: [],
      isLoading: true,
      hasError: false,
    );
  

  factory CitySearchState.success(List<City> cities) 
    return CitySearchState(
      cities: cities,
      isLoading: false,
      hasError: false,
    );
  

  factory CitySearchState.error() 
    return CitySearchState(
      cities: [],
      isLoading: false,
      hasError: true,
    );
  

  @override
  String toString() =>
      'CitySearchState cities: $cities.toString(), isLoading: $isLoading, hasError: $hasError ';

这是添加到主要 BLoC 实现的编辑,它使用以前创建的状态和事件来为操作构建更好的业务逻辑。

class CityBloc extends Bloc<CitySearchEvent, CitySearchState> 
  @override
  CitySearchState get initialState => CitySearchState.initial();

  @override
  void onTransition(Transition<CitySearchEvent, CitySearchState> transition) 
  
    print(transition.toString());
  

  @override
  Stream<CitySearchState> mapEventToState(CitySearchEvent event) async* 
    yield CitySearchState.loading();

    try 
      List<City> cities = await _getSearchResults(event.query);
      yield CitySearchState.success(cities);
     catch (_) 
      yield CitySearchState.error();
    
  

  Future<List<City>> _getSearchResults(String query) async 
    // Simulating network latency
    await Future.delayed(Duration(seconds: 1));
    return [City('Chicago'), City('Los Angeles')];
  

这是一个包含 UI 和搜索方法的单文件实现的完整示例。

import 'dart:async';

import 'package:flutter/material.dart';

import 'package:bloc/bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
        title: 'Flutter Demo',
        home: BlocProvider(
          create: (_) => CityBloc(),
          child: MyHomePage(),
        ));
  


class MyHomePage extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(
        title: Text('Search Delegate'),
      ),
      body: Container(
        child: Center(
          child: RaisedButton(
            child: Text('Show search'),
            onPressed: () async 
              City selected = await showSearch<City>(
                context: context,
                delegate: CitySearch(BlocProvider.of<CityBloc>(context)),
              );
              print(selected);
            ,
          ),
        ),
      ),
    );
  


class City 
  final String name;

  const City(this.name);

  @override
  String toString() => 'City  name: $name ';


class CitySearch extends SearchDelegate<City> 
  final Bloc<CitySearchEvent, CitySearchState> cityBloc;

  CitySearch(this.cityBloc);

  @override
  List<Widget> buildActions(BuildContext context) => null;

  @override
  Widget buildLeading(BuildContext context) 
    return IconButton(
      icon: BackButtonIcon(),
      onPressed: () 
        close(context, null);
      ,
    );
  

  @override
  Widget buildResults(BuildContext context) 
    cityBloc.add(CitySearchEvent(query));

    return BlocBuilder(
      bloc: cityBloc,
      builder: (BuildContext context, CitySearchState state) 
        if (state.isLoading) 
          return Center(
            child: CircularProgressIndicator(),
          );
        
        if (state.hasError) 
          return Container(
            child: Text('Error'),
          );
        
        return ListView.builder(
          itemBuilder: (context, index) 
            return ListTile(
              leading: Icon(Icons.location_city),
              title: Text(state.cities[index].name),
              onTap: () => close(context, state.cities[index]),
            );
          ,
          itemCount: state.cities.length,
        );
      ,
    );
  

  @override
  Widget buildSuggestions(BuildContext context) => Container();


class CitySearchEvent 
  final String query;

  const CitySearchEvent(this.query);

  @override
  String toString() => 'CitySearchEvent  query: $query ';


class CitySearchState 
  final bool isLoading;
  final List<City> cities;
  final bool hasError;

  const CitySearchState(this.isLoading, this.cities, this.hasError);

  factory CitySearchState.initial() 
    return CitySearchState(
      cities: [],
      isLoading: false,
      hasError: false,
    );
  

  factory CitySearchState.loading() 
    return CitySearchState(
      cities: [],
      isLoading: true,
      hasError: false,
    );
  

  factory CitySearchState.success(List<City> cities) 
    return CitySearchState(
      cities: cities,
      isLoading: false,
      hasError: false,
    );
  

  factory CitySearchState.error() 
    return CitySearchState(
      cities: [],
      isLoading: false,
      hasError: true,
    );
  

  @override
  String toString() =>
      'CitySearchState cities: $cities.toString(), isLoading: $isLoading, hasError: $hasError ';


class CityBloc extends Bloc<CitySearchEvent, CitySearchState> 
  @override
  CitySearchState get initialState => CitySearchState.initial();

  @override
  void onTransition(Transition<CitySearchEvent, CitySearchState> transition) 
    print(transition.toString());
  

  @override
  Stream<CitySearchState> mapEventToState(CitySearchEvent event) async* 
    yield CitySearchState.loading();

    try 
      List<City> cities = await _getSearchResults(event.query);
      yield CitySearchState.success(cities);
     catch (_) 
      yield CitySearchState.error();
    
  

  Future<List<City>> _getSearchResults(String query) async 
    // Simulating network latency
    await Future.delayed(Duration(seconds: 1));
    return [City('Chicago'), City('Los Angeles')];
  

可以在这个 Github Gist 链接上找到上述代码的参考 https://gist.github.com/felangel/11769ab10fbc4076076299106f48fc95

【讨论】:

以上是关于颤振搜索栏与带有firestore的块的主要内容,如果未能解决你的问题,请参考以下文章

带有 .where() 的 Firestore 查询可以执行空搜索吗

颤振:卡住从 Firestore 收集 [关闭]

如何使用颤振提供程序从 Firestore 获取数据?

我如何在颤振中获得 Firestore 集合和子集合

添加“cloud_firestore”时出现颤振错误

我无法在颤振 Firestore 上将字段设置为 NULL