Flutter - 파일에 데이터 저장하기(2)

유의선·2024년 3월 28일
0

이번에는 앱 안에 미리 파일로 만들어 놓고 이를 사용하는 방법을 실습해보았다.

이전 프로젝트에 이어서 코드를 작성하였다.


프로젝트 아래 repo 폴더를 만들고 그 아래 fruit.txt 텍스트 파일을 만들었다.
해당 텍스트 파일에 과일 이름을 한 줄에 하나씩 적었다.


pubspec.yaml 파일에 shared_preferences 패키지를 추가하고
flutter: 항목에 fruit.txt 파일을 에셋으로 등록했다.

dependencies:
  flutter:
    sdk: flutter


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2
  path_provider: ^2.0.2
  shared_preferences: 2.0.6
  
  ...
  
flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true
  assets:
    - repo/fruit.txt

fileApp.dart 파일의 _FileApp 클래스에 readListFile() 함수를 만들었다. 이 함수는 fruit.txt 파일에서 데이터를 읽어와 내부 저장소인 sharedPreferences에 저장한 다음 리스트를 만드는 함수다.

import 'package:flutter/material.dart';
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';

class FileApp extends StatefulWidget { ... }

class _FileApp extends State<FileApp>{
  int _count = 0;

  
  void initState(){ ... }

  
  Widget build(BuildContext context) { ... }

  void writeCountFile(int count) async { ... }

  void readCountFile() async { ... }

  Future<List<String>> readListFile() async {
    List<String> itemList = new List.empty(growable: true);
    var key = 'first';
    SharedPreferences pref = await SharedPreferences.getInstance();
    bool? firstCheck = pref.getBool(key);
    var dir = await getApplicationDocumentsDirectory();
    bool firstExist = await File(dir.path + '/fruit.txt').exists();

    if(firstCheck == null || firstCheck == false || firstExist == false){
      pref.setBool(key, true);

      var file = await DefaultAssetBundle.of(context).loadString('repo/fruit.txt');
      File(dir.path + '/fruit.txt').writeAsStringSync(file);
      
      var array = file.split('\n');
      for(var item in array){
        print(item);
        itemList.add(item);
      }

      return itemList;
      
    } else {
      var file = await File(dir.path + '/fruit.txt').readAsString();
      
      var array = file.split('\n');
      for(var item in array){
        print(item);
        itemList.add(item);
      }
      
      return itemList;
    }
  }
}

먼저 sharedPreferences 에서 first 라는 키로 저장된 데이터를 불러온다.
이곳에는 파일을 처음 열었는지 여부가 저장된다.
파일을 처음 열었는지의 여부를 firstCheck에 저장한다.

    var key = 'first';
    SharedPreferences pref = await SharedPreferences.getInstance();
    bool? firstCheck = pref.getBool(key);

다음에는 내부 저장소에 파일이 있는지 확인한다.
File().exists 함수로 내부 저장소에 fruit.txt 파일이 있는지 확인 후
fileExist에 저장한다.

    var dir = await getApplicationDocumentsDirectory();
    bool firstExist = await File(dir.path + '/fruit.txt').exists();

파일을 처음 열었다면 or 내부 저장소에 파일이 없다면
sharedPreferencesfirst 키를 가지는 데이터에 true를 저장하고,
fruit.txt 파일을 내부 저장소에 만들고,
에셋에 등록한 fruit.txt 파일의 내용을 내부 저장소에 저장한다.
그 후 리스트를 만든다.

    if(firstCheck == null || firstCheck == false || firstExist == false){
      pref.setBool(key, true);

      var file = await DefaultAssetBundle.of(context).loadString('repo/fruit.txt');
      File(dir.path + '/fruit.txt').writeAsStringSync(file);
      
      var array = file.split('\n');
      for(var item in array){
        print(item);
        itemList.add(item);
      }
    }

파일을 처음 연게 아니고 내부 저장소에 파일이 있다면
내부 저장소의 파일을 대상으로 데이터를 불러와 리스트를 만든다.

} else {
      var file = await File(dir.path + '/fruit.txt').readAsString();
      
      var array = file.split('\n');
      for(var item in array){
        print(item);
        itemList.add(item);
      }

      return itemList;
    }

앱을 실행할 때 readListFile() 함수가 실행되도록 코드를 추가했다.

class _FileApp extends State<FileApp>{
  int _count = 0;
  List<String> itemList = new List.empty(growable: true);
  TextEditingController controller = new TextEditingController();

  
  void initState(){
    super.initState();

    readCountFile();
    
    initData();
  }

  ...

  void initData() async {
    var result = await readListFile();
    setState(() {
      itemList.addAll(result);
    });
  }

  ...

}

async를 사용한 initData() 함수를 만들고, 그 안에서 readListFile() 함수를 호출하고, setState() 함수를 이용해 리스트를 만든다.

initState() 함수 안에서 initData() 함수를 실행시켜 앱을 실행할 때 실행되도록 만들었다.


화면에 리스트를 표시하도록 build() 함수를 다음처럼 수정했다.

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('File Example'),
      ),
      body: Container(
        child: Center(
          child: Column(
            children: <Widget>[
              TextField(
                controller: controller,
                keyboardType: TextInputType.text,
              ),
              Expanded(
                  child: ListView.builder(
                      itemBuilder: (context, index) {
                        return Card(
                          child: Center(
                            child: Text(
                              itemList[index],
                              style: TextStyle(fontSize: 30),
                            ),
                          ),
                        );
                      },
                      itemCount: itemList.length))
            ],
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {},
        child: Icon(Icons.add),
      ),
    );
  }

ListView.builder를 이용해 리스트 데이터를 화면에 표시한다.
이 때 Expanded 위젯을 이용해 텍스트필드 이외의 나머지 부분을 모두 ListView로 사용하였다.
Expanded를 사용하지 않으면 ListView는 내용의 길이를 가늠하지 못해 렌더링 오류가 생긴다.


다음으로 과일 이름을 입력하고 Floating Button을 누르면 리스트에 해당 과일을 추가하는 기능을 만들었다.

먼저 사용자가 입력한 과일 이름을 내부 저장소의 fruit.txt 파일에 추가하는 writeFruit() 함수를 _FileApp 클래스에 만들었다.

  void writeFruit(String fruit) async {
    var dir = await getApplicationDocumentsDirectory();
    var file = await File(dir.path + '/fruit.txt').readAsString();
    file = file + '\n' + fruit;
    File(dir.path + '/fruit.txt').writeAsStringSync(file);
  }

fruit.txt 파일의 내용을 가져와 매개변수로 전달받은 과일 이름을 추가한 후 다시 파일에 기록한다.

writeFruit() 함수를 Floating Button의 onPressed 이벤트 처리 함수에 호출한다.

      floatingActionButton: FloatingActionButton(
        onPressed: () {
          writeFruit(controller.value.toString());
          setState(() {
            itemList.add(controller.value.text);
          });
        },
        child: Icon(Icons.add),
      ),

이 때 사용자가 입력한 값이 들어 있는 controller.value.txt를 매개변수로 넘긴다.
그 후 리스트에 이 값을 추가한다.

0개의 댓글