반응형
메모 관리를 위한 MySQL 설정
MySQL - 테이블 생성
- memo 테이블 생성
- 컬럼은 아래와 같음
- 특이사항
- 메모 추가 시간 자동 등록 : createDate의 Deafault/Expression : now()
- 메모 수정 시간 자동 업데이트 : updateDate의 Default/Expression : now() on update now()
- Default/Expression에 위와 같이 입력하면 자동으로 그림처럼 변경 됨
- FK 설정
- 이 포스팅은 로그인된 유저에게만 메모를 작성하도록 설계되어 있음
- 로그인되어 있는 유저의 인덱스 번호가 필요
- 참고 : users 테이블의 정보는 다음과 같음
Flutter - MySQL 연동
포스팅의 내용이 매우 길어질 것 같아서 아래의 포스팅의 링크로 대처 합니다.
- 기본적인 MySQL 연동하는 방법
- 이 포스팅과 동일한 설정 하기
Flutter - 메모 관리를 위한 DB 설계
- 메모 DB 쿼리 관련 코드를 두기 위한 파일 생성
- memoDB.dart
모든 메모 보기
// 모든 메모 보기
Future<IResultSet?> selectMemoALL() async {
// MySQL 접속 설정
final conn = await dbConnector();
// 유저 식별 정보 호출
SharedPreferences prefs = await SharedPreferences.getInstance();
final String? token = prefs.getString('token');
// DB에 저장된 메모 리스트
IResultSet result;
// 유저의 모든 메모 보기
try {
result = await conn.execute(
"SELECT m.id, userIndex, u.userName, memoTitle, memoContent, createDate, updateDate FROM memo AS m LEFT JOIN users AS u ON m.userIndex = u.id WHERE userIndex = :token",
{"token": token});
if (result.numOfRows > 0) {
return result;
}
} catch (e) {
print('Error : $e');
} finally {
await conn.close();
}
// 메모가 없으면 null 값 반환
return null;
}
메모 작성하기
// 메모 작성
Future<String?> addMemo(String title, String content) async {
// MySQL 접속 설정
final conn = await dbConnector();
// 유저 식별 정보 호출
SharedPreferences prefs = await SharedPreferences.getInstance();
final String? token = prefs.getString('token');
// 쿼리 수행 결과 저장 변수
IResultSet? result;
// 유저의 아이디를 저장할 변수
String? userName;
// 메모 추가
try {
// 유저 이름 확인
result = await conn.execute(
"SELECT userName FROM users WHERE id = :token",
{"token": token},
);
// 유저 이름 저장
for (final row in result.rows) {
userName = row.colAt(0);
}
// 메모 추가
result = await conn.execute(
"INSERT INTO memo (userIndex, memoTitle, memoContent) VALUES (:userIndex, :title, :content)",
{"userIndex": token, "title": title, "content": content},
);
} catch (e) {
print('Error : $e');
} finally {
await conn.close();
}
// 예외처리용 에러코드 '-1' 반환
return '-1';
}
메모 수정하기
// 메모 수정
Future<void> updateMemo(String id, String title, String content) async {
// MySQL 접속 설정
final conn = await dbConnector();
// 유저 식별 정보 호출
SharedPreferences prefs = await SharedPreferences.getInstance();
final String? token = prefs.getString('token');
// 쿼리 수행 결과 저장 변수
IResultSet? result;
// 메모 수정
try {
await conn.execute(
"UPDATE memo SET memoTitle = :title, memoContent = :content where id = :id and userIndex = :token",
{"id": id, "token": token, "title": title, "content": content});
} catch (e) {
print('Error : $e');
} finally {
await conn.close();
}
}
특정 메모 조회하기
// 특정 메모 조회
Future<IResultSet?> selectMemo(String id) async {
// MySQL 접속 설정
final conn = await dbConnector();
// 유저 식별 정보 호출
SharedPreferences prefs = await SharedPreferences.getInstance();
final String? token = prefs.getString('token');
// 쿼리 수행 결과 저장 변수
IResultSet? result;
// 메모 수정
try {
result = await conn.execute(
"SELECT m.id, userIndex, u.userName, memoTitle, memoContent, createDate, updateDate FROM memo AS m LEFT JOIN users AS u ON m.userIndex = u.id WHERE userIndex = :token and m.id = :id",
{"token": token, "id": id});
return result;
} catch (e) {
print('Error : $e');
} finally {
await conn.close();
}
return null;
}
특정 메모 삭제하기
// 특정 메모 삭제
Future<void> deleteMemo(String id) async {
// MySQL 접속 설정
final conn = await dbConnector();
// 메모 수정
try {
await conn.execute("DELETE FROM memo WHERE id = :id", {"id": id});
} catch (e) {
print('Error : $e');
} finally {
await conn.close();
}
}
전체 코드(memoDB.dart)
더보기
// ignore_for_file: avoid_print
// ignore_for_file: file_names
import 'package:flutter_memo_app/config/mySqlConnector.dart';
import 'package:mysql_client/mysql_client.dart';
import 'package:shared_preferences/shared_preferences.dart';
// 모든 메모 보기
Future<IResultSet?> selectMemoALL() async {
// MySQL 접속 설정
final conn = await dbConnector();
// 유저 식별 정보 호출
SharedPreferences prefs = await SharedPreferences.getInstance();
final String? token = prefs.getString('token');
// DB에 저장된 메모 리스트
IResultSet result;
// 유저의 모든 메모 보기
try {
result = await conn.execute(
"SELECT m.id, userIndex, u.userName, memoTitle, memoContent, createDate, updateDate FROM memo AS m LEFT JOIN users AS u ON m.userIndex = u.id WHERE userIndex = :token",
{"token": token});
if (result.numOfRows > 0) {
return result;
}
} catch (e) {
print('Error : $e');
} finally {
await conn.close();
}
// 메모가 없으면 null 값 반환
return null;
}
// 메모 작성
Future<String?> addMemo(String title, String content) async {
// MySQL 접속 설정
final conn = await dbConnector();
// 유저 식별 정보 호출
SharedPreferences prefs = await SharedPreferences.getInstance();
final String? token = prefs.getString('token');
// 쿼리 수행 결과 저장 변수
IResultSet? result;
// 유저의 아이디를 저장할 변수
String? userName;
// 메모 추가
try {
// 유저 이름 확인
result = await conn.execute(
"SELECT userName FROM users WHERE id = :token",
{"token": token},
);
// 유저 이름 저장
for (final row in result.rows) {
userName = row.colAt(0);
}
// 메모 추가
result = await conn.execute(
"INSERT INTO memo (userIndex, memoTitle, memoContent) VALUES (:userIndex, :title, :content)",
{"userIndex": token, "title": title, "content": content},
);
} catch (e) {
print('Error : $e');
} finally {
await conn.close();
}
// 예외처리용 에러코드 '-1' 반환
return '-1';
}
// 메모 수정
Future<void> updateMemo(String id, String title, String content) async {
// MySQL 접속 설정
final conn = await dbConnector();
// 유저 식별 정보 호출
SharedPreferences prefs = await SharedPreferences.getInstance();
final String? token = prefs.getString('token');
// 쿼리 수행 결과 저장 변수
IResultSet? result;
// 메모 수정
try {
await conn.execute(
"UPDATE memo SET memoTitle = :title, memoContent = :content where id = :id and userIndex = :token",
{"id": id, "token": token, "title": title, "content": content});
} catch (e) {
print('Error : $e');
} finally {
await conn.close();
}
}
// 특정 메모 조회
Future<IResultSet?> selectMemo(String id) async {
// MySQL 접속 설정
final conn = await dbConnector();
// 유저 식별 정보 호출
SharedPreferences prefs = await SharedPreferences.getInstance();
final String? token = prefs.getString('token');
// 쿼리 수행 결과 저장 변수
IResultSet? result;
// 메모 수정
try {
result = await conn.execute(
"SELECT m.id, userIndex, u.userName, memoTitle, memoContent, createDate, updateDate FROM memo AS m LEFT JOIN users AS u ON m.userIndex = u.id WHERE userIndex = :token and m.id = :id",
{"token": token, "id": id});
return result;
} catch (e) {
print('Error : $e');
} finally {
await conn.close();
}
return null;
}
// 특정 메모 삭제
Future<void> deleteMemo(String id) async {
// MySQL 접속 설정
final conn = await dbConnector();
// 메모 수정
try {
await conn.execute("DELETE FROM memo WHERE id = :id", {"id": id});
} catch (e) {
print('Error : $e');
} finally {
await conn.close();
}
}
메모 업데이트를 위한 Provider 설정
참고 : 이 포스팅에서 말하는 메모 업데이트는 메모가 추가되거나 변경, 삭제될 경우 리스트 뷰에 출력되는 메모가 정적이지 않고 동적으로 데이터베이스의 데이터에 맞게 변동되는 것을 의미합니다.
- 기본적인 Provider 다루는 방법 보기
프로바이더를 사용하는 이유?
메모가 추가, 변경, 삭제 될 경우 화면은 정적으로 보여집니다.
우리는 메모의 상황에 따라 삭제 될 경우에는 삭제 된 메모 리스트가 출력되며,
수정될 경우에는 수정된 메모의 내용이 리스트 뷰에 출력되기를 원합니다.
그러기 때문에 상태 StatefulWidget으로 상태관리를 하여야 하는데,
이를 편리하게 상태를 관리하게 해주는 패키지가 Provider입니다.
기본 개념만 잘 알고 있다면 Provider없이 해줄 수 있습니다만...
이상하게 그냥 쓰면 자꾸 빌더에서 오류나서... 이 포스팅에서는 프로바이더를 사용합니다.
Flutter 메인 함수에 Provider 래핑
- main.dart
- 참고 : 아래의 소스 코드 사용시 오류가 발생한다면, MaterialApp이 최상위에 선언되지 않아서 오류가 날 가능성이 큽니다. 자신의 프로젝트에서 오류가 발생한다면 하위 요소가 MaterialApp인지 확인해주세요.
- 이 포스팅에서는 MyApp의 하위 위젯은 MaterialApp입니다.
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => MemoUpdator()),
],
child: const MyApp(),
),
);
}
메모 상태 관리를 위한 Provider Class 정의
- 메모 라는 변수 리스트에 상태 관리를 위한 프로바이더 클래스 정의
- 파일명 : memoListProvider.dart
import 'package:flutter/material.dart';
class MemoUpdator extends ChangeNotifier {
List _memoList = [];
List get memoList => _memoList;
// 리스트 업데이트
void updateList(List newList) {
_memoList = newList;
notifyListeners();
}
}
메모 조회하기
개요
아래의 그림과 같이 앱 실행시 로그인 여부를 확인하고,
로그인을 하면 개인 메모를 확인하는 목록을 보여주는 페이지를 생성합니다.
- 메모를 보여줄 페이지를 만들기 위해 파일 생성
- 파일명 : memoMainPage.dart
코드 설계 (memoMainPage.dart)
- 메모 정보를 저장할 변수 선언 및 초기화
// 메모 리스트 저장 변수
List items = [];
- DB에 있는 메모를 불러오기 위한 함수 선언
- 비동기로 메모 리스트를 받아와서 메모가 존재하면 리스트뷰에 메모를 보여주기 위함
// 메모 리스트 출력
Future<void> getMemoList() async {
List memoList = [];
// DB에서 메모 정보 호출
var result = await selectMemoALL();
print(result?.numOfRows);
// 메모 리스트 저장
for (final row in result!.rows) {
var memoInfo = {
'id': row.colByName('id'),
'userIndex': row.colByName('userIndex'),
'userName': row.colByName('userName'),
'memoTitle': row.colByName('memoTitle'),
'memoContent': row.colByName('memoContent'),
'createDate': row.colByName('createDate'),
'updateDate': row.colByName('updateDate')
};
memoList.add(memoInfo);
}
print(memoList);
context.read<MemoUpdator>().updateList(memoList);
}
- 앱 실행시 메모가 보여지기 위해 위에서 정의한 getMemoList 함수 호출
- getMemoList 함수는 MySQL DB의 메모 정보를 불러와 변수의 값을 Provider로 상태 변경을 하는 기능을 함
@override
void initState() {
// TODO: implement initState
super.initState();
getMemoList();
}
- 페이지 본문
- 빌더 위젯을 상위에 래핑하여 메모의 유무에 따라 보여지는 페이지 설계
- context.watch<T>() 메서드를 이용하여 데이터 읽기
- DB에 메모 정보가 없으면 '표시할 메모가 없습니다.' 라는 페이지 표시
- DB에 메모 정보가 있으면 메모 리스트를 리스트뷰에 표시
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Expanded(
child: Builder(
builder: (context) {
// 메모 수정이 일어날 경우 메모 리스트 새로고침
items = context.watch<MemoUpdator>().memoList;
// 메모가 없을 경우의 페이지
if (items.isEmpty) {
return Center(
child: Text(
"표시할 메모가 없습니다.",
style: TextStyle(fontSize: 20),
),
);
}
// 메모가 있을 경우의 페이지
else {
// items 변수에 저장되어 있는 모든 값 출력
return ListView.builder(
itemCount: items.length,
itemBuilder: (BuildContext context, int index) {
// 메모 정보 저장
dynamic memoInfo = items[index];
String userName = memoInfo['userName'];
String memoTitle = memoInfo['memoTitle'];
String memoContent = memoInfo['memoContent'];
String createDate = memoInfo['createDate'];
String updateDate = memoInfo['updateDate'];
// 각각의 메모 카드에 표시
return Card(
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.all(Radius.elliptical(20, 20))),
child: ListTile(
leading: Text(userName),
title: Text(memoTitle),
subtitle: Text(memoContent),
trailing: Text(updateDate),
onTap: () => cardClickEvent(context, index),
),
);
},
);
}
},
),
),
],
),
);
}
}
전체 소스 코드
// 메모 페이지
// 앱의 상태를 변경해야하므로 StatefulWidget 상속
// ignore_for_file: avoid_print, use_build_context_synchronously
import 'package:flutter/material.dart';
import 'package:flutter_memo_app/memoPage/memoDB.dart';
import 'package:flutter_memo_app/memoPage/memoListProvider.dart';
import 'package:provider/provider.dart';
import 'memoDetailPage.dart';
class MyMemoPage extends StatefulWidget {
const MyMemoPage({super.key});
@override
MyMemoState createState() => MyMemoState();
}
class MyMemoState extends State<MyMemoPage> {
// 메모 리스트 저장 변수
List items = [];
// 메모 리스트 출력
Future<void> getMemoList() async {
List memoList = [];
// DB에서 메모 정보 호출
var result = await selectMemoALL();
print(result?.numOfRows);
// 메모 리스트 저장
for (final row in result!.rows) {
var memoInfo = {
'id': row.colByName('id'),
'userIndex': row.colByName('userIndex'),
'userName': row.colByName('userName'),
'memoTitle': row.colByName('memoTitle'),
'memoContent': row.colByName('memoContent'),
'createDate': row.colByName('createDate'),
'updateDate': row.colByName('updateDate')
};
memoList.add(memoInfo);
}
print(memoList);
context.read<MemoUpdator>().updateList(memoList);
}
@override
void initState() {
// TODO: implement initState
super.initState();
getMemoList();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Expanded(
child: Builder(
builder: (context) {
// 메모 수정이 일어날 경우 메모 리스트 새로고침
items = context.watch<MemoUpdator>().memoList;
// 메모가 없을 경우의 페이지
if (items.isEmpty) {
return Center(
child: Text(
"표시할 메모가 없습니다.",
style: TextStyle(fontSize: 20),
),
);
}
// 메모가 있을 경우의 페이지
else {
// items 변수에 저장되어 있는 모든 값 출력
return ListView.builder(
itemCount: items.length,
itemBuilder: (BuildContext context, int index) {
// 메모 정보 저장
dynamic memoInfo = items[index];
String userName = memoInfo['userName'];
String memoTitle = memoInfo['memoTitle'];
String memoContent = memoInfo['memoContent'];
String createDate = memoInfo['createDate'];
String updateDate = memoInfo['updateDate'];
return Card(
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.all(Radius.elliptical(20, 20))),
child: ListTile(
leading: Text(userName),
title: Text(memoTitle),
subtitle: Text(memoContent),
trailing: Text(updateDate),
onTap: () => cardClickEvent(context, index),
),
);
},
);
}
},
),
),
],
),
// 플로팅 액션 버튼
floatingActionButton: FloatingActionButton(
onPressed: () => addItemEvent(context), // 버튼을 누를 경우 메모 추가 UI 표시
tooltip: 'Add Item', // 플로팅 액션 버튼 설명
child: Icon(Icons.add), // + 모양 아이콘
),
);
}
}
참고
반응형