扑扑搜索栏与集团与firestore

时间:2020-04-10 21:48:38

标签: android flutter search bloc appbar

我搜索应用程序栏。我按标题(子字符串)搜索数据并返回项目。我有Ui,bloc,服务类。我不知道如何将'$ searchQuery'传递给块接收器。我返回流列表(在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 个答案:

答案 0 :(得分: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