Flutter에서 전체 텍스트 검색 구현하기 🔍✨
안녕, 친구들! 오늘은 정말 재밌고 유용한 주제로 찾아왔어. 바로 Flutter에서 전체 텍스트 검색을 구현하는 방법에 대해 알아볼 거야. 😎 이 기능은 앱 개발할 때 정말 많이 쓰이는데, 특히 재능넷 같은 플랫폼에서 사용자들이 원하는 재능이나 서비스를 쉽게 찾을 수 있게 해주는 핵심 기능이지. 그럼 지금부터 Flutter로 어떻게 멋진 검색 기능을 만들 수 있는지 함께 알아보자고!
🚀 시작하기 전에: Flutter와 Dart에 대한 기본적인 이해가 있다면 더 쉽게 따라올 수 있을 거야. 하지만 걱정 마! 모르는 부분이 있더라도 차근차근 설명할 테니까.
1. 전체 텍스트 검색이 뭐야? 🤔
먼저, '전체 텍스트 검색'이 뭔지 알아보자. 전체 텍스트 검색은 데이터베이스나 문서 내의 모든 텍스트를 대상으로 특정 단어나 구문을 찾는 기능이야. 예를 들어, 재능넷에서 '그림 그리기'라는 키워드로 검색하면, 제목뿐만 아니라 설명이나 태그 등 모든 텍스트에서 이 키워드를 포함하는 재능들을 찾아주는 거지.
이 기능이 왜 중요할까? 🧐
- 사용자 경험 향상: 원하는 정보를 빠르고 정확하게 찾을 수 있어.
- 데이터 활용도 증가: 숨겨진 정보도 쉽게 발견할 수 있지.
- 앱의 가치 상승: 강력한 검색 기능은 앱의 사용성을 크게 높여줘.
특히 재능넷 같은 플랫폼에서는 사용자들이 자신에게 필요한 재능을 쉽게 찾을 수 있게 해주는 게 정말 중요해. 그래서 오늘 우리가 배울 내용이 엄청 유용할 거야! 😉
위 그림을 보면, 전체 텍스트 검색이 데이터베이스의 여러 부분을 어떻게 커버하는지 한눈에 볼 수 있어. 제목, 내용, 태그, 심지어 댓글까지! 모든 텍스트가 검색 대상이 되는 거지. 이렇게 하면 사용자가 찾고자 하는 정보를 놓치지 않고 정확하게 찾을 수 있어.
Flutter에서 이런 강력한 검색 기능을 구현하면, 너의 앱은 단숨에 프로 레벨로 업그레이드될 거야! 사용자들이 "와, 이 앱 검색 기능 진짜 좋다!"라고 말하게 될 걸? 그럼 이제 본격적으로 구현 방법을 알아보자고! 🚀
2. Flutter 프로젝트 셋업하기 🛠️
자, 이제 본격적으로 hands-on 할 시간이야! 먼저 Flutter 프로젝트를 셋업해보자. 이미 Flutter 개발 환경이 준비되어 있다고 가정하고 시작할게. 없다면 Flutter 공식 사이트에서 설치 가이드를 참고해줘!
💡 Pro Tip: Flutter 개발 환경 설정은 처음에는 좀 까다로울 수 있어. 하지만 한 번 제대로 해놓으면 앞으로 개발할 때 정말 편해질 거야. 재능넷에서 Flutter 개발 관련 도움을 받을 수 있는 전문가를 찾아보는 것도 좋은 방법이야!
먼저, 새로운 Flutter 프로젝트를 만들어보자:
flutter create text_search_app
cd text_search_app
이렇게 하면 기본적인 Flutter 앱 구조가 만들어져. 이제 우리의 검색 기능을 구현할 준비가 된 거야! 🎉
프로젝트를 생성했으면, pubspec.yaml 파일을 열어서 필요한 패키지들을 추가해야 해. 우리 프로젝트에서 사용할 주요 패키지들은 다음과 같아:
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
http: ^0.13.3
provider: ^6.0.0
sqflite: ^2.0.0+3
path_provider: ^2.0.2
각 패키지의 역할을 간단히 설명해줄게:
- http: 네트워크 요청을 보내기 위한 패키지야. API에서 데이터를 가져올 때 사용할 거야.
- provider: 상태 관리를 위한 패키지. 검색 결과를 효율적으로 관리하는 데 도움을 줘.
- sqflite: SQLite 데이터베이스를 사용하기 위한 패키지. 로컬에서 데이터를 저장하고 검색할 때 사용할 거야.
- path_provider: 앱의 로컬 파일 시스템에 접근하기 위한 패키지. 데이터베이스 파일을 저장할 위치를 지정할 때 사용해.
이 패키지들을 추가했다면, 터미널에서 다음 명령어를 실행해 패키지들을 설치해줘:
flutter pub get
좋아, 이제 기본적인 셋업은 끝났어! 🎊 다음 단계로 넘어가기 전에, 프로젝트가 제대로 실행되는지 확인해보자:
flutter run
모든 게 잘 작동한다면, 기본 Flutter 앱 화면이 보일 거야. 여기서부터 우리의 멋진 검색 기능을 추가해 나갈 거야!
위 그림은 우리가 만들 Flutter 프로젝트의 기본 구조를 보여줘. lib 폴더 안에 screens, models, services 폴더를 만들어서 코드를 깔끔하게 구조화할 거야. 이렇게 하면 나중에 프로젝트가 커져도 관리하기 쉬워지지.
자, 이제 기본 셋업은 끝났어! 다음 단계에서는 실제로 검색 기능을 구현하기 시작할 거야. 준비됐니? Let's dive in! 🏊♂️
3. 데이터 모델 만들기 📊
검색 기능을 구현하기 전에, 먼저 우리가 다룰 데이터의 구조를 정의해야 해. 이걸 '데이터 모델'이라고 부르지. 재능넷을 예로 들면, 각각의 재능 정보를 어떻게 표현할지 결정하는 거야.
lib 폴더 안에 models 폴더를 만들고, 그 안에 talent.dart 파일을 만들어보자.
// lib/models/talent.dart
class Talent {
final int id;
final String title;
final String description;
final String category;
final double price;
Talent({
required this.id,
required this.title,
required this.description,
required this.category,
required this.price,
});
factory Talent.fromJson(Map<string dynamic> json) {
return Talent(
id: json['id'],
title: json['title'],
description: json['description'],
category: json['category'],
price: json['price'].toDouble(),
);
}
Map<string dynamic> toJson() {
return {
'id': id,
'title': title,
'description': description,
'category': category,
'price': price,
};
}
}
</string></string>
이 Talent 클래스는 재능 정보를 표현해. 각 재능은 id, 제목, 설명, 카테고리, 가격 정보를 가지고 있어. fromJson과 toJson 메서드는 JSON 형식과 Dart 객체 사이의 변환을 담당해. 이렇게 하면 서버에서 데이터를 받아오거나 저장할 때 편리하지.
🎨 디자인 팁: 데이터 모델을 설계할 때는 앱의 요구사항을 잘 고려해야 해. 재능넷의 경우, 나중에 리뷰 시스템이나 판매자 정보 같은 걸 추가하고 싶다면 모델을 확장하면 돼. 유연성을 고려한 설계가 중요해!
이제 검색 결과를 표현할 모델도 만들어보자. search_result.dart 파일을 만들고 다음 코드를 추가해:
// lib/models/search_result.dart
import 'talent.dart';
class SearchResult {
final List<talent> results;
final int totalCount;
SearchResult({
required this.results,
required this.totalCount,
});
factory SearchResult.fromJson(Map<string dynamic> json) {
return SearchResult(
results: (json['results'] as List)
.map((item) => Talent.fromJson(item))
.toList(),
totalCount: json['total_count'],
);
}
}
</string></talent>
이 SearchResult 클래스는 검색 결과를 담고 있어. results 리스트에는 검색된 Talent 객체들이 들어가고, totalCount는 전체 검색 결과의 개수를 나타내지. 이렇게 하면 페이지네이션 같은 기능을 구현할 때도 유용해.
위 그림은 우리가 만든 데이터 모델의 구조를 보여줘. Talent 클래스는 개별 재능 정보를 나타내고, SearchResult 클래스는 여러 Talent 객체를 포함하는 검색 결과를 표현해. 이렇게 구조화하면 데이터를 체계적으로 관리할 수 있어.
이런 데이터 모델을 사용하면 앱 전체에서 일관된 방식으로 데이터를 다룰 수 있어. 예를 들어, 검색 결과를 표시할 때나 상세 정보 페이지를 만들 때 이 모델을 사용하면 돼. 재능넷에서 새로운 재능을 등록하거나 검색할 때도 이 모델을 기반으로 작업할 수 있겠지?
다음 단계에서는 이 데이터 모델을 사용해서 실제로 데이터를 가져오고 저장하는 서비스를 만들 거야. 준비됐니? 계속 가보자고! 🚀
4. 데이터베이스 헬퍼 만들기 💾
자, 이제 우리의 앱에서 데이터를 저장하고 검색할 수 있게 해주는 데이터베이스 헬퍼를 만들어볼 거야. 이 헬퍼는 SQLite 데이터베이스를 사용해서 로컬에 데이터를 저장하고 관리해줄 거야.
lib 폴더 안에 services 폴더를 만들고, 그 안에 database_helper.dart 파일을 생성해보자.
// lib/services/database_helper.dart
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import '../models/talent.dart';
class DatabaseHelper {
static final DatabaseHelper instance = DatabaseHelper._init();
static Database? _database;
DatabaseHelper._init();
Future<database> get database async {
if (_database != null) return _database!;
_database = await _initDB('talents.db');
return _database!;
}
Future<database> _initDB(String filePath) async {
final dbPath = await getDatabasesPath();
final path = join(dbPath, filePath);
return await openDatabase(path, version: 1, onCreate: _createDB);
}
Future<void> _createDB(Database db, int version) async {
await db.execute('''
CREATE TABLE talents(
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
description TEXT NOT NULL,
category TEXT NOT NULL,
price REAL NOT NULL
)
''');
}
Future<int> insertTalent(Talent talent) async {
final db = await instance.database;
return await db.insert('talents', talent.toJson());
}
Future<list>> getAllTalents() async {
final db = await instance.database;
final result = await db.query('talents');
return result.map((json) => Talent.fromJson(json)).toList();
}
Future<list>> searchTalents(String query) async {
final db = await instance.database;
final result = await db.query(
'talents',
where: 'title LIKE ? OR description LIKE ?',
whereArgs: ['%$query%', '%$query%'],
);
return result.map((json) => Talent.fromJson(json)).toList();
}
}
</list></list></int></void></database></database>
우와, 코드가 좀 길지? 하나씩 설명해줄게!
- DatabaseHelper 클래스는 싱글톤 패턴을 사용해. 이렇게 하면 앱 전체에서 하나의 데이터베이스 인스턴스만 사용할 수 있어.
- _initDB 메서드는 데이터베이스 파일을 생성하거나 열어.
- _createDB 메서드는 talents 테이블을 생성해. 이 테이블에 우리의 Talent 정보가 저장될 거야.
- insertTalent 메서드는 새로운 재능을 데이터베이스에 추가해.
- getAllTalents 메서드는 모든 재능 정보를 가져와.
- searchTalents 메서드가 바로 우리의 핵심 기능이야! 이 메서드는 주어진 쿼리로 제목이나 설명에서 검색을 수행해.
🔍 검색 팁: searchTalents 메서드에서 LIKE 연산자와 % 와일드카드를 사용하고 있어. 이렇게 하면 부분 일치도 검색할 수 있지. 예를 들어, "그림"으로 검색하면 "그림 그리기", "캐리커처 그림" 등도 모두 결과에 포함돼.
이제 이 DatabaseHelper를 사용해서 재능 데이터를 저장하고 검색할 수 있어. 예를 들어, 재능넷에서 새로운 재능을 등록할 때 insertTalent 메서드를 호출하면 되고, 검색 기능을 구현할 때는 searchTalents 메서드를 사용하면 돼.
위 그림은 DatabaseHelper의 작동 방식을 보여줘. 앱에서 데이터를 요청하면, DatabaseHelper가 그 요청을 처리하고 SQLite 데이터베이스와 통신해. 이렇게 하면 앱의 다른 부분에서는 복잡한 데이터베이스 로직을 신경 쓰지 않고도 쉽게 데이터를 다룰 수 있어.
이제 우리의 앱은 로컬 데이터베이스를 사용해 빠르고 효율적인 검색을 할 수 있게 됐어! 이는 재능넷 같은 앱에서 정말 중요한 기능이지. 사용자들이 오프라인 상태에서도 저장된 재능 정보를 검색할 수 있으니까.
다음 단계에서는 이 DatabaseHelper를 사용해서 실제로 검색 기능을 구현하는 서비스를 만들 거야. 그리고 나서 UI까지 만들면, 드디어 우리의 전체 텍스트 검색 기능이 완성될 거야! 😃 계속 가보자!
5. 검색 서비스 구현하기 🔍
자, 이제 우리가 만든 DatabaseHelper를 활용해서 실제로 검색 기능을 수행할 서비스를 만들어볼 거야. 이 서비스는 앱의 다른 부분에서 쉽게 검색 기능을 사용할 수 있게 해줄 거야.
lib/services 폴더에 search_service.dart 파일을 만들고 다음 코드를 추가해보자:
// lib/services/search_service.dart
import 'database_helper.dart';
import '../models/talent.dart';
import '../models/search_result.dart';
class SearchService {
final DatabaseHelper _dbHelper = DatabaseHelper.instance;
Future<searchresult> searchTalents(String query) async {
final talents = await _dbHelper.searchTalents(query);
return SearchResult(
results: talents,
totalCount: talents.length,
);
}
Future<void> addTalent(Talent talent) async {
await _dbHelper.insertTalent(talent);
}
Future<list>> getAllTalents() async {
return await _dbHelper.getAllTalents();
}
}
</list></void></searchresult>
이 SearchService 클래스는 다음과 같은 기능을 제공해:
- searchTalents: 주어진 쿼리로 재능을 검색하고 SearchResult 객체를 반환해.
- addTalent: 새로운 재능을 데이터베이스에 추가해.
- getAllTalents: 모든 재능 정보를 가져와.
이제 이 서비스를 사용해서 앱의 어느 부분에서든 쉽게 검색 기능을 구현할 수 있어. 예를 들어, 검색 화면에서 이 서비스를 호출하면 돼.
💡 Pro Tip: 실제 앱에서는 네트워크 요청과 로컬 데이터베이스 검색을 결합할 수 있어. 예를 들어, 먼저 로컬에서 검색하고, 결과가 충분하지 않으면 서버에 요청을 보내는 방식으로 구현할 수 있지. 이렇게 하면 앱의 응답 속도도 빨라지고, 데이터 사용량도 줄일 수 있어!
자, 이제 검색 기능의 백엔드 부분이 거의 다 완성됐어! 다음 단계에서는 이 기능을 실제로 사용자가 볼 수 있는 UI를 만들어볼 거야. Flutter의 강력한 위젯 시스템을 사용해서 멋진 검색 화면을 만들어보자고!
위 그림은 우리가 만든 검색 서비스의 전체 구조를 보여줘. UI에서 시작된 검색 요청이 SearchService를 거쳐 DatabaseHelper로 전달되고, 최종적으로 SQLite 데이터베이스에서 데이터를 가져오는 과정을 볼 수 있어. 이런 구조로 만들면 각 부분이 독립적으로 작동하면서도 효율적으로 연결돼 있어서 유지보수하기 좋은 코드가 되지.
이제 우리의 앱은 강력한 검색 기능을 갖추게 됐어! 재능넷 같은 서비스에서 이런 검색 기능은 정말 중요해. 사용자들이 원하는 재능을 쉽고 빠르게 찾을 수 있으니까. 다음 단계에서는 이 기능을 실제로 사용자가 사용할 수 있게 UI를 만들어볼 거야. Flutter의 멋진 위젯들을 사용해서 예쁘고 사용하기 쉬운 검색 화면을 만들어보자!
6. 검색 UI 구현하기 🎨
드디어 우리의 검색 기능을 사용자가 실제로 볼 수 있는 UI를 만들 차례야! Flutter의 강력한 위젯 시스템을 사용해서 멋진 검색 화면을 만들어보자.
lib/screens 폴더에 search_screen.dart 파일을 만들고 다음 코드를 추가해:
// lib/screens/search_screen.dart
import 'package:flutter/material.dart';
import '../services/search_service.dart';
import '../models/talent.dart';
class SearchScreen extends StatefulWidget {
@override
_SearchScreenState createState() => _SearchScreenState();
}
class _SearchScreenState extends State<searchscreen> {
final SearchService _searchService = SearchService();
List<talent> _searchResults = [];
final TextEditingController _searchController = TextEditingController();
void _performSearch(String query) async {
if (query.isEmpty) {
setState(() {
_searchResults = [];
});
return;
}
final results = await _searchService.searchTalents(query);
setState(() {
_searchResults = results.results;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('재능 검색'),
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: _searchController,
decoration: InputDecoration(
labelText: '재능을 검색해보세요',
suffixIcon: Icon(Icons.search),
border: OutlineInputBorder(),
),
onChanged: _performSearch,
),
),
Expanded(
child: ListView.builder(
itemCount: _searchResults.length,
itemBuilder: (context, index) {
final talent = _searchResults[index];
return ListTile(
title: Text(talent.title),
subtitle: Text(talent.description),
trailing: Text('${talent.price}원'),
onTap: () {
// TODO: 상세 페이지로 이동
},
);
},
),
),
],
),
);
}
}
</talent></searchscreen>
우와, 코드가 좀 길지? 하나씩 설명해줄게:
- SearchScreen은 StatefulWidget이야. 검색 결과가 동적으로 변하기 때문이지.
- _performSearch 메서드는 사용자가 입력한 쿼리로 검색을 수행해.
- UI는 검색 입력 필드와 결과 목록으로 구성돼 있어.
- TextField의 onChanged 속성을 사용해서 사용자가 입력할 때마다 실시간으로 검색이 수행돼.
- 검색 결과는 ListView.builder를 사용해 효율적으로 표시돼.
🎨 UI 팁: 실제 앱에서는 검색 결과가 없을 때 적절한 메시지를 표시하거나, 로딩 중일 때 프로그레스 인디케이터를 보여주는 것이 좋아. 또한, 디바운스(debounce) 기법을 사용해서 사용자가 입력을 멈춘 후에만 검색을 수행하면 성능을 더 개선할 수 있어!
이제 이 SearchScreen을 앱의 메인 화면으로 설정하거나, 네비게이션 메뉴에 추가해서 사용자가 접근할 수 있게 만들면 돼.
위 그림은 우리가 만든 검색 화면의 UI를 보여줘. 상단에 검색 입력 필드가 있고, 그 아래로 검색 결과가 리스트 형태로 표시돼. 각 항목은 재능의 제목, 간단한 설명, 그리고 가격 정보를 포함하고 있어. 이런 직관적인 UI 덕분에 사용자들은 쉽고 빠르게 원하는 재능을 찾을 수 있을 거야.
축하해! 이제 우리의 Flutter 앱에 완전한 전체 텍스트 검색 기능이 구현됐어. 데이터 모델부터 시작해서 데이터베이스 헬퍼, 검색 서비스, 그리고 UI까지 모든 것을 만들었지. 이 기능은 재능넷 같은 플랫폼에서 정말 중요한 역할을 할 거야. 사용자들이 원하는 재능을 쉽고 빠르게 찾을 수 있으니까!
다음 단계로는 이 검색 기능을 더 개선할 수 있어. 예를 들어, 검색 결과에 페이지네이션을 추가하거나, 검색 필터를 구현하거나, 검색 기록을 저장하는 기능을 추가할 수 있지. 또한, 서버 측 검색과 로컬 검색을 결합해서 더 강력한 검색 시스템을 만들 수도 있어.
어때? 이제 너의 Flutter 앱이 한층 더 강력해졌지? 이런 검색 기능은 많은 앱에서 핵심적인 역할을 해. 네가 만든 이 기능으로 사용자들이 더 쉽고 편리하게 앱을 사용할 수 있을 거야. 계속해서 기능을 개선하고 새로운 아이디어를 추가해 나가면, 정말 멋진 앱이 될 거야! 화이팅! 🚀✨