티스토리 뷰
데이터 추가 및 삭제하기
SharedPreference 함수 추가하기
// 키값을 통해 Json 리스트 불러오기
static List<Map<String, dynamic>> getJsonList(String key) {
List<String>? dataList = _prefs?.getStringList(key);
return dataList
?.map((value) => json.decode(value) as Map<String, dynamic>)
.toList() ?? [];
}
// Json 리스트 키에 저장하기
static Future<bool> setJsonList(String key, List<Map<String, dynamic>> list) {
List<String> dataList = list.map((map) => json.encode(map)).toList();
return _prefs!.setStringList(key, dataList);
}
// 키값 삭제
static Future<bool> remove(String key) async {
return await _prefs!.remove(key);
}
shared_preference_util.dart에 추가해준다.
운동기록을 출력하려면 List<Map<String, dynamic>>형태의 데이터를
키값에 저장하고 불러오는 작업이 필요하다.
List<Map<String, dynamic>> # [{}, {}, {}] < 이런 형태의 자료형이다.
참고로 Map<String,dynamic>은 json형태의 데이터를 의미한다.
List<map<string, dynamic>> < 자료형이 뭔가 복잡해보이지만
리스트에 json 데이터가 들어간 자료형이라고 보면 된다.
static Future<bool> setJsonList(String key, List<Map<String, dynamic>> list) {
List<String> dataList = list.map((map) => json.encode(map)).toList();
return _prefs!.setStringList(key, dataList);
}
json 리스트를 키에 저장하는 함수이다.
SharedPreference에서 json 리스트를 저장하는 함수가 따로 없어
일단 setStringList로 List<String>형의 데이터를 추가해준다.
static List<Map<String, dynamic>> getJsonList(String key) {
List<String>? dataList = _prefs?.getStringList(key);
return dataList
?.map((value) => json.decode(value) as Map<String, dynamic>)
.toList() ?? [];
}
List<String>형의 데이터를 가진 키값을 dataList라는 변수에 저장하고
String형인 dataList의 요소를 json형으로 파싱을 진행하여 결과적으로 json리스트를 반환한다.
키값이 null일 때는 빈 배열을 반환하여 null 오류를 방지한다.
함수 작동 테스트
class HomeController with ChangeNotifier {
List<Map<String, dynamic>> dataList = [];
// 데이터리스트를 초기화한다.
void initDataList() {
dataList = SharedPreferencesUtil.getJsonList('dataList');
}
// 데이터리스트에 추가된 데이터를 키에 저장한다.
void addData(data) {
dataList.add(data);
notifyListeners();
SharedPreferencesUtil.setJsonList('dataList', dataList);
}
// 데이터리스트를 빈 배열로 초기화하고 키값을 지운다.
void deleteData() {
dataList.clear();
notifyListeners();
SharedPreferencesUtil.remove('dataList');
}
}
작성한 함수가 잘 작동하는지 테스트를 하기 위해
데이터를 불러오는 함수, 추가하는 함수, 삭제하는 함수
총 3개의 함수를 HomeController에 작성해준다.
dataList의 변경사항을 화면에 바로 적용시키려면
notifyListner() 를 함수에 추가해줘야한다.
@override
void initState() {
super.initState();
_controller.initDataList();
}
initState에서 빈 배열을 'dataList'의 키값으로 초기화한다.
Widget _buildPage(BuildContext context) {
return SingleChildScrollView(
child: Column(
화면 오버플로우를 방지하기 위해
Column을 SingleChildScrollView로 감싸준다.
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CommonButton(
width: 140,
text: '데이터 추가',
onPressed: () {
_controller.addData({
'day': 1,
'ex1': '풀업',
'ex2': '푸쉬업',
'ex1List': [10, 10, 10],
'ex2List': [10, 10, 10]
});
},
),
CommonButton(
width: 140,
text: '데이터 삭제',
onPressed: () {
_controller.deleteData();
},
)
],
);
화면 맨 아래에 문자열 형태의 데이터리스트와 데이터 추가 및 삭제 버튼을 추가한다.
홈화면으로 라우팅 될 떄 dataList는 키값으로 초기화되므로
새로고침을 해도 dataList의 변경사항은 그대로 유지된다.
여기까지 했다면 리스트뷰 출력을 위한 준비는 끝이났다.
운동기록 출력 리스트뷰
3항 연산자의 활용
Center(
child: _controller.dataList.isEmpty
? Column(
children: [ // 운동기록이 없을 때 처리 ]
)
: SizedBox(
child: // 운동기록 출력
)
)
3항 연산자를 이용해 리스트가 비어있는지의 여부에 따라 다른 화면을 출력해준다.
개요
운동 데이터별로 구분 선이 있어야 하고 스크롤은 비활성화 상태여야한다.
이 조건을 만족한 상태에서 최근 3일의 운동기록을 보여줘야 한다.
기본조건 만족
SizedBox(
height: _controller.returnListViewHeight(), // 데이터 개수에 따른 리스트뷰 높이 설정
child: ListView.separated(
physics: const NeverScrollableScrollPhysics(), // 스크롤 비활성화
itemBuilder: (context, index) {
final data = _controller.dataList.length > 3 // 운동기록이 3일을 넘어가면
? _controller.dataList[
index + _controller.dataList.length - 3] // 최근 3일부터 출력
: _controller.dataList[index];
데이터마다 구분선을 주기 위해서 ListView.separated 위젯을 사용해줬다.
여기서 index는 0 ~ 운동일수의 범위에 해당하는 값이다.
운동기록이 3일을 넘어갈 때와아닐 때의 데이터를 다른값으로 할당해준다.
double returnListViewHeight() {
if (dataList.length == 1) {
return 63;
} else if (dataList.length == 2) {
return 140;
} else {
return 217;
}
}
운동일수가 1일이거나 2일일때도 고려해야한다.
SizedBox의 height 프로퍼티에는 double형 값이 들어가야한다.
separatorBuilder:
(BuildContext context, int index) {
return const Column(
children: [
Divider(
color: Color(0xFFEAEAEA),
height: 4,
)
],
);
},
구분선은 separatorBuilder 프로퍼티를 이용해서 줄 수 있다.
데이터 출력
int sum1 = (data['ex1'][0] +
data['ex1'][1] +
data['ex1'][2]);
int sum2 = data['ex2'][0] +
data['ex2'][1] +
data['ex2'][2];
코드의 가독성을 위해 필요한 변수들은 리스트뷰 내에서 선언해준다.
return ListTile(
contentPadding: const EdgeInsets.only(left: 2),
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
'${data['day']}일차',
style: TextStyles.b3Medium,
),
],
),
const SizedBox(
height: 2,
),
],
),
subtitle: Column(
children: [
Row(
children: [
Text(
'${data['ex1_name']}',
style: TextStyles.b4Medium,
),
const SizedBox(
width: 8,
),
Text(
'${data['ex1'].join(' ')}',
style: TextStyles.b4Regular,
),
const SizedBox(
width: 5,
),
Text(
'($sum1개)',
style: TextStyles.b4Regular.copyWith(
color: context.isLight
? LightModeColors.dark3
: DarkModeColors.dark3),
)
],
),
Row(
children: [
Text(
'${data['ex2_name']}',
style: TextStyles.b4Medium,
),
const SizedBox(
width: 8,
),
Text(
'${data['ex2'].join(' ')}',
style: TextStyles.b4Regular,
),
const SizedBox(
width: 5,
),
Text(
'($sum2개)',
style: TextStyles.b4Regular.copyWith(
color: context.isLight
? LightModeColors.dark3
: DarkModeColors.dark3),
)
],
),
],
),
);
리스트뷰는 ListTile위젯을 리턴한다.
data['키']로 해당 운동일자의 데이터에서 원하는 키값을 가지고 올 수 있다.
콘솔창 오류메시지 해결
itemCount: _controller.dataList.length > 3
? 3
: _controller.dataList.length
리스트뷰의 itemCount 프로퍼티로 반환할 수 있는 ListTile위젯의 수를 제한할 수 있다.
3으로 주거나 운동일수로 값을 줘도 화면상에서 문제는 발생하지 않지만
두 경우의 값을 처리해주지 않으면 보이지 않는 곳에서 index range error가 뜨게된다.
테스트
테스트를 위해 임시적으로 버튼을 통해서 값을 추가 혹은 제거를 하였다.
'멸치탈출 (플러터)' 카테고리의 다른 글
[플러터] 운동 기록이 없으면 1주일 후 일지를 초기화 시키는 타이머 구현 (0) | 2024.05.12 |
---|---|
[플러터] 전체 일지 확인 화면 구현 (운동 종목 별로 전날 대비 개수 출력) (0) | 2024.05.06 |
[플러터] 홈 화면 그리기 & 다이얼로그 사용법 (1) | 2024.04.21 |
[플러터] 이름 입력 페이지 구현 (싱글톤 패턴을 활용한 shared_preferences 사용법) (1) | 2024.04.20 |
[플러터] 앱 테마 변경 기능 구현 (1) | 2024.04.19 |