【问题标题】:flutter search bar with bloc with firestore颤振搜索栏与带有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();
  }
}

【问题讨论】:

    标签: android flutter search bloc appbar


    【解决方案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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-02-27
      • 1970-01-01
      • 2020-11-16
      • 2019-05-25
      相关资源
      最近更新 更多