(Flutter-기초 강의) 19. Firebase Database(Realtime Database) 사용하기

Firebase Realtime Database 란?

개요

Firebase Realtime Database는 구글이 제공하는 클라우드 호스팅 데이터베이스입니다.

키-값 형태로 데이터를 저장하고(NoSQL Database), 클라이언트와 서버가 실시간으로 데이터를 동기화할 수 있습니다.

웹과 모바일 앱 개발자들에게 실시간 데이터 처리를 간단하게 만들어주며, 멀티 유저 애플리케이션 구축에 이상적인 환경을 제공합니다.

특징

실시간 데이터 동기화:

데이터가 변경되면, 연결된 모든 클라이언트에게 자동으로 이 변경사항이 실시간으로 전송됩니다.

오프라인 지원

앱이 오프라인일 때도 데이터베이스 작업이 가능하며, 온라인으로 돌아오면 자동으로 동기화됩니다.

유연한 데이터베이스 규칙

Firebase의 보안 규칙을 사용하여 데이터에 대한 접근을 제어할 수 있습니다.

확장성

Firebase Realtime Database는 다양한 크기의 애플리케이션에 맞춰 확장 가능합니다.

서버리스 아키텍처

서버 관리의 필요 없이 데이터베이스 관리를 할 수 있어, 개발 시간과 비용을 절감합니다.

Firebase Realtime Database vs Firestore Database

특징Firestore DatabaseFirebase Realtime Database
데이터 모델JSON키-값
쿼리고급 쿼리 지원기본 쿼리 지원
동기화비동기실시간
용도다양한 앱 개발에 적합실시간 데이터 동기화에 적합

Firestore Database

Firestore Database는 클라우드 기반의 NoSQL 데이터베이스 서비스입니다.

JSON 형태로 데이터를 저장하고, 다양한 쿼리 기능을 제공합니다. Firestore Database는 다음과 같은 특징을 가지고 있습니다.

  • JSON 데이터 모델: 
    • JSON 형태로 데이터를 저장하여, 다양한 데이터 구조를 지원합니다.
  • 고급 쿼리 지원: 
    • 다양한 조건을 사용하여 데이터를 검색할 수 있습니다.
  • 비동기 동기화: 
    • 클라이언트와 서버가 비동기 방식으로 데이터를 동기화합니다.

Firebase Realtime Database

Firebase Realtime Database는 클라우드 기반의 실시간 데이터베이스 서비스입니다.

키-값 형태로 데이터를 저장하고, 클라이언트와 서버가 실시간으로 데이터를 동기화할 수 있습니다.

Firebase Realtime Database는 다음과 같은 특징을 가지고 있습니다.

  • 키-값 데이터 모델: 
    • 키-값 형태로 데이터를 저장하여, 데이터를 쉽게 접근하고 관리할 수 있습니다.
  • 기본 쿼리 지원: 
    • 기본적인 조건을 사용하여 데이터를 검색할 수 있습니다.
  • 실시간 동기화: 
    • 클라이언트와 서버가 실시간으로 데이터를 동기화하여, 데이터가 변경되면 즉시 모든 클라이언트에 반영됩니다.

Database 선택

Firestore Database를 사용하는 경우
  • 다양한 데이터 구조를 지원해야 하는 경우
  • 고급 쿼리 기능이 필요한 경우
Firebase Realtime Database를 사용하는 경우
  • 실시간으로 데이터를 동기화해야 하는 경우
  • 키-값 형태로 데이터를 저장해야 하는 경우

사용 방법

1. Firebase 프로젝트 생성 및 Flutter SDK 연결

기본적인 Firebase 프로젝트를 생성하고, Flutter SDK를 연결하는 것은 전 포스팅인 아래 글을 참조해주세요.

(Flutter-기초 강의) 18. Flutter에서 Firebase 사용하기 (소개 및 프로젝트 설정)

2. Firebase Realtime Database 생성

Firebase console에 접속합니다.

위에서 생성한 프로젝트를 선택합니다.

(본인은 앞선 포스팅에서 생성한 devitworld-tutorial 프로젝트를 사용합니다.)

[Build] -> [Realtime Database] -> [Create Database] 를 선택하고

DB 위치(Location) -> 규칙(Rules) 을 선택하여 Database를 생성합니다.

  • 규칙은 Test 모드로 하지 않으면 기본이 false라 read와 write가 모두 불가합니다.

3. Flutter App 생성 및 연결

Flutter project(테스트 프로젝트 이름은 _7_firebase) 를 생성하고

firebase_core, firebase_database 모듈을 추가했습니다.

$ flutter pub add firebase_core firebase_database

메인 페이지에 App 생성전에 Firebase.initializeApp()을 통해 Firebase를 초기화 합니다.

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  runApp(const MyApp());
}

4. Model 생성

Database에 사용할 모델을 정의합니다.

DataSnapshot 객체(snapshot)를 입력받아 fromSnapshot()을 통해 데이터베이스에서 가져와 정의한 Memo 클래스에 데이터를 로드합니다.

DataSnapshot 객체는 Firebase 데이터베이스의 특정 경로에 있는 Data의 스냅샷으로 데이터를 객체(Memo class)로 로드할 수 있게 합니다.

다시 Memo 클래스의 데이터를 전송할 때는 toJson()을 이용하여 JSON 형태로 변경하여 전송합니다.

/lib/models/memo.dart

