简体   繁体   English

flutter 搜索栏,带防火存储区

[英]flutter search bar with bloc with firestore

I do search app bar.I search data by title(substring) and return items.I have Ui,bloc,service classes.I dont know how to pass '$searchQuery' into bloc sink.I return stream list(in Ui),but firstly i need to filter my list by title(item) and put in sink.Could you give me some advise or examples?Should i fix my bloc class?我搜索应用程序栏。我按标题(子字符串)搜索数据并返回项目。我有 Ui、bloc、服务类。我不知道如何将“$searchQuery”传递到 bloc sink。我返回 stream 列表(在 Ui 中),但首先我需要按标题(项目)过滤我的列表并放入接收器。你能给我一些建议或例子吗?我应该修复我的 bloc class 吗?

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();
  }
}

A possible implementation of this Feature, includes a SearchDelegate used relatively to the main BLoC that changes states and events.此功能的可能实现包括相对于更改状态和事件的主 BLoC 使用的 SearchDelegate。
In this case the Resource needed is a City , so it is implemented a "SearchCity" class that extends SearchDelegate .在这种情况下,所需的 Resource 是一个City ,因此它实现了一个扩展SearchDelegate的“SearchCity” class 。
Overriding the method buildResults and BlocBuilder, it also adds a SearchCity event and some SearchCity states, in order to easily manage all possible operations related to the search operation itself.重写方法 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 }';
}

Here is the edits added to the main BLoC implementation, that uses previously created States and Events in order to contruct a better business logic for the operation.这是添加到主要 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')];
  }
}

Here is a full example of a single-file implementation that includes both UI and search methods.这是一个包含 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')];
  }
}

A reference for the code above is available at this Github Gist link https://gist.github.com/felangel/11769ab10fbc4076076299106f48fc95以上代码的参考可在此Github GIST链接Z5E056C500A1C500A1C500A1C500B6AA71110B50D807BADE5Z://GIST.ZBF2151B51BF2151B511B514BF214B514BF214B54B54B54BF54BF54BF54BF54BD4BD4BFF24BFF24BD4BFRENER

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM