영화 데이터를 얻는 작업과, 영화 데이터를 클라우드 DB(Firestore)에 저장하는 작업, 앞의 두 작업을 자동화하는 작업까지 끝냈다.
이번에는 위의 작업으로 얻은 데이터를 앱에서 사용하기 위해 해야 할 작업에 대해 알아보자.
우선 초기 영화 데이터를 만들어 앱에 추가한다.
만약에 모든 데이터를 Firestore에서 가져와 저장하게 된다면 읽기 수가 너무 많아 요금폭탄이 발생한다..
따라서 초기 데이터를 만들어 앱에 추가하도록 한다.
영화 데이터를 다루는 클래스, 함수들도 필요하니 만들어야 한다.
해야 할 작업
- 초기 데이터 파일 만들기
- 데이터 모델, DB Class 만들기
초기 데이터 파일 만들기
2008/01/01부터 2023/09/30까지의 영화 데이터를 클라우드 DB에 저장한다.
(로컬에 저장해놓기도 할거지만 혹시 읽어버리면 귀찮아지니 그냥 클라우드 DB에 저장해 놓기로)
# 전체 소스파일은 ChildMovie_backend 레포에 있다.
# 여기서는 이 함수를 실행한다를 기록하는 목적으로 update함수 실행만 기록
# 2008/01/01 ~ 2023/09/30 까지의 영화 데이터를
# id가 'initial_data'인 컬렉션에 저장
update(
key_path='keys.json',
start_date='20080101',
end_date='20230930',
collection_id='initial_data',
my_driver=driver,
)
그 후 클라우드 DB의 initial_data 컬렉션을 읽어 db파일로 만들자.
sqlite3 라이브러리를 사용하여 db파일을 생성한다.
(sqlite3은 SQLite를 사용하는 라이브러리이다. 파이썬 표준 라이브러리라 설치 없이 사용가능)
(3.9 버전 문서는 번역이 거의 다 되어있어 읽기 편해서 링크 걸어둠)
https://docs.python.org/ko/3.9/library/sqlite3.html
(3.10)
https://docs.python.org/ko/3.10/library/sqlite3.html
클라우드 DB의 initial_data 컬렉션을 읽어 db파일로 만들어주는 함수
def create_db_file(firestore_key, file_path: str = 'db.db', collection_id: str = 'initial_data'):
if Path(file_path).exists():
print('이미 같은 이름의 .db파일이 존재합니다!')
return
movie_list = []
default_dic = {
'aplcName': None,
'coreHarmRsn': None,
'descriptive_content': None,
'direName': None,
'direNatnlName': None,
'gradeName': None,
'leadaName': None,
'mvAssoName': None,
'oriTitle': None,
'prodYear': None,
'prodcName': None,
'prodcNatnlName': None,
'rtCoreHarmRsnNm': None,
'rtDate': None,
'rtNo': None,
'rtStdName1': None,
'rtStdName2': None,
'rtStdName3': None,
'rtStdName4': None,
'rtStdName5': None,
'rtStdName6': None,
'rtStdName7': None,
'screTime': None,
'stadCont': None,
'suppaName': None,
'useTitle': None,
'workCont': None,
}
table_name = "movies"
create_table_query =f"""
CREATE TABLE IF NOT EXISTS {table_name} (
id INTEGER PRIMARY KEY,
aplcName TEXT,
coreHarmRsn TEXT,
descriptive_content TEXT,
direName TEXT,
direNatnlName TEXT,
gradeName TEXT,
leadaName TEXT,
mvAssoName TEXT,
oriTitle TEXT,
prodYear TEXT,
prodcName TEXT,
prodcNatnlName TEXT,
rtCoreHarmRsnNm TEXT,
rtDate TEXT,
rtNo TEXT,
rtStdName1 TEXT,
rtStdName2 TEXT,
rtStdName3 TEXT,
rtStdName4 TEXT,
rtStdName5 TEXT,
rtStdName6 TEXT,
rtStdName7 TEXT,
screTime TEXT,
stadCont TEXT,
suppaName TEXT,
useTitle TEXT,
workCont TEXT
)
"""
columns = ', '.join(default_dic.keys())
placeholders = ':' + ', :'.join(default_dic.keys())
insert_query = f'INSERT INTO {table_name} ({columns}) VALUES ({placeholders})'
if not is_app_initialized():
cred = credentials.Certificate(firestore_key)
firebase_admin.initialize_app(cred)
db = firestore.client()
documents = db.collection(collection_id).get()
for document in documents:
movie_list.append({**default_dic, **document.to_dict()})
con = sqlite3.connect(file_path)
cur = con.cursor()
cur.execute(create_table_query)
cur.executemany(insert_query, movie_list)
con.commit()
con.close()
print(f'{file_path}에 저장되었습니다!')
return
위의 함수를 실행하면 file_path에 "db파일 이름" db 파일이 생성된다. (약 14MB 정도 되더라)
이 db파일은 앱의 앱 내부 DB파일을 초기 세팅할 때 사용한다.
assets 폴더에 넣어둔다.
Data Model, DB Class 만들기
위에서 만든 db파일을 열고, 읽고, 데이터를 추가 하기 위한 클래스를 만들어야 한다.
우선 앱에서 사용할 영화 데이터 모델을 만들자
(앱은 Dart(Flutter)로 구현한다.)
class Movie{
String? aplcName;
String? coreHarmRsn;
String? descriptive_content;
String? direName;
String? direNatnlName;
String? gradeName;
String? leadaName;
String? mvAssoName;
String? oriTitle;
String? prodYear;
String? prodcName;
String? prodcNatnlName;
String? rtCoreHarmRsnNm;
String? rtDate;
String? rtNo;
String? rtStdName1;
String? rtStdName2;
String? rtStdName3;
String? rtStdName4;
String? rtStdName5;
String? rtStdName6;
String? rtStdName7;
String? screTime;
String? stadCont;
String? suppaName;
String? useTitle;
String? workCont;
Movie({
this.aplcName,
this.coreHarmRsn,
this.descriptive_content,
this.direName,
this.direNatnlName,
this.gradeName,
this.leadaName,
this.mvAssoName,
this.oriTitle,
this.prodYear,
this.prodcName,
this.prodcNatnlName,
this.rtCoreHarmRsnNm,
this.rtDate,
this.rtNo,
this.rtStdName1,
this.rtStdName2,
this.rtStdName3,
this.rtStdName4,
this.rtStdName5,
this.rtStdName6,
this.rtStdName7,
this.screTime,
this.stadCont,
this.suppaName,
this.useTitle,
this.workCont,
});
Movie.fromJson(Map<String, dynamic> json) {
aplcName = json['aplcName'];
coreHarmRsn = json['coreHarmRsn'];
descriptive_content = json['descriptive_content'];
direName = json['direName'];
direNatnlName = json['direNatnlName'];
gradeName = json['gradeName'];
leadaName = json['leadaName'];
mvAssoName = json['mvAssoName'];
oriTitle = json['oriTitle'];
prodYear = json['prodYear'];
prodcName = json['prodcName'];
prodcNatnlName = json['prodcNatnlName'];
rtCoreHarmRsnNm = json['rtCoreHarmRsnNm'];
rtDate = json['rtDate'];
rtNo = json['rtNo'];
rtStdName1 = json['rtStdName1'];
rtStdName2 = json['rtStdName2'];
rtStdName3 = json['rtStdName3'];
rtStdName4 = json['rtStdName4'];
rtStdName5 = json['rtStdName5'];
rtStdName6 = json['rtStdName6'];
rtStdName7 = json['rtStdName7'];
screTime = json['screTime'];
stadCont = json['stadCont'];
suppaName = json['suppaName'];
useTitle = json['useTitle'];
workCont = json['workCont'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['aplcName'] = this.aplcName;
data['coreHarmRsn'] = this.coreHarmRsn;
data['descriptive_content'] = this.descriptive_content;
data['direName'] = this.direName;
data['direNatnlName'] = this.direNatnlName;
data['gradeName'] = this.gradeName;
data['leadaName'] = this.leadaName;
data['mvAssoName'] = this.mvAssoName;
data['oriTitle'] = this.oriTitle;
data['prodYear'] = this.prodYear;
data['prodcName'] = this.prodcName;
data['prodcNatnlName'] = this.prodcNatnlName;
data['rtCoreHarmRsnNm'] = this.rtCoreHarmRsnNm;
data['rtDate'] = this.rtDate;
data['rtNo'] = this.rtNo;
data['rtStdName1'] = this.rtStdName1;
data['rtStdName2'] = this.rtStdName2;
data['rtStdName3'] = this.rtStdName3;
data['rtStdName4'] = this.rtStdName4;
data['rtStdName5'] = this.rtStdName5;
data['rtStdName6'] = this.rtStdName6;
data['rtStdName7'] = this.rtStdName7;
data['screTime'] = this.screTime;
data['stadCont'] = this.stadCont;
data['suppaName'] = this.suppaName;
data['useTitle'] = this.useTitle;
data['workCont'] = this.workCont;
return data;
}
}
앞에서 db파일을 만들 때 SQLite를 사용했으므로, 앱 구현에서도 SQLite를 사용한다.
Flutter에서 SQLite를 사용하려면 sqflite 패키지를 사용해야 한다.
(sqlite가 아니라 sqflite임..)
https://pub.dev/packages/sqflite
다음은 DB Class(MovieDatabase class)를 만들자.
MovieDatabase클래스를 만들어 영화 데이터를 다루도록 한다.
MovieDatabase 클래스의 클래스 함수는 크게 4가지로 구성된다.
- openDB
- insertMovie
- getMovieFromTitle
- deleteDB
클래스 변수
late Database db;
1. openDB()
db파일을 연다. 만약 databasePath에 db파일이 없다면 assets폴더에서 가져와 databasePath에 저장한다.
Future openDB() async{
final databasePath = await getDatabasesPath();
String path = join(databasePath, DB_FILE_NAME);
var exists = await databaseExists(path);
if (!exists) {
print("Creating $DB_FILE_NAME from asset");
try {
await Directory(dirname(path)).create(recursive: true);
} catch (_) {}
ByteData data = await rootBundle.load(join("assets", DB_FILE_NAME));
List<int> bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
await File(path).writeAsBytes(bytes, flush: true);
print('Created!');
}
db = await openDatabase(
path,
version: 1,
onConfigure: (Database db) => {},
onCreate: (Database db, int version) => {},
onUpgrade: _onUpgrade,
);
print('Opened!');
}
2. insertMovie(Movie movie)
영화 데이터를 db파일에 추가한다.
위에서 만든 영화 데이터 모델(Movie)을 인자로 받음.
Future<Movie> insertMovie(Movie movie) async{
await db.insert(TABLE_NAME, movie.toJson(),conflictAlgorithm: ConflictAlgorithm.replace,);
return movie;
}
3. getMovieFronTitle(String word, bool block18)
영화 제목을 인자로 받고, 영화 제목이 들어가 있는 영화 데이터들을 반환한다.
block18 파라미터가 True 일 경우에는 청소년관람불가 영화를 반환하지 않는다.
Future<List<Movie>> getMovieFromTitle(String word, bool block18) async{
List<Movie> searchResult = [];
List<Map<String, dynamic>> maps;
if (block18){
maps = await db.rawQuery('SELECT * FROM $TABLE_NAME WHERE (oriTitle LIKE \'%$word%\' OR useTitle LIKE \'%$word%\') AND gradeName != \'청소년관람불가\'');
}
else{
maps = await db.rawQuery('SELECT * FROM $TABLE_NAME WHERE oriTitle LIKE \'%$word%\' OR useTitle LIKE \'%$word%\'');
}
for (int i = 0; i < maps.length; i++) {
searchResult.add(Movie.fromJson(maps[i]));
}
print(maps.length);
return searchResult;
}
4. deleteDB()
databasePath에 있는 db파일 삭제. 이건 그냥 개발할 때 사용했음.
Future deleteDB() async{
final databasePath = await getDatabasesPath();
String path = join(databasePath, DB_FILE_NAME);
await databaseFactory.deleteDatabase(path);
print('done');
}
전체 소스코드
class MovieDatabase{
late Database db;
Future openDB() async{
final databasePath = await getDatabasesPath();
String path = join(databasePath, DB_FILE_NAME);
var exists = await databaseExists(path);
if (!exists) {
print("Creating $DB_FILE_NAME from asset");
try {
await Directory(dirname(path)).create(recursive: true);
} catch (_) {}
ByteData data = await rootBundle.load(join("assets", DB_FILE_NAME));
List<int> bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
await File(path).writeAsBytes(bytes, flush: true);
print('Created!');
}
db = await openDatabase(
path,
version: 1,
onConfigure: (Database db) => {},
onCreate: (Database db, int version) => {},
onUpgrade: _onUpgrade,
);
print('Opened!');
}
Future<Movie> insertMovie(Movie movie) async{
await db.insert(TABLE_NAME, movie.toJson(),conflictAlgorithm: ConflictAlgorithm.replace,);
return movie;
}
Future<List<Movie>> getMovieFromTitle(String word, bool block18) async{
List<Movie> searchResult = [];
List<Map<String, dynamic>> maps;
if (block18){
maps = await db.rawQuery('SELECT * FROM $TABLE_NAME WHERE (oriTitle LIKE \'%$word%\' OR useTitle LIKE \'%$word%\') AND gradeName != \'청소년관람불가\'');
}
else{
maps = await db.rawQuery('SELECT * FROM $TABLE_NAME WHERE oriTitle LIKE \'%$word%\' OR useTitle LIKE \'%$word%\'');
}
for (int i = 0; i < maps.length; i++) {
searchResult.add(Movie.fromJson(maps[i]));
}
print(maps.length);
return searchResult;
}
Future deleteDB() async{
final databasePath = await getDatabasesPath();
String path = join(databasePath, DB_FILE_NAME);
await databaseFactory.deleteDatabase(path);
print('done');
}
}
이렇게 해서 초기 데이터(db파일)를 만들고, 영화 데이터를 다루는데 필요한 클래스와 함수를 만들었다.
다음 글에서는 클라우드 DB(Firestore)에서 최신 데이터를 받아와서 db파일에 저장하는 과정에 대해 서술하겠다.
'프로젝트 > 아이의 영화' 카테고리의 다른 글
아이의 영화 앱 만들기#8-AdMob (0) | 2023.11.09 |
---|---|
아이의 영화 앱 만들기#7-업데이트 (2) | 2023.11.01 |
아이의 영화 앱 만들기#5-자동화 (Google Cloud Run) (0) | 2023.10.24 |
아이의 영화 앱 만들기#4-데이터 저장(Firestore) (0) | 2023.10.18 |
아이의 영화 앱 만들기#3-영화 데이터(2) (0) | 2023.10.17 |