앱을 개발할 때 항상 들어가야 하는 기능 중 하나인 검색 기능에 대해 알아보도록 하겠다.
‘Flutter 검색’이라고 구글링하면 정말 다양한 방식으로 구현한 블로그 글들이 나온다.
따라서 다른 검색 구현 방식과 차별점을 두기 위해 다음과 같은 조건들을 두겠다.
- 첫 검색일 경우에 검색 결과 페이지(새로운 페이지)로 이동
- 새로운 검색 단어로 검색 시( Enter 입력) 페이지 이동 없이 결과 변경
- 검색어를 삭제할 때 모든 아이템 출력


StreamBuilder vs FutureBuilder
이전 Retrofit 관련 글에서는 Future를 활용하여 구현하였다.
처음 시도했을 때는 FutureBuilder를 통해 API를 호출하여 검색 기능을 구현했었다.
하지만 FutureBuilder로 검색 기능을 구현하면 검색어가 바뀔 때마다 새로운 호출을 해야 하는 상황에 놓였고, 매번 화면을 초기화하고 새로 덮어씌우는 비효율적인 로직으로 구현이 되었다.
그래서 다른 구현 방법을 탐색하게 되었고, StreamBuilder 함수를 찾을 수 있었다.
StreamBuilder
SteamBuilder도 비동기 이벤트를 처리하는 방법들 중 하나이다.
하지만 Futurebuilder와 다른 가장 큰 특징은 연속적으로 발생하는 비동기 이벤트를 처리하여 UI 업데이트가 필요한 곳에 사용된다. 즉 SetState() 함수를 사용하지 않고도 UI를 빌드할 수 있다.
- Future : 단일 비동기 연산
- Stream: 연속적인 비동기 연산 (Sequence)
예를 들어 채팅, 실시간 통계와 같이 UI가 수시로 변하는 곳에 사용이 된다.
‘별 헤는밤’ 검색 페이지는 검색어를 변경했을 때 실시간으로 UI가 변경되어야 하므로 StreamBuilder를 선택하게 되었다.
StreamBuilder<int>(
stream: 호출할 Stream 명,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
return Text('Data: ${snapshot.data}');
}
},
);
StreamBuilder 예시 코드
검색 기능 구현
- StreamController 선언
- StreamController: Dart에서 Stream을 생성하고 제어하는데 사용되는 Controller
- 선언 방식
StreamController<List<검색결과Item>> _searchController =
StreamController <List<검색결과Item>>();
2. didChangeDependencies
- didChangeDependencies: StatefulWidget에 사용되는 생명주기 메서드 중 하나로 Widget의 종속성이 변경되었을 때 호출된다. 보통 초기화 작업과 종속성 관련 초기 설정할 때 사용한다.
여기에 이전 페이지들에서 넘어올 다양한 필터값, 검색어, 해시태그 등을 설정할 것이다.
(상세 코드는 업계 비밀이므로 예시로만 보여주도록 하겠다)
- 예시 코드
@override
void didChangeDependencies() {
if(isInit){
//이전 페이지에서 넘어온 argument(ex 검색어)
final arguments = ModalRoute.of(context)!.settings.arguments;
if(type==1){
~~~~ 상황에 따른 초기화 코드들 ~~~
}
}
super.didChangeDependencies();
}
3. dispose
- dispose: StatefulWidget 메서드 중 하나로, 위젯이 삭제될 때 호출된다. 검색 페이지를 떠날 때 실행시켰던 StreamController를 중지시키는 역할을 한다.
@override
void dispose() {
_searchController.close();
super.dispose();
}
4. Retrofit Call 호출 함수
FutureBuilder와 가장 큰 차이는 StreamBuilder는 Retrofit Call 함수는 void로 선언되고 Call에 대한 결과값을 controller에 add 하면 된다.
void SearchCall(SearchKey searchKey) async{
final List<검색결과Item> result = await _RestClient.[Call이름](searchKey);
_searchController.add(result);
}
여기서 SearchKey는 자체적으로 만든 검색어 Entity이다.
전체 코드
class _StarSearchState extends State<StarSearch> {
late final RestClient _RestClient;
StreamController<List<StarItem>> _searchController = StreamController <List<StarItem>>();
late SearchKey searchKey;
void initState() {
super.initState();
Dio dio = Dio();
_StarRestClient = StarRestClient(dio);
isInit = true;
}
@override
void didChangeDependencies() {
if(isInit){
//이전 페이지에서 넘어온 argument(ex 검색어)
final arguments = ModalRoute.of(context)!.settings.arguments;
if(type==1){
~~~~ 상황에 따른 초기화 코드들 ~~~
}
}
super.didChangeDependencies();
}
void SearchCall(SearchKey searchKey) async{
final List<Type> result = await _RestClient.[Call이름](searchKey);
_searchController.add(result);
}
@override
void dispose() {
_searchController.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false, // bottom overflowed by pixel 방지 코드
body: SingleChildScrollView(
child: Column(
children: <Widget>[
~~ 검색창 UI 코드 생략 ~~
StreamBuilder(stream: _searchController.stream,
builder: (BuildContext context, AsyncSnapshot snapshot){
if(snapshot.hasData){
final List<검색결과Item> result = snapshot.data;
return ~~ 출력할 검색 결과 창 ~~
}else if(snapshot.hasError){
print('Error: ${snapshot.error}');
return Text('Error: ${snapshot.error}');}
else{
return Center(child: CircularProgressIndicator());
}
})
]
),
),
);
}
}
정리
이로써 StreamBuilder를 활용하여 검색 기능을 구현하였다.
검색 결과 창 코드는 snapshot.data를 활용하기 때문에 FutureBuilder를 사용했을 경우랑 코드가
아예 똑같기 때문에 생략하였다.
단순히 보면 FutureBuilder와 코드가 거의 유사하기 때문에 차이점을 거의 못 느낄 수도 있다.
하지만 StreamBuilder를 사용할 때는 실시간 데이터를 사용하므로 dispose, didChangeDependencies와 같은 StatefulWidget의 메소드들을 이해해야 하는 주의점이 있다.
앞으로 UI를 구성할 때 실시간으로 UI가 변해야 하는 경우가 있을 때는 FutureBuilder대신 StreamBuilder를 사용해 보도록 하자!
별 헤는 밤: 밤하늘, 별자리, 여행정보와 날씨예보까지 - Google Play 앱
오늘부터 별잘알! 오늘밤 별자리 정보, 날씨·광공해·월령까지 고려한 '관측적합도', 주변 관측지 검색으로 누구나 쉽게 밤하늘을 즐겨보세요.
play.google.com