はじめに
始めましてwatnowに所属してますしゅんやです。今日はRitsbookにAlgoliaを用いた全文検索機能をメルカリ風に実装していきたいと思います!!
楽しみですね!
RitsBookとは
立命館大学BKCキャンパス専用の教科書フリマアプリです。元々、所属している学生団体の先輩が作っていたものなのですが、現在はその方含め3人(エンジニア2名、デザイナー1名)でモバイル版の開発を行っています。
元々WEB版には完全一致検索はあったのですが、今回は全文検索機能を実装していきたいと思います。
Algoliaの連携
元々連携の方法について記事を書こうと思っていたのですが、すでにとてもわかりやすくまとめられていた方がいたので是非こちらの記事
をご覧になってください。
連携の流れとしては、
Algoliaのアカウント登録、設定
↓
Firebaseとの連携
↓
FirestoreのデータをAlgoliaに連携
↓
Flutterに導入
といった流れになっています。
検索フォームの実装
それでは検索フォームを作っていきます。今回はメルカリの検索フォームのようにしていきたいと思っております。 メルカリは商品一覧ページのAppBarに検索窓がありそこから検索するUIかと思いきや、実はボタンでそこから検索ページに遷移する形で実装されていたのでその通り実装していこうと思います。
商品一覧画面
検索画面
まずホームの画面(商品の一覧が載っている画面に検索ページに遷移するボタン)をつけていきたいと思います
LandingPage.dart
import 'package:flutter/material.dart';
//クラス名などは省略
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Padding(
padding: const EdgeInsets.all(8.0),
child: SizedBox(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context, MaterialPageRoute(builder: (context) => Search()));
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xfff5f5f5),
),
child: const Align(
alignment: Alignment.centerLeft,
child: Text(
'検索',
style: TextStyle(color: Color(0xff8f8f8f), fontSize: 12),
),
),
),
),
),
backgroundColor: Colors.white,
),
//以下省略
検索画面の実装
とりあえずUIは作らず、タイトルのみを表示してみました。続きはまた投稿する予定です。SearchPage.dart
import 'dart:async';
import 'package:algolia/algolia.dart';
import 'package:flutter/material.dart';
class Application {
static final Algolia algolia = Algolia.init(
applicationId: 'Algoliaに載ってているapplicationId',
apiKey: 'Algoliaに載っているapiKey',
);
}
class Search extends StatefulWidget {
@override
_SearchState createState() => _SearchState();
}
class _SearchState extends State<Search> {
StreamController searchController = StreamController();
Future searchFireStore(word) async {
Algolia algolia = Application.algolia;
AlgoliaQuery query = algolia.instance.index("ItemName").query(word);
AlgoliaQuerySnapshot snap = await query.getObjects();
List<AlgoliaObjectSnapshot> hits = snap.hits;
searchController.add(hits);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: SearchItems(),
backgroundColor: Colors.white,
),
body: Column(
children: <Widget>[
Expanded(
child: StreamBuilder(
stream: searchController.stream,
builder: (context,AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return Text((snapshot.data[index].data['ItemName']);
},
);
} else {
return Center(
child: Text("No Data"),
);
}
},
),
)
],
),
);
}
}
class SearchItems extends StatefulWidget {
@override
_SearchItemsState createState() => _SearchItemsState();
}
class _SearchItemsState extends State<SearchItems> {
String searchWord = "";
@override
Widget build(BuildContext context) {
return TextField(
decoration: const InputDecoration(
hintText: '検索',
),
controller: TextEditingController(text: searchWord),
onChanged: (String text) {
searchWord = text;
},
onSubmitted: (searchWord) => _SearchState().searchFireStore(searchWord),
);
}
}