To-Do List 앱 Step 5. 데이터 저장

2024. 12. 2. 08:33같이 공부합시다 - Flutter/Flutter로 To-Do 앱 만들기

728x90
반응형

 
이제 앱을 종료해도 데이터가 유지되도록
저장 기능을 추가해 봅시다 : )
 
한층 더 진짜 서비스 앱처럼 나아가고 있죠? (ㅎ)
 
할 일 목록입니다. 😆
 


3 . 화면에 할 일 리스트 표시.
4 . 할 일 삭제 기능 추가
5 . 데이터 저장 추가
6 . 항목 완료 체크 기능 추가
7 . 완료/미완료 분리 표시 및 필터 기능 추가
8 . 할 일 목록 수정 기능 추가
9 . 완료된 항목을 자동으로 삭제하는 옵션 추가



 
<할 일 목록>


 


 
등록한 할 일이 이렇게나 많은데 !
지금은 앱을 끄면 정보가 삭제되죠. 후~~
 

 
아니 될 말입니다. 당장 저장 기능을 추가하죠 !
온라인 서버를 사용하는 것이 좋겠지만,
당장은 로컬 저장소를 이용하도록 하겠습니다.
( 추후에는 Firebase 를 사용해보죠. 😅 )
 


 
 
자, 로컬에 저장하도록 구성하려면,
‘shared_preferences 라이브러리’를 사용하면 됩니다.
 
Flutter팀이 제공하는 패키지이며,
로컬 저장소에 간단한 데이터를
저장하고 불러오기 위한 도구예요.
 
 
 

1) 라이브러리 추가

 
라이브러리를 추가하려면,
pubspec.yaml 파일에 추가하면 됩니다.
 


 
<의존성 정보 추가>
 


 
의존성 정보를 추가하고 저장하면
자동으로 관련 라이브러리가 다운받아지네요.
 
 


<라이브러리 자동 추가 결과>


 
만약 추가되지 않았다면,
터미널에서 flutter pub get 명령어를 입력해 봅시다.
 
 


 
 
 
라이브러리 추가가 완료되었다면,
이제 우리 main 에서 사용하겠다고
선언해 줘야겠죠 : )
 
다시 main.dart 로 돌아와서,
제일 위쪽에 라이브러리를 추가해 봅시다.
 


<라이브러리 추가>


 
이렇게 라이브러리를
추가해 보는 것은 처음이네요.
좋은 경험이예요 (ㅎ)
하나씩 직접 해봐야 느는 겁니다 : )
 
자, 라이브러리 추가 완료 !
다음 !
 


<세이브와 로드 이미지>

출처 : ChatGPT, 세이브와 로드 이미지


 
저장 기능의 기본은
저장(Save)과 불러오기(Load)죠.
 
게임을 시작하면 저장된 진행 정보를
우선 로드해서 사용하잖아요?
 
앱도 똑같죠 뭐. (ㅎ)
먼저 세이브는 어떻게 하는지 봅시다 !
 
 
 
 


 
 
 

2) 세이브 호출 위치 정하기

 
세이브는 어디에서 할까요?
따로 저장 버튼을 만들어 두는 것 보다는,
할 일 목록을 ‘추가’하거나 ‘삭제’할 때
자동으로 저장되면 편하겠죠?
 
좋은 생각입니다 !
 
그럼 할 일을 추가하는 곳과
삭제하는 곳에 두면 되겠네요.
 
 
<세이브 메서드 호출 위치 설정>

  // 할 일 추가 메서드
  void _addTodoItem(String task) {
    setState(() {
      _todoList.add(task);
    });
    _saveTodoList(); // 로컬 저장소에 저장
  }

  // 할 일 삭제 메서드
  void _deleteTodoItem(int index) {
    setState(() {
      _todoList.removeAt(index);
    });
    _saveTodoList(); // 로컬 저장소에 저장
  }

 
이제 실제 저장하는 함수를 만들면 되겠네요.
 
 
 


 
 
 

3) _saveTodoList 메서드 만들기

<세이브 메서드>

  // 로컬 저장소에 데이터를 저장하는 메서드
  Future<void> _saveTodoList() async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setStringList('todoList', _todoList);
  }

  // 할 일 추가 메서드
  void _addTodoItem(String task) {
    setState(() {
      _todoList.add(task);
    });
    _saveTodoList(); // 로컬 저장소에 저장
  }

  // 할 일 삭제 메서드
  void _deleteTodoItem(int index) {
    setState(() {
      _todoList.removeAt(index);
    });
    _saveTodoList(); // 로컬 저장소에 저장
  }

 
Future는 언제 저장될 지 모르기 때문이고,
 
SharedPreferences.getInstance(); 는
라이브러리에서 제공하는 저장 객체를 가져오고,
 
가져온 객체를 prefs 변수에 담습니다.
이 때부터는 prefs를 저장 객체로 쓸 수 있어요.
 
저장 객체는 key & value 로 이루어진 객체이며,
저장하고 가져오는 기능이 포함되어 있어요.
 