import 'package:firebase_database/firebase_database.dart';

class Memo {
  String? key;
  String title;
  String content;
  String createTime;

  Memo(this.title, this.content, this.createTime);

  Memo.fromSnapshot(DataSnapshot snapshot)
      : key = snapshot.key,
        title = (snapshot.value as Map)["title"] ?? '',
        content = (snapshot.value as Map)["content"] ?? '',
        createTime = (snapshot.value as Map)['createTime'] ?? '';

  toJson() {
    return {
      'title': title,
      'content': content,
      'createTime': createTime,
    };
  }
}

5. Database 내용 표시 View

먼저 Database 접근 경로(URL)를 Firebase Console에서 확인합니다.

firebase-database-connect-link

Database의 데이터를 표시할 Page(MemoPage)를 생성합니다.

initState() 에서 Firebase.app()를 통해 Firebase 앱의 인스턴스 (FirebaseApp)를 가져오고

Firebase Database Instance로 사용할 FirebaseDatabase(_database)를 Firebase.instanceFor() 과 앞서 확인한 URL(_databaseURL)을 이용하여 앞서 만든 데이터 베이스에 접근하기 위해 초기화 합니다.

DatabaseReference(reference)를 통하여 Database의 memo에 해당하는 경로를 참조합니다.

이후 DatabaseReference의 onChildAdded.listen()을 통해 memo 경로에 있는 데이터를 실시간으로 감시하게 됩니다.

그렇게 감지된 데이터를 List<Memo>(memos) 리스트에 메모를 추가하여 관리합니다.

/lib/views/memo_page.dart

class _MemoPage extends State<MemoPage> {
  FirebaseDatabase? _database;
  DatabaseReference? reference;
  final String _databaseURL =
      'Database URL'; // URL
  List<Memo> memos = List.empty(growable: true);

  @override
  void initState() {
    super.initState();

    final firebaseApp = Firebase.app();
    _database = FirebaseDatabase.instanceFor(
        app: firebaseApp, databaseURL: _databaseURL);

    reference = _database!.ref().child('memo');

    reference!.onChildAdded.listen(
      (event) {
        print(event.snapshot.value.toString());
        setState(
          () {
            memos.add(Memo.fromSnapshot(event.snapshot));
          },
        );
      },
    );
  }

6. 데이터 표시

GridView 를 이용하여, memo 데이터를 표시 합니다.

/lib/views/memo_page.dart

    return Scaffold(
      appBar: AppBar(
        title: const Text('Memo App'),
      ),
      body: Container(
        child: Center(
          child: memos.isEmpty
              ? const CircularProgressIndicator()
              : GridView.builder(
                  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                    crossAxisCount: 2,
                  ),
                  itemBuilder: (context, index) {
                    return Card(
                      child: GridTile(
                        header: Text(memos[index].title),
                        footer: Text(
                          memos[index].createTime.substring(0, 10),
                        ),
                        child: Container(
                          padding: const EdgeInsets.only(top: 20, bottom: 20),
                          child: SizedBox(
                            child: GestureDetector(
                              onTap: () {},
                              onLongPress: () {},
                              child: Text(memos[index].content),
                            ),
                          ),
                        ),
                      ),
                    );
                  },
                  itemCount: memos.length,
                ),
        ),
      ),

7. 데이터 추가

TextField와 TextEditingController를 통해 데이터를 제목(title)과 내용(content)를 입력받아서

Store 버튼을 누르면 Memo 모델을 만들고 이를 JSON 형태로 변환하여 reference.push().set()을 통해 데이터 베이스에 저장하는 페이지를 생성합니다.

/lib/views/memo_add_page.dart

class _MemoAddPage extends State<MemoAddPage> {
  TextEditingController? titleController;
  TextEditingController? contentController;

  @override
  void initState() {
    super.initState();
    titleController = TextEditingController();
    contentController = TextEditingController();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Add Memo'),
      ),
      body: Container(
        padding: const EdgeInsets.all(20),
        child: Center(
          child: Column(
            children: <Widget>[
              TextField(
                controller: titleController,
                decoration: const InputDecoration(
                    labelText: 'Title', fillColor: Colors.blueAccent),
              ),
              Expanded(
                  child: TextField(
                controller: contentController,
                keyboardType: TextInputType.multiline,
                maxLines: 100,
                decoration: const InputDecoration(labelText: 'Contents'),
              )),
              MaterialButton(
                onPressed: () {
                  widget.reference
                      .push()
                      .set(Memo(
                              titleController!.value.text,
                              contentController!.value.text,
                              DateTime.now().toIso8601String())
                          .toJson())
                      .then((_) {
                    Navigator.of(context).pop();
                  });
                },
                shape:
                    OutlineInputBorder(borderRadius: BorderRadius.circular(1)),
                child: const Text('Store'),
              )
            ],
          ),
        ),
      ),
    );
  }
}

진입은 MemoPage에서 floatingActionButton을 눌러 이동시킵니다.

이동시키면서 DatabaseReference를 파라미터로 함께 전달합니다.

      floatingActionButton: Padding(
        padding: const EdgeInsets.only(bottom: 40),
        child: FloatingActionButton(
          onPressed: () {
            Navigator.of(context).push(MaterialPageRoute(
                builder: (context) => MemoAddPage(reference!)));
          },
          child: const Icon(Icons.add),
        ),

결과

firebase-database-example_result

참고 링크

Leave a Comment