ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [FLUTTER] shared_preferences 패키지 사용해보기 (앱 자체 데이터 저장소)
    Flutter 2023. 4. 6. 18:37

    Flutter에서는 앱 자체 데이터베이스를 사용할 수 있다.

    그러나 Flutter에서는 iOS와 Android에서 사용하는 기본 데이터베이스가 다르다.

    나는 iOS만 사용해봤는데,  iOS에서 사용하는 기본 데이터베이스는 CoreData이고, Android에서 사용하는 기본 데이터베이스는 SQLite라고한다.

    Flutter에서는 이러한 기본 데이터베이스를 직접 사용할 수 있지만, 이를 위해서는 각 플랫폼에서 지원하는 네이티브 코드를 작성해야 야하는 불편함이 존재한다. 또한, shared_preferences 패키지를 통해 앱 자체 데이터베이스를 대체할 수 있는 영구적인 데이터 저장소를 제공하지만, 이는 일종의 key-value 저장소로, 복잡한 데이터 모델링을 지원하지 않는다고 한다. (ㅠㅠ따라서 사용 시에 주의해야 한다ㅠㅠ)

     

    https://org9899.tistory.com/121

     

    [FLUTTER] 화면전환 (Navigator)

    [Navigator 화면전환] 각 화면을 라우트(Route)라고 부르며, 화면을 이동할 때 네비게이터(Navigator)를 사용 다음페이지로 이동 Navigator.push( context, MaterialPageRoute(builder: (context) => SecondPage()), // 이동하려

    org9899.tistory.com

    위 스토리를 보면 온보딩 페이지를 볼 수 있다. (하단의 온보딩 페이지 전체 소스코드 참고)

     

    온보딩을 완료했지만 Restart를 눌러 앱을 재시작 하는 경우, 온보딩 페이지로 계속 시작되는 문제가 있다. 이런 경우 보통 온보딩 완료 여부를 어딘가에 저장해두고 앱 실행시 해당 값을 불러와 온보딩을 완료한 경우 바로 HomePage를 보여주는 방식으로 해결한다.

    그런데 지금까지 작성한 모든 코드는 메모리(Memory)라는 공간에서 실행되는데, 이 공간은 앱을 재시작 하거나 종료하는 경우 모두 사라져버리는 문제가 있다!

     

    💡 데이터를 메모리가 아닌 다른 곳에 저장해 앱을 재시작해도 이전 데이터를 유지하는 방법

    1. 기기에 파일로 저장하기 내용을 파일로 저장해두고 앱을 시작할 때 파일을 읽어오는 방식. (패키지 : shared_preferences)
    2. 기기 데이터베이스에 저장하기 모든 폰에는 SQLite라는 데이터베이스(데이터 저장 전문 프로그램)이 있는데 이 데이터베이스를 이용하여 데이터를 보존할 수 있다 -좀 더 복잡한 데이터를 기기에 저장할 수 있음. (패키지 : sqflite)
    3. 다른 컴퓨터(서버)에 저장하기 인터넷을 통해 다른 컴퓨터에 데이터를 전송하여 저장하는 방식. 

    여기선 shared_preferences 패키지를 이용하여 1. 기기에 파일로 저장 방법을 배워보도록 할 것이다! 

     

    1. 패키지 설치

    https://pub.dev/packages/shared_preferences/install

     

    shared_preferences | Flutter Package

    Flutter plugin for reading and writing simple key-value pairs. Wraps NSUserDefaults on iOS and SharedPreferences on Android.

    pub.dev

    • 터미널에서 아래 명령어를 입력
    flutter pub add shared_preferences
    • 사용할 페이지에 import
    import 'package:shared_preferences/shared_preferences.dart';

     

    2. shared_preferences 패키지 사용법

     

    2-1. 사용준비

    SharedPreferences를 이용하려면 먼저 SharedPreferences 인스턴스를 가져와야한다. (가져온 인스턴스를 prefs 변수에 저장)

    // 인스턴스 생성
    SharedPreferences prefs = await SharedPreferences.getInstance();
    기기에 저장된 파일을 읽어오는데, 다소 시간이 걸리는데 완료될 때 까지 기다리도록 앞에 await이라는 키워드를 붙여준다.

    await란?

     

    2-2. 값 저장하기

    SharedPreferences는 데이터를 Key와 Value로 구성된 Map 형태로 데이터를 저장한다.

    저장시 사용되는 Key는 원하는 이름으로 정하시면 된다!

    예를들어, 온보딩을 완료했는지 여부를 isOnboarded라는 이름의 Key에 bool 타입을 저장한다면 아래와 같다.
    prefs.setBool("isOnboarded", true);

     

    2-3. 값 불러오기

    저장한 isOnboarded라는 Key를 이용해 다시 값을 가져올 수 있다. (저장된 값이 없는 경우 null을 반환하는 점 유의!)

    bool? value = prefs.getBool("isOnboarded");
    SharedPreferences에는String, List<String>, double, int, bool 타입을 저장 할 수 있다.

    좀 더 자세한 사항은 공식 문서 참고

    https://pub.dev/documentation/shared_preferences/latest/shared_preferences/SharedPreferences-class.html

     

    SharedPreferences class - shared_preferences library - Dart API

    Wraps NSUserDefaults (on iOS) and SharedPreferences (on Android), providing a persistent store for simple data. Data is persisted to disk asynchronously. Properties hashCode → int The hash code for this object. read-onlyinherited runtimeType → Type A r

    pub.dev

     

    3. shared_preferences 사용준비

     

    3-1. main 함수에서 앱이 시작되기 전에, SharedPreferences 인스턴스를 불러오기

    void main() async {	
    	// main() 함수에서 async를 쓰려면 필요
      WidgetsFlutterBinding.ensureInitialized();
    
      // shared_preferences 인스턴스 생성
      SharedPreferences prefs = await SharedPreferences.getInstance();

    위 코드를 main에 추가하면, 아래와 같다. 아마 위 코드를 추가하면 오류가 나올것이다. 그 오류는 await 키워드를 사용할때는 async를 사용해야한다는 키워드이다! → 파일을 읽을 때 시간이 걸리므로 await을 앞에 적어 기다리도록 만들어준다. 단, await 사용시 해당 함수의 소괄호와 중괄호 사이에 async를 추가해야한다!

    왜 async를 사용해야하는지?

    → await는 Promise 객체가 resolve되기를 기다리는 데 사용되는 키워드인데, await를 사용하면 Promise가 resolve될 때까지 코드 실행이 중지되고, Promise가 resolve되면 결과값을 반환한다. async 함수는 비동기적인 코드를 작성할 때 사용되며, await를 포함하는 함수가 비동기 함수이기 때문이다. async 함수 내에서 await를 사용하면 자동으로 해당 함수는 Promise를 반환한다.

    (async 함수가 아닌 일반적인 함수에서 await를 사용하려고 하면 SyntaxError가 발생!)

    void main() async {
      // main() 함수에서 async를 쓰려면 필요
      WidgetsFlutterBinding.ensureInitialized();
    
      // shared_preferences 인스턴스 생성
      SharedPreferences prefs = await SharedPreferences.getInstance();
    
      runApp(MyApp());
    }

    → 또한, main 함수에서 async & await을 사용하려면 WidgetsFlutterBinding.ensureInitialized();를 넣어줘야한다!

     

    • 위 코드에서 보면 현재 SharedPreferences 인스턴스인 prefs는 main 함수 내부에서만 사용 가능하다.

    향후 prefs 변수를 OnboardingPage 클래스와 HomePage 클래스 등 여러 곳에서 사용할 예정인데, 각 클래스 생성자에 전달하면 코드가 길어지므로 어디서든 접근 가능한 전역 변수로 변경!

    late SharedPreferences prefs; // 전역변수
    
    void main() async {
      // main() 함수에서 async를 쓰려면 필요
      WidgetsFlutterBinding.ensureInitialized();
    
      // shared_preferences 인스턴스 생성
      prefs = await SharedPreferences.getInstance();
    
      runApp(MyApp());
    }

    late 키워드를 붙여주지않는다면? 

    : late를 붙이지않고 마우스를 올려봤을때, 비어있을 수 없는 변수(non-nullable variable)이므로 초기값을 넣어라는 에러가 발생한다.

    SharedPreferences 뒤에 비어있을 수 있다는 의미의 물음표(?)를 붙여주어 해결할 수 있지만, main 함수에서 값을 넣어주므로 나중에 값을 할당해 준다는 의미로 7번째 줄 앞에 late 키워드를 추가해 문제를 해결한다!


    • late 키워드 배우기

    아래 코드는 String name = null;과 동일한 코드로 에러가 발생한다.

    String name; // null이 될 수 없는 타입에 null이 할당되어 에러 발생

    위 코드를 고치는 첫 번째 방법String?null 또는 String이라고 표시하는 것이다.

    String? name; // null 또는 String

    두 번째 방법은 앞에 late라는 키워드를 붙여 나중에 String 을 넣어줄 거라고 선언하는 방식이다.

    late String name; // 나중에 String을 넣을거다.

    위와 같은 경우 나중에 아래와 같이 값을 넣어주면 된다.

    late String name; // 나중에 String을 넣을거다.
    name = "철수";
    print(name); // 철수

    단, 값을 넣기 전에 호출하면 에러가 난다.

    late String name; // 나중에 String을 넣을거다.
    print(name); // 아직 값을 안넣었으므로 에러 발생

    4. 온보딩 여부 저장하고 불러오기

    isOnboarded라는 Key로 온보딩 완료 여부를 저장하고, 만약 값이 true인 경우 앱 실행시 HomePage를 바로 띄워주는 방식으로 구현

     

    4-1. 처음 어떤 화면을 보여줄지 결정하는 MyApp 위젯에서 온보딩 여부를 나타내는 isOnboarded 값을 가져오기

    // SharedPreferences에서 온보딩 완료 여부 조회
    // isOnboarded에 해당하는 값에서 null을 반환하는 경우 false 할당
    bool isOnboarded = prefs.getBool("isOnboarded") ?? false;

    getBool("isOnboarded") 함수를 호출시 아무런 값이 없는 경우 null을 반환하는데, null인 경우 false를 반환하도록 ?? false를 붙여주었음.

     

    4-2. isOnboarded 값이 true인 경우 바로 HomePage 위젯을 보여주고, 그렇지 않은 경우 OnboardingPage 위젯을 보여주도록 하겠습니다. 아래 코드스니펫을 복사해서 31번째 줄을 교체한 뒤 저장

    home: isOnboarded ? HomePage() : OnboardingPage(),

     

    • MyApp
    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        // SharedPreferences에서 온보딩 완료 여부 조회
        // isOnboarded에 해당하는 값에서 null을 반환하는 경우 false 할당
        bool isOnboarded = prefs.getBool("isOnboarded") ?? false;
        return MaterialApp(
          debugShowCheckedModeBanner: false,
          theme: ThemeData(
            textTheme: GoogleFonts.getTextTheme('Jua'),
          ),
          home: isOnboarded ? HomePage() : OnBoardingPage(),
        );
      }
    }

     

    4-3. OnboardingPage에서 Done 버튼을 누르는 시점에 온보딩을 완료했다고 볼 수 있음. 이 때 isOnboarded 값을 true로 저장. 

    // Done 클릭시 isOnboarded = true로 저장
    prefs.setBool("isOnboarded", true);

     

    • onDone
    onDone: () {
              // When done button is press
    
              // Done 클릭시 isOnboarded = true로 저장
              prefs.setBool("isOnboarded", true);
    
              // Done 클릭시 페이지 이동
              Navigator.pushReplacement(
                // 기존의 OnboardingPage는 없애고, HomePage로 교체하는 방식으로! (push->pushReplacement)
                context,
                // 이동하고 싶은 페이지 이름 적어주기
                MaterialPageRoute(builder: (context) => HomePage()),
              );
            },

     

    : 여기까지 완료했으면 restart 버튼을 눌러도 온보딩 페이지가 보이지않음.

     

    + 초기화하는 코드를 추가해 확인해보는 과정

    	
    				actions: [
              // 삭제 버튼
              IconButton(
                onPressed: () {
                  // SharedPreferences에 저장된 모든 데이터 삭제
                  prefs.clear();
                },
                icon: Icon(Icons.delete),
              )
            ],

    : 아래와 같은 위치에 위 코드 추가

    class HomePage extends StatelessWidget {
      // 페이지 이름 : HomePage
      const HomePage({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("Home Page!"),
            actions: [
              // 삭제 버튼
              IconButton(
                onPressed: () {
                  // SharedPreferences에 저장된 모든 데이터 삭제
                  prefs.clear();
                },
                icon: Icon(Icons.delete),
              )
            ],
          ),
          body: Center(
            child: Text(
              "환영합니다!",
              style: TextStyle(
                fontSize: 24,
              ),
            ),
          ),
        );
      }
    }

    : prefs.clear();를 호출하면 저장된 모든 SharedPreferences를 초기화

     

    • 결과

    : 휴지통 버튼을 누르면 저장된 데이터가 전부 사라지면서 온보딩 페이지가 다시 나옴, 그 전까지는 restart를 눌러도 HomePage 화면만 나옴

     

    'Flutter' 카테고리의 다른 글

    [FLUTTER] 리펙토리(Refactory)  (0) 2023.04.07
    [FLUTTER] 비동기처리 (async와 await) -화면 간 데이터 전달  (0) 2023.04.07
    [FLUTTER] 화면전환 (Navigator)  (0) 2023.03.23
    [FLUTTER] Stack  (0) 2023.03.23
    [FLUTTER] Column & Row  (0) 2023.03.23
Designed by Tistory.