prefs.setStringList('todoList', _todoList); 로
현재 ‘할 일 목록’(리스트)를 로컬 정보인
todoList 에 저장할 겁니다.
 
await 와 async 는 사용자의 호출,
그러니까 할 일을 추가하거나 삭제할 때까지
기다렸다가(await) 싱크(async)를
맞춘다고 보면 되겠네요.
 
좋아요! 메서드 완성!
 
바로 테스트를 해보고 싶지만,
아직 중요한 처리를 안했어요.
 
저장한 정보를 로드해 보려면
상태 정보를 먼저 초기화 해야 합니닷 !
다음 !
 
 
 


 
 
 

4) state 초기화 설정하기

 
자, 기존의 데이터가 있는지 로드하려고 합니다.
 
그런데 여기서 꼭 살펴 볼 내용이 있어요.
기존 데이터와 불러오는 데이터가 충돌되면 어쩌죠?
 


<저장된 데이터와 로드된 데이터가 충돌되는 모습>

출처 : ChatGPT, 세이브와 로드의 충돌


 
사설이지만, 어렸을 적 게임을 하다보면
이전 세이브를 로드하는데 문제가 생겨서
세이브 데이터가 날아간 적도 있죠 ㅠ^ㅠ
(다시 처음부터 키워야 했답니다..)
 
이런 상황을 개발자들도 겪었었던 것 같아요.
그래서 아마 플러터에서도
‘초기화’하는 부분을 넣었을 겁니다.
 
우리는 지금 StatefulWidget을 사용중입니다.
 
상태값, 그러니까 ‘할 일 목록’이라는 리스트에,
할 일이 추가되거나 삭제되면 setState()를 통해
바로바로 상태를 체크하여 화면에 뿌려주죠.
 
따라서, StatefulWidget 의 State 가 있는
_TodoListScreenState 에서 초기화를 할 겁니다.
 
<초기화 메서드>

class _TodoListScreenState extends State<TodoListScreen> {
  final List<String> _todoList = []; // 할 일 목록

  @override
  void initState() {
    super.initState();
    _loadTodoList(); // 앱 시작 시 로컬 저장소에서 데이터 로드
  }

 
super.initState 로 초기화를 합니다.
 
그리고 초기화 후에 바로 저장되어 있는
할 일 리스트를 로드해 두면 되는거죠.
 
음? 그런데 void initState() 는
따로 호출하지 않아도 되는건가요?
 
아주 좋은 질문입니다!
 
initState() 는 State 클래스에서
제공하는 ‘생명주기 메서드’입니다.
 
결론만 말하면 자동으로 실행되는거죠.
하나 더 배웠네요. 좋습니다!
다음!
 
 


 
 
 

4) _loadTodoList 메서드 만들기

 
초기화하고 로드하는 메서드 호출도 바로 했어요.
그럼 저장된 데이터를 로드하는 메서드를 작성해야겠죠.
 
바로 갑시다!
 
<로드 메서드>

class _TodoListScreenState extends State<TodoListScreen> {
  final List<String> _todoList = []; // 할 일 목록

  @override
  void initState() {
    super.initState();
    _loadTodoList(); // 앱 시작 시 로컬 저장소에서 데이터 로드
  }

  // 로컬 저장소에서 데이터를 불러오는 메서드
  Future<void> _loadTodoList() async {
    final prefs = await SharedPreferences.getInstance();
    final List<String>? loadedList = prefs.getStringList('todoList');
    if (loadedList != null) {
      setState(() {
        _todoList.addAll(loadedList);
      });
    }
  }

 
세이브랑 비슷하쥬? 그렇습니다.
 
로드할 때에도 담아둘 수 있는 저장 객체가
있어야 하는 것이 눈에 들어오시죠? 굿굿!
 
저장 메서드에서 봤던 ‘todoList’는
로컬에 저장된 키워드라고 보시면 됩니다.
여기에 할 일 리스트가 저장되어 있는 거죠.
 
그 리스트를 로드 메서드가 호출되는 순간
가져와서 _todoList <List> 에 모두 담습니다.
바로 _todoList.addAll(loadedList) 내용이죠.
 
 
 


 
 
 

5) 테스트

 
여기까지 잘 따라 오셨습니다.
 
좋아요!
이제 테스트를 합시다.
 
Ctrl + F5 !! 뙇 !!
 
……..?
 
맙소사.
 
저는 오류가 있네요.
 
오류가 없으시다면 짝짝짝!
축하드립니다. 끝 ! 하셔도 됩니다.
 
하지만 저와 같은 빌드 오류가 난다?
그럼 더 따라 오시죠 ㅎㅎ
 
Error: Building with plugins requires symlink support.
 
허얼.
 
확인해보니 Windows 앱으로 띄우려면
따로 권한이 필요하다고 하네요.
 
자! 따라 오시죠!
 


<개발자 모드 허용 설정 띄우는 명령어>


 
<개발자 모드 오픈>


<팝업 확인>


<다시 정상 실행 완료>


 
이제 드디어 끝났습니다.
 
고생하셨습니다!
끝!



오늘까지의 코드

728x90
반응형